From ca8817c6a474c0d47e21956591b062321da7b344 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 16 Feb 2023 15:06:25 -0800 Subject: [PATCH 001/100] initial --- package-lock.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 88e0e1722..521b28d44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,14 @@ { + "name": "snapcon", + "lockfileVersion": 3, "requires": true, - "lockfileVersion": 1, - "dependencies": { - "fullcalendar-scheduler": { + "packages": { + "": { + "dependencies": { + "fullcalendar-scheduler": "^5.6.0" + } + }, + "node_modules/fullcalendar-scheduler": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" From 6bba07db38bbf5013f02cd2f281b72fd8f02e184 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 16 Feb 2023 15:07:49 -0800 Subject: [PATCH 002/100] init --- readme | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 readme diff --git a/readme b/readme new file mode 100644 index 000000000..e69de29bb From c704cb0953cf33a1045786e1ea55bda5981ae0c6 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 16 Feb 2023 15:34:37 -0800 Subject: [PATCH 003/100] fullcalendar --- package-lock.json | 77 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 78 insertions(+) diff --git a/package-lock.json b/package-lock.json index 521b28d44..036c04cfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,90 @@ "packages": { "": { "dependencies": { + "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } }, + "node_modules/@fullcalendar/core": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.4.tgz", + "integrity": "sha512-ZDD0Owv0LezAk14nsRNaOc9nbowItGmT0mnjOhEw+L6B8P5eads8yYaNA9itn70MWoOjiAG8xqD7Yk1iJGxqgQ==", + "dependencies": { + "preact": "^10.0.5" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.4.tgz", + "integrity": "sha512-X0QWEiA/hT8GYiQzmXt9DlZTWaQbNtBHBXGtaMNcVXbGHDCzLoWTHrde/jABGfr/i2+d9sLUO4oTtwz2HVpNtQ==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.4.tgz", + "integrity": "sha512-69G2B61bPiHy7VyTDiwU9l8yRHPUK9XxNxjIdm3N0nvWR6BaUIBDQe8dIWht+IZUf9qirFhnfcLWkRI0fOTWtw==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/list": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.4.tgz", + "integrity": "sha512-RY2fE9J7ckzhKtvHOWhlI2FnVEBC4Z9ZlgRBE2nQekX2+THt+3KnZtOQxuYGnGi30NBKx794YsXeIc6/Lnl0Ww==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/multimonth": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.4.tgz", + "integrity": "sha512-bljwyIER4APKTpKF/M8u0BkMDbYDKVkSszCSg1VIX9uVTPpAbzlStqiMAwT+zoOPvoQ8Hsn+yuCFFKKbfUwAFw==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.4" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.4.tgz", + "integrity": "sha512-B2/levLKW0CyDQru75JeuASpCZml5W8sINCwVTstxxhjKmNBG3F5qvSX12DDrTdga/ySYObNNP1pKDwavKk/JQ==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.4" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/fullcalendar": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.4.tgz", + "integrity": "sha512-XAgWTauifC8HKkbN8574wEWwWCgzcgMfjkxyvK1V5D0Q/HKldZlYuvOSrI0JKkzIXtufurx9IN+QljVQkAvg+A==", + "dependencies": { + "@fullcalendar/core": "~6.1.4", + "@fullcalendar/daygrid": "~6.1.4", + "@fullcalendar/interaction": "~6.1.4", + "@fullcalendar/list": "~6.1.4", + "@fullcalendar/multimonth": "~6.1.4", + "@fullcalendar/timegrid": "~6.1.4" + } + }, "node_modules/fullcalendar-scheduler": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" + }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } } } } diff --git a/package.json b/package.json index d8f5ba1af..8ef19f158 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } } From 44559b719fafed9dbc2ab2408f6197c197a33026 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 16 Feb 2023 15:46:38 -0800 Subject: [PATCH 004/100] jquery --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 036c04cfd..9a9145719 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@types/jquery": "^3.2.7", "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } @@ -63,6 +64,11 @@ "@fullcalendar/core": "~6.1.4" } }, + "node_modules/@types/jquery": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.7.tgz", + "integrity": "sha512-cBLuqUFfrUmlL3Fdx87pLSrnM33zaV57Tv9R4ZUwcxuG+z3/ftQVnpviK86w5j94U7XPuMQgtviySBLvRkWJ6Q==" + }, "node_modules/fullcalendar": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.4.tgz", diff --git a/package.json b/package.json index 8ef19f158..e06a6cc61 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "@types/jquery": "^3.2.7", "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } From 51b350ea00cb35d92459c5ca9ef10231c4cdb77c Mon Sep 17 00:00:00 2001 From: Monis Mohiudin <52933919+killamonis@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:13:23 -0800 Subject: [PATCH 005/100] Updated ReadMe --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ab8fbf3c0..073bbbc1b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ [![Maintainability](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/maintainability)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/test_coverage)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/test_coverage) +## Spring 2023 CS169L: +[![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) + # [Snap!Con](https://snapcon.org) forked from: ## Open Source Event Manager - [osem.io](https://osem.io) From ff84b13f09bb73d4f72442ca73884d2178d07d9b Mon Sep 17 00:00:00 2001 From: Monis Mohiudin <52933919+killamonis@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:18:20 -0800 Subject: [PATCH 006/100] created ruby workflow --- .github/workflows/main.yml | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..124cd4289 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,44 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Ruby 2.6.6 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6.6 + + - name: Set up Code Climate test-reporter + run: | + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + chmod +x ./cc-test-reporter + ./cc-test-reporter before-build + + - name: Install bundler + run: | + gem install bundler + - name: Install Dependencies + run: bundle install + - name: Set up test database + run: bundle exec rake db:setup + - name: run RSpec tests and capture coverage + run: | + bundle exec rspec + ./cc-test-reporter format-coverage --output coverage/codeclimate.$SUITE.json --input-type simplecov + - name: run Cucumber tests and capture coverage + run: | + bundle exec cucumber + ./cc-test-reporter format-coverage --output coverage/codeclimate.$SUITE.json --input-type simplecov + + - name: Publish code coverage + run: | + export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" + ./cc-test-reporter sum-coverage coverage/codeclimate.*.json + ./cc-test-reporter upload-coverage --id ${{ secrets.CODE_CLIMATE_ID }} + ./cc-test-reporter after-build --id ${{ secrets.CODE_CLIMATE_ID }} From cdda7a7bea5e2d05c5ab16b86f0dc3305ffb03fd Mon Sep 17 00:00:00 2001 From: Monis Mohiudin <52933919+killamonis@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:23:35 -0800 Subject: [PATCH 007/100] added status badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 073bbbc1b..cd7dfd435 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ ## Spring 2023 CS169L: [![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) +[![CodeQL](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml) +[![build](https://github.com/cs169/snapcon/actions/workflows/main.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/main.yml) +[![Specs](https://github.com/cs169/snapcon/actions/workflows/spec.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/spec.yml) # [Snap!Con](https://snapcon.org) forked from: From 5a400d9691210958580516431897fba9d38b589e Mon Sep 17 00:00:00 2001 From: Monis Mohiudin <52933919+killamonis@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:25:05 -0800 Subject: [PATCH 008/100] updated ruby workflow --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 124cd4289..dba91e45f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,10 +27,6 @@ jobs: run: bundle install - name: Set up test database run: bundle exec rake db:setup - - name: run RSpec tests and capture coverage - run: | - bundle exec rspec - ./cc-test-reporter format-coverage --output coverage/codeclimate.$SUITE.json --input-type simplecov - name: run Cucumber tests and capture coverage run: | bundle exec cucumber From df1f2fe2dec23b3e70b0d5941a9c57301c597752 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Fri, 17 Feb 2023 21:09:23 -0800 Subject: [PATCH 009/100] bluejay file --- info.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 info.yml diff --git a/info.yml b/info.yml new file mode 100644 index 000000000..df1335b63 --- /dev/null +++ b/info.yml @@ -0,0 +1,32 @@ +project: + name: 'snapcon' # Your project name, e.g. Cue-to-cue + owner: 'CS169L-23' # Do not change + teamId: '02' # Your team number, e.g. 02 + identities: + pivotal: 'https://www.pivotaltracker.com/n/projects/2487653' # Your Pivotal project URL + heroku: 'https://sp23-02-snapcon.herokuapp.com' # Your Heroku app URL + members: + member1: # Add all project members + name: 'Adam' # Member 1 name + surname: 'Salguero' # Member 1 last name + githubUsername: 'adamsalguero' # Member 1 GitHub username + pivotalUsername: 'adamsalguero' # Member 1 Pivotal username + herokuEmail: 'adam.a.salguero@berkeley.edu' # Member 1 Heroku username + member2: + name: 'Monis' + surname: 'Mohiuddin' + githubUsername: 'killamonis' + pivotalUsername: 'monis24' + herokuEmail: 'monmohiuddin@berkeley.edu' + member3: + name: 'Justin' + surname: 'Pau' + githubUsername: 'Juapu' + pivotalUsername: 'Jupau' + herokuEmail: 'jupau@berkeley.edu ' + member4: + name: 'Shiv' + surname: 'Sethi' + githubUsername: 'Shiv-Sethi' + pivotalUsername: 'shiv_sethi' + herokuEmail: 'shiv_sethi@berkeley.edu' From 3a1a785d4b7b79c5acbf5da2491f670ddb6e58dd Mon Sep 17 00:00:00 2001 From: Adam Salguero Date: Wed, 22 Feb 2023 14:57:40 -0800 Subject: [PATCH 010/100] README EDIT - Capital Forked From --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab8fbf3c0..5d8b8a63d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [![Test Coverage](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/test_coverage)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/test_coverage) # [Snap!Con](https://snapcon.org) -forked from: +Forked From: ## Open Source Event Manager - [osem.io](https://osem.io) ![OSEM Logo](doc/osem-logo.png) From 639f21589c1b9f18a8ac1425fe1de129eb6d7938 Mon Sep 17 00:00:00 2001 From: Adam Salguero Date: Wed, 22 Feb 2023 15:27:17 -0800 Subject: [PATCH 011/100] Updated Gemfile to require 2.6.6 or greater instead of 2.7 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 429b38b3f..b57fa9e6f 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -ruby ENV.fetch('OSEM_RUBY_VERSION', '~>2.7') +ruby ENV.fetch('OSEM_RUBY_VERSION', '~>2.6.6') # rails-assets requires >= 1.8.4 if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.8.4') From aba7bb860b88548ab4f504ed4544fcba4b243c21 Mon Sep 17 00:00:00 2001 From: Adam Salguero Date: Wed, 22 Feb 2023 15:35:40 -0800 Subject: [PATCH 012/100] Updated Gemfile.lockfor JQuery build issue --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 69be6eb30..31c39be12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -505,7 +505,7 @@ GEM rails-assets-jquery (>= 1.9.1, < 4) rails-assets-date.format (1.2.3) rails-assets-holderjs (2.9.6) - rails-assets-jquery (2.2.4) + rails-assets-jquery (>= 1.7.0) rails-assets-jquery-smooth-scroll (2.2.0) rails-assets-jquery (>= 1.7.0) rails-assets-markdown (0.5.0) From 0a9243c8e75b8e921e0d155071df5689b73512ea Mon Sep 17 00:00:00 2001 From: Adam Salguero Date: Wed, 22 Feb 2023 15:47:37 -0800 Subject: [PATCH 013/100] changed requirment for ruby version to be bellow 3 and regenerated GemFile --- Gemfile | 2 +- Gemfile.lock | 798 +++++++++++++++++++++++++-------------------------- 2 files changed, 395 insertions(+), 405 deletions(-) diff --git a/Gemfile b/Gemfile index b57fa9e6f..9b72d65d6 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -ruby ENV.fetch('OSEM_RUBY_VERSION', '~>2.6.6') +ruby ENV.fetch('OSEM_RUBY_VERSION', '<3') # rails-assets requires >= 1.8.4 if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.8.4') diff --git a/Gemfile.lock b/Gemfile.lock index 31c39be12..6306c479e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,49 +2,49 @@ GEM remote: https://rubygems.org/ remote: https://rails-assets.org/ specs: - Ascii85 (1.0.3) - actioncable (5.2.8) - actionpack (= 5.2.8) + Ascii85 (1.1.0) + actioncable (5.2.8.1) + actionpack (= 5.2.8.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.8) - actionpack (= 5.2.8) - actionview (= 5.2.8) - activejob (= 5.2.8) + actionmailer (5.2.8.1) + actionpack (= 5.2.8.1) + actionview (= 5.2.8.1) + activejob (= 5.2.8.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.8) - actionview (= 5.2.8) - activesupport (= 5.2.8) + actionpack (5.2.8.1) + actionview (= 5.2.8.1) + activesupport (= 5.2.8.1) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.8) - activesupport (= 5.2.8) + actionview (5.2.8.1) + activesupport (= 5.2.8.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_model_serializers (0.10.12) - actionpack (>= 4.1, < 6.2) - activemodel (>= 4.1, < 6.2) + active_model_serializers (0.10.13) + actionpack (>= 4.1, < 7.1) + activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (5.2.8) - activesupport (= 5.2.8) + activejob (5.2.8.1) + activesupport (= 5.2.8.1) globalid (>= 0.3.6) - activemodel (5.2.8) - activesupport (= 5.2.8) - activerecord (5.2.8) - activemodel (= 5.2.8) - activesupport (= 5.2.8) + activemodel (5.2.8.1) + activesupport (= 5.2.8.1) + activerecord (5.2.8.1) + activemodel (= 5.2.8.1) + activesupport (= 5.2.8.1) arel (>= 9.0) - activestorage (5.2.8) - actionpack (= 5.2.8) - activerecord (= 5.2.8) + activestorage (5.2.8.1) + actionpack (= 5.2.8.1) + activerecord (= 5.2.8.1) marcel (~> 1.0.0) - activesupport (5.2.8) + activesupport (5.2.8.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -53,27 +53,28 @@ GEM activerecord (>= 4.0) activesupport (>= 4.0) awesome_nested_set (>= 3.0) - acts_as_list (0.9.19) - activerecord (>= 3.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + acts_as_list (1.1.0) + activerecord (>= 4.2) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) afm (0.2.2) - ajax-datatables-rails (0.4.3) - railties (>= 4.0) - annotate (3.1.1) - activerecord (>= 3.2, < 7.0) + ajax-datatables-rails (1.4.0) + rails (>= 5.2) + zeitwerk + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) archive-zip (0.12.0) io-like (~> 0.3.0) arel (9.0.0) - ast (2.4.0) - autoprefixer-rails (9.6.4) - execjs - awesome_nested_set (3.4.0) - activerecord (>= 4.0.0, < 7.0) + ast (2.4.2) + autoprefixer-rails (10.4.7.0) + execjs (~> 2) + awesome_nested_set (3.5.0) + activerecord (>= 4.0.0, < 7.1) aws_cf_signer (0.1.3) - bcrypt (3.1.16) - bindex (0.6.0) + bcrypt (3.1.18) + bindex (0.8.1) bootstrap-sass (3.4.1) autoprefixer-rails (>= 5.2.1) sassc (>= 2.0.0) @@ -81,25 +82,26 @@ GEM bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) builder (3.2.4) - bullet (6.1.4) + bullet (7.0.7) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) - cancancan (3.3.0) - capybara (3.35.3) + cancancan (3.4.0) + capybara (3.38.0) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - carrierwave (2.1.1) + carrierwave (2.2.3) activemodel (>= 5.0.0) activesupport (>= 5.0.0) addressable (~> 2.6) image_processing (~> 1.1) - mimemagic (>= 0.3.0) + marcel (~> 1.0.0) mini_mime (>= 0.1.3) ssrf_filter (~> 1.0) carrierwave-bombshelter (0.2.2) @@ -108,66 +110,60 @@ GEM fastimage case_transform (0.2) activesupport - caxlsx (3.1.0) + caxlsx (3.3.0) htmlentities (~> 4.3, >= 4.3.4) marcel (~> 1.0) nokogiri (~> 1.10, >= 1.10.4) rubyzip (>= 1.3.0, < 3) - caxlsx_rails (0.6.2) + caxlsx_rails (0.6.3) actionpack (>= 3.1) caxlsx (>= 3.0) - chartkick (3.4.2) - childprocess (3.0.0) + chartkick (5.0.1) chronic (0.10.2) - chunky_png (1.3.11) - climate_control (0.2.0) - cloudinary (1.11.1) + chunky_png (1.4.0) + climate_control (1.2.0) + cloudinary (1.25.0) aws_cf_signer - rest-client + rest-client (>= 2.0.0) cocoon (1.2.15) - coderay (1.1.1) - concurrent-ruby (1.1.10) + coderay (1.1.3) + concurrent-ruby (1.2.0) + connection_pool (2.3.0) countable-rails (0.0.1) railties (>= 3.1) - countries (3.0.0) - i18n_data (~> 0.8.0) - sixarm_ruby_unaccent (~> 1.1) - unicode_utils (~> 1.4) - country_select (4.0.0) - countries (~> 3.0) - sort_alphabetical (~> 1.0) + countries (5.3.1) + unaccent (~> 0.3) + country_select (8.0.1) + countries (~> 5.0) crack (0.4.5) rexml crass (1.0.6) - cucumber (4.1.0) - builder (~> 3.2, >= 3.2.3) - cucumber-core (~> 7.1, >= 7.1.0) - cucumber-create-meta (~> 1.0.0, >= 1.0.0) - cucumber-cucumber-expressions (~> 10.1, >= 10.1.0) - cucumber-gherkin (~> 14.0, >= 14.0.1) - cucumber-html-formatter (~> 7.0, >= 7.0.0) - cucumber-messages (~> 12.2, >= 12.2.0) - cucumber-wire (~> 3.1, >= 3.1.0) - diff-lcs (~> 1.3, >= 1.3, < 1.4) - multi_test (~> 0.1, >= 0.1.2) - sys-uname (~> 1.0, >= 1.0.2) - cucumber-core (7.1.0) - cucumber-gherkin (~> 14.0, >= 14.0.1) - cucumber-messages (~> 12.2, >= 12.2.0) - cucumber-tag-expressions (~> 2.0, >= 2.0.4) - cucumber-create-meta (1.0.0) - cucumber-messages (~> 12.2, >= 12.2.0) - sys-uname (~> 1.2, >= 1.2.1) - cucumber-cucumber-expressions (10.3.0) - cucumber-gherkin (14.2.0) - cucumber-messages (~> 12.4, >= 12.4.0) - cucumber-html-formatter (7.2.0) - cucumber-messages (~> 12.4, >= 12.4.0) - cucumber-messages (12.4.0) - protobuf-cucumber (~> 3.10, >= 3.10.8) - cucumber-rails (2.5.1) + cucumber (8.0.0) + builder (~> 3.2, >= 3.2.4) + cucumber-ci-environment (~> 9.0, >= 9.0.4) + cucumber-core (~> 11.0, >= 11.0.0) + cucumber-cucumber-expressions (~> 15.1, >= 15.1.1) + cucumber-gherkin (~> 23.0, >= 23.0.1) + cucumber-html-formatter (~> 19.1, >= 19.1.0) + cucumber-messages (~> 18.0, >= 18.0.0) + diff-lcs (~> 1.5, >= 1.5.0) + mime-types (~> 3.4, >= 3.4.1) + multi_test (~> 1.1, >= 1.1.0) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-ci-environment (9.1.0) + cucumber-core (11.0.0) + cucumber-gherkin (~> 23.0, >= 23.0.1) + cucumber-messages (~> 18.0, >= 18.0.0) + cucumber-tag-expressions (~> 4.1, >= 4.1.0) + cucumber-cucumber-expressions (15.2.0) + cucumber-gherkin (23.0.1) + cucumber-messages (~> 18.0, >= 18.0.0) + cucumber-html-formatter (19.2.0) + cucumber-messages (~> 18.0, >= 18.0.0) + cucumber-messages (18.0.0) + cucumber-rails (2.6.1) capybara (>= 2.18, < 4) - cucumber (>= 3.2, < 8) + cucumber (>= 3.2, < 9) mime-types (~> 3.3) nokogiri (~> 1.10) railties (>= 5.0, < 8) @@ -175,26 +171,23 @@ GEM webrick (~> 1.7) cucumber-rails-training-wheels (1.0.0) cucumber-rails (>= 1.1.1) - cucumber-tag-expressions (2.0.4) - cucumber-wire (3.1.0) - cucumber-core (~> 7.1, >= 7.1.0) - cucumber-cucumber-expressions (~> 10.1, >= 10.1.0) - cucumber-messages (~> 12.2, >= 12.2.0) - daemons (1.3.1) - dalli (2.7.11) + cucumber-tag-expressions (4.1.0) + daemons (1.4.1) + dalli (3.2.4) dante (0.2.0) database_cleaner (2.0.1) database_cleaner-active_record (~> 2.0.0) - database_cleaner-active_record (2.0.0) + database_cleaner-active_record (2.0.1) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - delayed_job (4.1.9) - activesupport (>= 3.0, < 6.2) - delayed_job_active_record (4.1.5) - activerecord (>= 3.0, < 6.2) + date (3.3.3) + delayed_job (4.1.11) + activesupport (>= 3.0, < 8.0) + delayed_job_active_record (4.1.7) + activerecord (>= 3.0, < 8.0) delayed_job (>= 3.0, < 5) - devise (4.7.3) + devise (4.9.0) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -202,63 +195,57 @@ GEM warden (~> 1.2.3) devise_ichain_authenticatable (0.3.2) devise (>= 2.2) - diff-lcs (1.3) - docile (1.3.5) - domain_name (0.5.20180417) + diff-lcs (1.5.0) + docile (1.4.0) + domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) - dotenv-rails (2.7.6) - dotenv (= 2.7.6) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) railties (>= 3.2) - erubi (1.10.0) - erubis (2.7.0) - execjs (2.7.0) - factory_bot (4.11.1) - activesupport (>= 3.0.0) - factory_bot_rails (4.11.1) - factory_bot (~> 4.11.1) - railties (>= 3.0.0) - faker (2.17.0) - i18n (>= 1.6, < 2) - faraday (1.4.1) - faraday-excon (~> 1.1) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) - multipart-post (>= 1.2, < 3) + erubi (1.12.0) + execjs (2.8.1) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + faker (3.1.1) + i18n (>= 1.8.11, < 2) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) - faraday-excon (1.1.0) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.1.0) - fastimage (2.1.4) + faraday-net_http (3.0.2) + fastimage (2.2.6) feature (1.4.0) ffi (1.15.5) - flay (2.12.1) - erubis (~> 2.7.0) + flay (2.13.0) + erubi (~> 1.10) path_expander (~> 1.0) ruby_parser (~> 3.0) sexp_processor (~> 4.0) - font-awesome-rails (4.7.0.7) - railties (>= 3.2, < 7) - formatador (0.2.5) + font-awesome-rails (4.7.0.8) + railties (>= 3.2, < 8.0) + formatador (1.1.0) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic-bootstrap (3.1.1) formtastic (>= 3.0) - geckodriver-helper (0.23.0) + geckodriver-helper (0.24.0) archive-zip (~> 0.7) - gitlab (4.17.0) - httparty (~> 0.18) - terminal-table (~> 1.5, >= 1.5.1) - globalid (1.0.0) + gitlab (4.19.0) + httparty (~> 0.20) + terminal-table (>= 1.5.1) + globalid (1.1.0) activesupport (>= 5.0) gravtastic (3.2.6) - guard (2.14.1) + guard (2.18.0) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) - lumberjack (~> 1.0) + lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) - pry (>= 0.9.12) + pry (>= 0.13.0) shellany (~> 0.0) thor (>= 0.18.1) guard-compat (1.2.1) @@ -266,256 +253,251 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - haml (5.0.4) - temple (>= 0.8.0) + haml (6.1.1) + temple (>= 0.8.2) + thor tilt haml-lint (0.999.999) haml_lint - haml-rails (1.0.0) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - haml (>= 4.0.6, < 6.0) - html2haml (>= 1.0.1) - railties (>= 4.0.1) - haml_lint (0.28.0) - haml (>= 4.0, < 5.1) + haml-rails (2.1.0) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6) + railties (>= 5.1) + haml_lint (0.45.0) + haml (>= 4.0, < 6.2) + parallel (~> 1.10) rainbow - rake (>= 10, < 13) rubocop (>= 0.50.0) sysexits (~> 1.1) hashdiff (1.0.1) hashery (2.1.2) - hashie (4.1.0) - html2haml (2.2.0) - erubis (~> 2.7.0) - haml (>= 4.0, < 6) - nokogiri (>= 1.6.0) - ruby_parser (~> 3.5) + hashie (5.0.0) htmlentities (4.3.4) - http-cookie (1.0.3) + http-accept (1.7.0) + http-cookie (1.0.5) domain_name (~> 0.5) - httparty (0.18.1) - mime-types (~> 3.0) + httparty (0.21.0) + mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) - i18n_data (0.8.0) - icalendar (2.7.1) + icalendar (2.8.0) ice_cube (~> 0.16) - ice_cube (0.16.3) + ice_cube (0.16.4) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - io-like (0.3.0) - iso-639 (0.2.8) - jaro_winkler (1.5.3) + io-like (0.3.1) + iso-639 (0.3.6) jquery-datatables (1.10.20) - jquery-rails (4.4.0) + jquery-rails (4.5.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-ui-rails (6.0.1) railties (>= 3.2.16) - json (2.5.1) - json-schema (2.8.1) - addressable (>= 2.4) + json (2.6.3) + json-schema (3.0.0) + addressable (>= 2.8) jsonapi-renderer (0.2.2) - jwt (2.2.2) - launchy (2.4.3) - addressable (~> 2.3) - leaflet-rails (1.7.0) + jwt (2.7.0) + launchy (2.5.2) + addressable (~> 2.8) + leaflet-rails (1.9.3) rails (>= 4.2.0) - letter_opener (1.7.0) - launchy (~> 2.2) - letter_opener_web (1.4.0) - actionmailer (>= 3.2) - letter_opener (~> 1.0) - railties (>= 3.2) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.18.0) + letter_opener (1.8.1) + launchy (>= 2.2, < 3) + letter_opener_web (2.0.0) + actionmailer (>= 5.2) + letter_opener (~> 1.7) + railties (>= 5.2) + rexml + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - lumberjack (1.0.12) - mail (2.7.1) + lumberjack (1.2.8) + mail (2.8.1) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp marcel (1.0.2) - method_source (0.8.2) - middleware (0.1.0) - mime-types (3.3.1) + matrix (0.4.2) + method_source (1.0.0) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0225) - mimemagic (0.3.10) - nokogiri (~> 1) - rake - mina (1.2.3) + mime-types-data (3.2023.0218.1) + mina (1.2.4) open4 (~> 1.3.4) rake - mini_magick (4.9.5) + mini_magick (4.12.0) mini_mime (1.1.2) - mini_portile2 (2.8.0) - minitest (5.16.2) - momentjs-rails (2.20.1) + mini_portile2 (2.8.1) + minitest (5.17.0) + momentjs-rails (2.29.4.1) railties (>= 3.1) - monetize (1.9.2) + monetize (1.12.0) money (~> 6.12) - money (6.13.4) + money (6.16.0) i18n (>= 0.6.4, <= 2) - money-rails (1.13.3) + money-rails (1.15.0) activesupport (>= 3.0) - monetize (~> 1.9.0) - money (~> 6.13.2) + monetize (~> 1.9) + money (~> 6.13) railties (>= 3.0) multi_json (1.15.0) - multi_test (0.1.2) + multi_test (1.1.0) multi_xml (0.6.0) - multipart-post (2.1.1) nenv (0.3.0) + net-imap (0.3.4) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.1) + timeout + net-smtp (0.3.3) + net-protocol netrc (0.11.0) nio4r (2.5.8) - nokogiri (1.13.6) + nokogiri (1.14.2) mini_portile2 (~> 2.8.0) racc (~> 1.4) - notiffany (0.1.1) + notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) - oauth2 (1.4.4) - faraday (>= 0.8, < 2.0) + oauth2 (1.4.11) + faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (>= 1.2, < 3) - octokit (4.21.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) - omniauth (1.9.1) + rack (>= 1.2, < 4) + octokit (6.0.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + omniauth (1.9.2) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) omniauth-discourse (1.0.0) omniauth (~> 1.0) - omniauth-facebook (8.0.0) + omniauth-facebook (9.0.0) omniauth-oauth2 (~> 1.2) omniauth-github (1.4.0) omniauth (~> 1.5) omniauth-oauth2 (>= 1.4.0, < 2.0) - omniauth-google-oauth2 (0.8.1) + omniauth-google-oauth2 (0.8.2) jwt (>= 2.0) oauth2 (~> 1.1) - omniauth (>= 1.1.1) + omniauth (~> 1.1) omniauth-oauth2 (>= 1.6) - omniauth-oauth2 (1.7.1) - oauth2 (~> 1.4) + omniauth-oauth2 (1.7.3) + oauth2 (>= 1.4, < 3) omniauth (>= 1.9, < 3) - omniauth-openid (1.0.1) - omniauth (~> 1.0) - rack-openid (~> 1.3.1) + omniauth-openid (2.0.1) + omniauth (>= 1.0, < 3.0) + rack-openid (~> 1.4.0) open4 (1.3.4) orm_adapter (0.5.0) - pagy (3.12.0) - paper_trail (10.3.1) - activerecord (>= 4.2) + pagy (3.14.0) + paper_trail (13.0.0) + activerecord (>= 5.2) request_store (~> 1.1) - parallel (1.18.0) - parser (2.6.5.0) - ast (~> 2.4.0) - path_expander (1.1.0) + parallel (1.22.1) + parser (3.2.1.0) + ast (~> 2.4.1) + path_expander (1.1.1) pdf-core (0.9.0) pdf-inspector (1.3.0) pdf-reader (>= 1.0, < 3.0.a) - pdf-reader (2.2.0) - Ascii85 (~> 1.0.0) + pdf-reader (2.11.0) + Ascii85 (~> 1.0) afm (~> 0.2.1) hashery (~> 2.0) ruby-rc4 ttfunk - pg (1.2.3) + pg (1.4.5) prawn (2.4.0) pdf-core (~> 0.9.0) ttfunk (~> 1.7) - prawn-qrcode (0.3.1) + prawn-qrcode (0.5.2) prawn (>= 1) - rqrcode (>= 0.4.1) - prawn-rails (1.4.0) + rqrcode (>= 1.0.0) + prawn-rails (1.4.2) + actionview (>= 3.1.0) prawn prawn-table - railties (>= 3.1.0) prawn-table (0.2.2) prawn (>= 1.3.0, < 3.0.0) - pronto (0.11.0) - gitlab (~> 4.4, >= 4.4.0) - httparty (>= 0.13.7) - octokit (~> 4.7, >= 4.7.0) + pronto (0.11.1) + gitlab (>= 4.4.0, < 5.0) + httparty (>= 0.13.7, < 1.0) + octokit (>= 4.7.0, < 7.0) rainbow (>= 2.2, < 4.0) - rexml (~> 3.2) - rugged (>= 0.23.0, < 1.1.0) + rexml (>= 3.2.5, < 4.0) + rugged (>= 0.23.0, < 2.0) thor (>= 0.20.3, < 2.0) - pronto-flay (0.11.0) + pronto-flay (0.11.1) flay (~> 2.8) pronto (~> 0.11.0) - pronto-haml (0.11.0) + pronto-haml (0.11.1) haml_lint (~> 0.23) pronto (~> 0.11.0) - rubocop (< 1.0) - pronto-rubocop (0.11.1) + pronto-rubocop (0.11.5) pronto (~> 0.11.0) rubocop (>= 0.63.1, < 2.0) - protobuf-cucumber (3.10.8) - activesupport (>= 3.2) - middleware - thor - thread_safe - pry (0.10.4) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - pry-byebug (3.8.0) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.10.1) byebug (~> 11.0) - pry (~> 0.10) - public_suffix (4.0.6) - puma (5.6.4) + pry (>= 0.13, < 0.15) + public_suffix (5.0.1) + puma (5.6.5) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.4) - rack-openid (1.3.1) + racc (1.6.2) + rack (2.2.6.2) + rack-openid (1.4.2) rack (>= 1.1.0) ruby-openid (>= 2.1.8) rack-test (2.0.2) rack (>= 1.3) - rails (5.2.8) - actioncable (= 5.2.8) - actionmailer (= 5.2.8) - actionpack (= 5.2.8) - actionview (= 5.2.8) - activejob (= 5.2.8) - activemodel (= 5.2.8) - activerecord (= 5.2.8) - activestorage (= 5.2.8) - activesupport (= 5.2.8) + rails (5.2.8.1) + actioncable (= 5.2.8.1) + actionmailer (= 5.2.8.1) + actionpack (= 5.2.8.1) + actionview (= 5.2.8.1) + activejob (= 5.2.8.1) + activemodel (= 5.2.8.1) + activerecord (= 5.2.8.1) + activestorage (= 5.2.8.1) + activesupport (= 5.2.8.1) bundler (>= 1.3.0) - railties (= 5.2.8) + railties (= 5.2.8.1) sprockets-rails (>= 2.0.0) - rails-assets-bootstrap (3.3.6) - rails-assets-jquery (>= 1.9.1, < 3) + rails-assets-bootstrap (3.4.1) + rails-assets-jquery (>= 1.9.1, < 4) rails-assets-bootstrap-markdown (2.10.0) rails-assets-bootstrap (~> 3) - rails-assets-bootstrap-select (1.13.3) + rails-assets-bootstrap-select (1.13.10) rails-assets-bootstrap (>= 3.0.0) rails-assets-jquery (>= 1.9.1, < 4) rails-assets-date.format (1.2.3) rails-assets-holderjs (2.9.6) - rails-assets-jquery (>= 1.7.0) + rails-assets-jquery (3.6.3) rails-assets-jquery-smooth-scroll (2.2.0) rails-assets-jquery (>= 1.7.0) rails-assets-markdown (0.5.0) - rails-assets-momentjs (2.22.2) + rails-assets-momentjs (2.29.4) rails-assets-spectrum (1.8.0) rails-assets-jquery (>= 1.7.2) - rails-assets-tinycolor (1.4.1) + rails-assets-tinycolor (1.6.0) rails-assets-to-markdown (3.1.1) rails-assets-trianglify (1.2.0) - rails-assets-waypoints (4.0.0) + rails-assets-waypoints (4.0.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -523,181 +505,187 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.4.3) - loofah (~> 2.3) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) rails-i18n (5.1.3) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) - railties (5.2.8) - actionpack (= 5.2.8) - activesupport (= 5.2.8) + railties (5.2.8.1) + actionpack (= 5.2.8.1) + activesupport (= 5.2.8.1) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (12.3.3) - rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rainbow (3.1.1) + rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) ffi (~> 1.0) - recaptcha (4.14.0) + recaptcha (5.12.3) json - redcarpet (3.5.1) - redis (4.4.0) - regexp_parser (2.1.1) - request_store (1.4.1) + redcarpet (3.6.0) + redis (5.0.6) + redis-client (>= 0.9.0) + redis-client (0.12.2) + connection_pool + regexp_parser (2.7.0) + request_store (1.5.1) rack (>= 1.4) responders (2.4.1) actionpack (>= 4.2.0, < 6.0) railties (>= 4.2.0, < 6.0) - rest-client (2.0.2) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.5) - rolify (5.2.0) - rqrcode (0.10.1) + rolify (6.0.1) + rqrcode (2.1.2) chunky_png (~> 1.0) - rspec (3.6.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) + rqrcode_core (~> 1.0) + rqrcode_core (1.2.0) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) rspec-activemodel-mocks (1.1.0) activemodel (>= 3.0) activesupport (>= 3.0) rspec-mocks (>= 2.99, < 4.0) - rspec-core (3.6.0) - rspec-support (~> 3.6.0) - rspec-expectations (3.6.0) + rspec-core (3.12.1) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-mocks (3.6.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-rails (3.6.1) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) - rspec-support (~> 3.6.0) - rspec-support (3.6.0) - rubocop (0.75.1) - jaro_winkler (~> 1.5.1) + rspec-support (~> 3.12.0) + rspec-rails (5.1.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.12.0) + rubocop (1.46.0) + json (~> 2.3) parallel (~> 1.10) - parser (>= 2.6) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.26.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) - rubocop-faker (1.0.0) - rubocop (>= 0.74) - rubocop-rspec (1.36.0) - rubocop (>= 0.68.1) - ruby-oembed (0.12.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.26.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.17.1) + rubocop (~> 1.41) + rubocop-faker (1.1.0) + faker (>= 2.12.0) + rubocop (>= 0.82.0) + rubocop-rspec (2.18.1) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + ruby-oembed (0.16.1) ruby-openid (2.9.2) - ruby-progressbar (1.10.1) + ruby-progressbar (1.11.0) ruby-rc4 (0.1.5) ruby-vips (2.1.4) ffi (~> 1.12) - ruby2_keywords (0.0.4) - ruby_dep (1.5.0) - ruby_parser (3.11.0) - sexp_processor (~> 4.9) - rubyzip (2.3.0) - rugged (1.0.1) - sass (3.7.2) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sassc (2.0.1) + ruby2_keywords (0.0.5) + ruby_parser (3.19.2) + sexp_processor (~> 4.16) + rubyzip (2.3.2) + rugged (1.5.1) + sass-rails (6.0.0) + sassc-rails (~> 2.1, >= 2.1.1) + sassc (2.4.0) ffi (~> 1.9) - rake - sawyer (0.8.2) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) + faraday (>= 0.17.3, < 3) selectize-rails (0.12.6) - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) - sentry-delayed_job (4.2.1) - sentry-ruby-core (~> 4.2.0) - sentry-rails (4.2.2) - rails (>= 5.0) - sentry-ruby-core (~> 4.2.0) - sentry-ruby (4.2.2) + selenium-webdriver (4.8.1) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + sentry-delayed_job (5.8.0) + delayed_job (>= 4.0) + sentry-ruby (~> 5.8.0) + sentry-rails (5.8.0) + railties (>= 5.0) + sentry-ruby (~> 5.8.0) + sentry-ruby (5.8.0) concurrent-ruby (~> 1.0, >= 1.0.2) - faraday (>= 1.0) - sentry-ruby-core (= 4.2.2) - sentry-ruby-core (4.2.2) - concurrent-ruby - faraday - sexp_processor (4.11.0) + sexp_processor (4.16.1) shellany (0.0.1) - shoulda-matchers (4.5.1) - activesupport (>= 4.2.0) - simplecov (0.21.2) + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-cobertura (1.4.2) - simplecov (~> 0.8) + simplecov-cobertura (2.1.0) + rexml + simplecov (~> 0.19) simplecov-html (0.12.3) - simplecov_json_formatter (0.1.2) - sixarm_ruby_unaccent (1.2.0) - skylight (5.1.1) + simplecov_json_formatter (0.1.4) + skylight (5.3.4) activesupport (>= 5.2.0) - slop (3.6.0) - sort_alphabetical (1.1.0) - unicode_utils (>= 1.2.2) - spring (1.6.3) + spring (4.1.1) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.7.2) + sprockets (4.2.0) concurrent-ruby (~> 1.0) - rack (> 1, < 3) + rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.4.2) - ssrf_filter (1.0.7) - stripe (5.14.0) - stripe-ruby-mock (3.0.1) + sqlite3 (1.6.1) + mini_portile2 (~> 2.8.0) + ssrf_filter (1.1.1) + stripe (8.3.0) + stripe-ruby-mock (2.5.8) dante (>= 0.2.0) multi_json (~> 1.0) - stripe (> 5, < 6) + stripe (>= 2.0.3) sys-uname (1.2.2) ffi (~> 1.1) sysexits (1.2.0) - temple (0.8.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + temple (0.10.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) thor (1.2.1) thread_safe (0.3.6) - tilt (2.0.9) - timecop (0.9.4) - transitions (1.2.1) + tilt (2.1.0) + timecop (0.9.6) + timeout (0.3.2) + transitions (1.3.0) ttfunk (1.7.0) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (1.2.9) + tzinfo (1.2.11) thread_safe (~> 0.1) - uglifier (4.1.20) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unaccent (0.4.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.6.1) - unicode_utils (1.4.0) - uniform_notifier (1.14.2) + unf_ext (0.0.8.2) + unicode-display_width (2.4.2) + uniform_notifier (1.16.0) unobtrusive_flash (3.3.1) railties warden (1.2.9) @@ -707,22 +695,24 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webdrivers (4.6.0) + webdrivers (5.2.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (>= 3.0, < 4.0) - webmock (3.12.2) - addressable (>= 2.3.6) + selenium-webdriver (~> 4.0) + webmock (3.18.1) + addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.7.0) + webrick (1.8.1) + websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - whenever (0.10.0) + whenever (1.0.0) chronic (>= 0.6.3) xpath (3.2.0) nokogiri (~> 1.8) + zeitwerk (2.6.7) PLATFORMS ruby @@ -855,7 +845,7 @@ DEPENDENCIES whenever RUBY VERSION - ruby 2.7.6 + ruby 2.7.7p221 BUNDLED WITH 2.1.4 From 1bd6de0fe2a8bfcdd51af394a35886345abc1922 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 1 Mar 2023 12:39:30 -0800 Subject: [PATCH 014/100] gemlock --- Gemfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 98b891c6d..438fcef14 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -220,7 +220,7 @@ GEM cucumber-html-formatter (17.0.0) cucumber-messages (~> 17.1, >= 17.1.0) cucumber-messages (17.1.1) - cucumber-rails (2.5.1) + cucumber-rails (2.6.1) capybara (>= 2.18, < 4) cucumber (>= 3.2, < 9) mime-types (~> 3.3) @@ -412,7 +412,7 @@ GEM money (~> 6.13) railties (>= 3.0) multi_json (1.15.0) - multi_test (1.1.0) + multi_test (0.1.2) multi_xml (0.6.0) nenv (0.3.0) net-imap (0.3.4) @@ -696,7 +696,7 @@ GEM unicode_utils (>= 1.2.2) sprockets (4.1.1) concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) + rack (> 1, < 3) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) @@ -707,7 +707,7 @@ GEM stripe-ruby-mock (3.1.0.rc3) dante (>= 0.2.0) multi_json (~> 1.0) - stripe (>= 2.0.3) + stripe (> 5, < 6) sys-uname (1.2.2) ffi (~> 1.1) sysexits (1.2.0) @@ -727,7 +727,6 @@ GEM concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) - unaccent (0.4.0) unf (0.1.4) unf_ext unf_ext (0.0.8.2) @@ -763,6 +762,7 @@ GEM zeitwerk (2.6.7) PLATFORMS + x86_64-darwin-21 x86_64-darwin-22 x86_64-linux From 8a63f75cd118539145c0361c6fb802ba0ca2ea36 Mon Sep 17 00:00:00 2001 From: Juapu <70485564+Juapu@users.noreply.github.com> Date: Thu, 2 Mar 2023 14:26:33 -0800 Subject: [PATCH 015/100] Update INSTALL_SNAPCON.md --- INSTALL_SNAPCON.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL_SNAPCON.md b/INSTALL_SNAPCON.md index ec80c33c6..9eef43c6b 100644 --- a/INSTALL_SNAPCON.md +++ b/INSTALL_SNAPCON.md @@ -26,7 +26,7 @@ To run Snap!Con, using [Overmind](https://github.com/DarthSim/overmind) or [Fore When deploying to Heroku, the `heroku/nodejs` buildpack must be run before the `heroku/ruby` buildpack. Since Snap!Con runs on PostgreSQL, the Postgres add-on must also be added to the Heroku deployment. -Finally, it may be necessary to downgrade the Heroku stack to `heroku-18`. Details on how to downgrade can be found [in this Heroku article](https://devcenter.heroku.com/articles/heroku-18-stack#using-heroku-18). +Finally, it may be necessary to downgrade the Heroku stack to `heroku-22`. Details on how to downgrade can be found [in this Heroku article](https://devcenter.heroku.com/articles/heroku-18-stack#using-heroku-18). ### Example Data Example data can easily be generated by running `rake data:demo`. Please note that this command can fail if the database is not fresh. From 094ea48988fe51d8289758646795fac7c1f3df5a Mon Sep 17 00:00:00 2001 From: Juapu <70485564+Juapu@users.noreply.github.com> Date: Thu, 2 Mar 2023 14:29:48 -0800 Subject: [PATCH 016/100] Update INSTALL_SNAPCON.md --- INSTALL_SNAPCON.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALL_SNAPCON.md b/INSTALL_SNAPCON.md index 9eef43c6b..9efc35b85 100644 --- a/INSTALL_SNAPCON.md +++ b/INSTALL_SNAPCON.md @@ -9,11 +9,11 @@ The recommended setup steps are as follows: 1. Run `bundle config set path vendor/bundle` to install gems to `vendor/bundle`. 1. Run `bundle install` to install the necessary gems. 1. Configure your environment variables. - 1. Create `config/local_env.yml`. + 1. Run `cp dotenv.example .env` 1. At a bare minimum, you will likely need to provide credentials with which to access your PostgreSQL database. An example might be as follows: ``` - OSEM_DB_USER: esobeck - OSEM_DB_PASSWORD: password123 + OSEM_DB_USER= esobeck #this can be computer's username + OSEM_DB_PASSWORD= password123 #likely not necessary if using postgress with computer's username ``` 1. Other features will require more environment variables. See [Environment Variables](#environment-variables) and [INSTALL.md#configuration](INSTALL.md#configuration) for all the environment variables that may be set. 1. Run `rake db:setup` (this command and all following commands may need to prefixed with `bundle exec`) to initialize the database. From 557310810940f23345731dcd3107c57c56bae0ae Mon Sep 17 00:00:00 2001 From: Monis Mohiudin <52933919+killamonis@users.noreply.github.com> Date: Thu, 2 Mar 2023 15:27:25 -0800 Subject: [PATCH 017/100] Delete main.yml --- .github/workflows/main.yml | 40 -------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index dba91e45f..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: build - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install Ruby 2.6.6 - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6.6 - - - name: Set up Code Climate test-reporter - run: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build - - - name: Install bundler - run: | - gem install bundler - - name: Install Dependencies - run: bundle install - - name: Set up test database - run: bundle exec rake db:setup - - name: run Cucumber tests and capture coverage - run: | - bundle exec cucumber - ./cc-test-reporter format-coverage --output coverage/codeclimate.$SUITE.json --input-type simplecov - - - name: Publish code coverage - run: | - export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" - ./cc-test-reporter sum-coverage coverage/codeclimate.*.json - ./cc-test-reporter upload-coverage --id ${{ secrets.CODE_CLIMATE_ID }} - ./cc-test-reporter after-build --id ${{ secrets.CODE_CLIMATE_ID }} From 5731f8a305a76c709beb068472e329e3144aa9c7 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 7 Mar 2023 14:34:43 -0800 Subject: [PATCH 018/100] env file --- dotenv.example | 126 ---------------------------------- package-lock.json | 171 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 163 insertions(+), 134 deletions(-) delete mode 100644 dotenv.example diff --git a/dotenv.example b/dotenv.example deleted file mode 100644 index 372800ec9..000000000 --- a/dotenv.example +++ /dev/null @@ -1,126 +0,0 @@ -# Set environment variables for OSEM in this file and copy it to .env or -# .env.$environment (like env.production or env.development) -# -# The following is a list of variables that OSEM uses to -# configure the system/application. Uncomment them to change -# the defaults. - -# Your secret key base. You can generate a secure one with -# bundle exec rake secret -# SECRET_KEY_BASE=12345 - -# The type of database to use (postgresql, mysql2, sqlite3) -# OSEM_DB_ADAPTER=mysql2 - -# The name of the host the database runs on -# OSEM_DB_HOST=database - -# The port the databse runs on -# OSEM_DB_PORT=3306 - -# The user to access the database with -# OSEM_DB_USER=root - -# The password to access the database with -# OSEM_DB_PASSWORD=mysecretpassword - -# The name of the database -# OSEM_DB_NAME=osem_production - -# The memached servers to use, default is a file based cache -# OSEM_MEMCACHED_SERVERS='cache-1.example.com,cache-2.example.com' -# OSEM_MEMCACHED_USERNAME='root' -# OSEM_MEMCACHED_PASSWORD='1234' - -# Set this if you want to deviate from our 'standard' ruby version -# OSEM_RUBY_VERSION=3.1.2 - -# What time is it? -# OSEM_TIME_ZONE="UTC" - -# The name of your OSEM installation -# OSEM_NAME=OSEM - -# The host this OSEM instance runs on. Used for -# generating urls in emails sent -# OSEM_HOSTNAME=osem.example.com - -# The address OSEM uses for sending mails -# OSEM_EMAIL_ADDRESS="no-reply@example.com" - -# The api key for transifex.com. -# See https://github.com/openSUSE/osem/wiki/Translation -# OSEM_TRANSIFEX_APIKEY=1234 - -# sentry.io DSN key -# OSEM_SENTRY_DSN=1234 - -# OMNIAUTH Developer Key/Secret for GOOGLE -# OSEM_GOOGLE_KEY=1234 -# OSEM_GOOGLE_SECRET=5678 - -# OMNIAUTH Developer Key/Secret for Facebook -# OSEM_FACEBOOK_KEY=1234 -# OSEM_FACEBOOK_SECRET=5678 - -# OMNIAUTH Developer Key/Secret for GitHub -# OSEM_GITHUB_KEY=1234 -# OSEM_GITHUB_SECRET=5678 - -# OMNIAUTH Developer KEY/Secret for SUSE/openSUSE -# OSEM_SUSE_KEY=1234 -# OSEM_SUSE_SECRET=5678 - -# STRIPE Publishable/Secret keys -# -> https://github.com/openSUSE/osem/wiki/Stripe -# STRIPE_PUBLISHABLE_KEY=1234 -# STRIPE_SECRET_KEY=5678 - -# MATOMO/PIWIK CONFIGURATION -# OSEM_PIWIK_ID=12345 -# OSEM_PIWIK_URL=localhost -# OSEM_PIWIK_ASYNC=false -# OSEM_PIWIK_DISABLED=true -# OSEM_PIWIK_HOSTNAME=localhost - -# The smtp configuration. See the rails guides for more -# http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration -# OSEM_SMTP_ADDRESS=smtp.gmail.com -# OSEM_SMTP_PORT=587 -# OSEM_SMTP_DOMAIN=example.com -# OSEM_SMTP_USERNAME=user1 -# OSEM_SMTP_PASSWORD=password123 -# OSEM_SMTP_AUTHENTICATION=plain -# OSEM_SMTP_ENABLE_STARTTLS_AUTO=true -# OSEM_SMTP_OPENSSL_VERIFY_MODE=peer - -# Enable the usage of the devise ichain plugin -# OSEM_ICHAIN_ENABLED=false -# OSEM_ICHAIN_BASE_URL=https://example.com - -# ReCAPTCHA keys -# RECAPTCHA_SITE_KEY=1234 -# RECAPTCHA_SECRET_KEY=5678 - -# The Conference#short_title to redirect the root URL to -# OSEM_ROOT_CONFERENCE=osc18 - -# log level in production environment -# OSEM_LOG_LEVEL=info - -# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. -# FORCE_SSL=true - -# Your skylight.io keys -# SKYLIGHT_AUTHENTICATION=1234 -# SKYLIGHT_PUBLIC_DASHBOARD_URL='https://oss.skylight.io/app/applications/xxxxxxxxxxxx' - -# How should browser tests be performed? -# For headless Chrome (default): -# OSEM_TEST_DRIVER=chrome_headless -# For Chrome: -# OSEM_TEST_DRIVER=chrome -# For headless Firefox: -# OSEM_TEST_DRIVER=firefox_headless -# For Firefox: -# OSEM_TEST_DRIVER=firefox diff --git a/package-lock.json b/package-lock.json index d41ef7e66..d2cf3735d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,29 +5,184 @@ "packages": { "": { "dependencies": { + "@types/jquery": "^3.2.7", + "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } }, + "node_modules/@fullcalendar/core": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.4.tgz", + "integrity": "sha512-ZDD0Owv0LezAk14nsRNaOc9nbowItGmT0mnjOhEw+L6B8P5eads8yYaNA9itn70MWoOjiAG8xqD7Yk1iJGxqgQ==", + "dependencies": { + "preact": "^10.0.5" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.4.tgz", + "integrity": "sha512-X0QWEiA/hT8GYiQzmXt9DlZTWaQbNtBHBXGtaMNcVXbGHDCzLoWTHrde/jABGfr/i2+d9sLUO4oTtwz2HVpNtQ==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.4.tgz", + "integrity": "sha512-69G2B61bPiHy7VyTDiwU9l8yRHPUK9XxNxjIdm3N0nvWR6BaUIBDQe8dIWht+IZUf9qirFhnfcLWkRI0fOTWtw==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/list": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.4.tgz", + "integrity": "sha512-RY2fE9J7ckzhKtvHOWhlI2FnVEBC4Z9ZlgRBE2nQekX2+THt+3KnZtOQxuYGnGi30NBKx794YsXeIc6/Lnl0Ww==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/multimonth": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.4.tgz", + "integrity": "sha512-bljwyIER4APKTpKF/M8u0BkMDbYDKVkSszCSg1VIX9uVTPpAbzlStqiMAwT+zoOPvoQ8Hsn+yuCFFKKbfUwAFw==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.4" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.4.tgz", + "integrity": "sha512-B2/levLKW0CyDQru75JeuASpCZml5W8sINCwVTstxxhjKmNBG3F5qvSX12DDrTdga/ySYObNNP1pKDwavKk/JQ==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.4" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.4" + } + }, + "node_modules/@types/jquery": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", + "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" + }, + "node_modules/fullcalendar": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.4.tgz", + "integrity": "sha512-XAgWTauifC8HKkbN8574wEWwWCgzcgMfjkxyvK1V5D0Q/HKldZlYuvOSrI0JKkzIXtufurx9IN+QljVQkAvg+A==", + "dependencies": { + "@fullcalendar/core": "~6.1.4", + "@fullcalendar/daygrid": "~6.1.4", + "@fullcalendar/interaction": "~6.1.4", + "@fullcalendar/list": "~6.1.4", + "@fullcalendar/multimonth": "~6.1.4", + "@fullcalendar/timegrid": "~6.1.4" + } + }, "node_modules/fullcalendar-scheduler": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" + }, + "node_modules/preact": { + "version": "10.13.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.0.tgz", + "integrity": "sha512-ERdIdUpR6doqdaSIh80hvzebHB7O6JxycOhyzAeLEchqOq/4yueslQbfnPwXaNhAYacFTyCclhwkEbOumT0tHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } } }, "dependencies": { + "@fullcalendar/core": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.4.tgz", + "integrity": "sha512-ZDD0Owv0LezAk14nsRNaOc9nbowItGmT0mnjOhEw+L6B8P5eads8yYaNA9itn70MWoOjiAG8xqD7Yk1iJGxqgQ==", + "requires": { + "preact": "^10.0.5" + } + }, + "@fullcalendar/daygrid": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.4.tgz", + "integrity": "sha512-X0QWEiA/hT8GYiQzmXt9DlZTWaQbNtBHBXGtaMNcVXbGHDCzLoWTHrde/jABGfr/i2+d9sLUO4oTtwz2HVpNtQ==", + "requires": {} + }, + "@fullcalendar/interaction": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.4.tgz", + "integrity": "sha512-69G2B61bPiHy7VyTDiwU9l8yRHPUK9XxNxjIdm3N0nvWR6BaUIBDQe8dIWht+IZUf9qirFhnfcLWkRI0fOTWtw==", + "requires": {} + }, + "@fullcalendar/list": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.4.tgz", + "integrity": "sha512-RY2fE9J7ckzhKtvHOWhlI2FnVEBC4Z9ZlgRBE2nQekX2+THt+3KnZtOQxuYGnGi30NBKx794YsXeIc6/Lnl0Ww==", + "requires": {} + }, + "@fullcalendar/multimonth": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.4.tgz", + "integrity": "sha512-bljwyIER4APKTpKF/M8u0BkMDbYDKVkSszCSg1VIX9uVTPpAbzlStqiMAwT+zoOPvoQ8Hsn+yuCFFKKbfUwAFw==", + "requires": { + "@fullcalendar/daygrid": "~6.1.4" + } + }, + "@fullcalendar/timegrid": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.4.tgz", + "integrity": "sha512-B2/levLKW0CyDQru75JeuASpCZml5W8sINCwVTstxxhjKmNBG3F5qvSX12DDrTdga/ySYObNNP1pKDwavKk/JQ==", + "requires": { + "@fullcalendar/daygrid": "~6.1.4" + } + }, + "@types/jquery": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", + "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", + "requires": { + "@types/sizzle": "*" + } + }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" + }, + "fullcalendar": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.4.tgz", + "integrity": "sha512-XAgWTauifC8HKkbN8574wEWwWCgzcgMfjkxyvK1V5D0Q/HKldZlYuvOSrI0JKkzIXtufurx9IN+QljVQkAvg+A==", + "requires": { + "@fullcalendar/core": "~6.1.4", + "@fullcalendar/daygrid": "~6.1.4", + "@fullcalendar/interaction": "~6.1.4", + "@fullcalendar/list": "~6.1.4", + "@fullcalendar/multimonth": "~6.1.4", + "@fullcalendar/timegrid": "~6.1.4" + } + }, "fullcalendar-scheduler": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" }, - "node_modules/preact": { - "version": "10.12.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", - "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } + "preact": { + "version": "10.13.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.0.tgz", + "integrity": "sha512-ERdIdUpR6doqdaSIh80hvzebHB7O6JxycOhyzAeLEchqOq/4yueslQbfnPwXaNhAYacFTyCclhwkEbOumT0tHw==" } } } From 1b3887c3aadf042d52e80aaefecb1e53d992c0b3 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 7 Mar 2023 14:50:51 -0800 Subject: [PATCH 019/100] spec file --- .github/workflows/spec.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index e1d2c048a..121dd35ec 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -17,6 +17,8 @@ jobs: env: PRONTO_PULL_REQUEST_ID: ${{ github.event.pull_request.number }} PRONTO_GITHUB_ACCESS_TOKEN: "${{ github.token }}" + CCTR: ./cc-test-reporter + CCTR_ID: ${{ secrets.CC_TEST_REPORTER_ID }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 @@ -51,6 +53,11 @@ jobs: - run: sudo apt-get install xvfb - name: Install JavaScript libraries via npm run: npm install + - name: set up CodeClimate test-reporter + run: | + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > $CCTR + chmod +x $CCTR + $CCTR before-build - name: Prepare spec run: | rm -f osem_test osem_development @@ -59,7 +66,9 @@ jobs: bundle exec rake factory_bot:lint RAILS_ENV=test # TODO: Not all suites need xvfb - name: spec/${{ matrix.suite }} - run: xvfb-run --auto-servernum bundle exec rake spec:${{ matrix.suite }} + run: | + xvfb-run --auto-servernum bundle exec rake spec:${{ matrix.suite }} + $CCTR format-coverage --output coverage/codeclimate.$SUITE.json --input-type simplecov # - name: coverage upload ${{ matrix.suite }} # uses: codacy/codacy-coverage-reporter-action@v1 # if: github.ref == 'refs/heads/master' && always() @@ -73,3 +82,10 @@ jobs: name: capybara-screenshots path: tmp/capybara/ retention-days: 7 + + - name: Publish code coverage + run: | + export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" + $CCTR sum-coverage coverage/codeclimate.*.json + $CCTR upload-coverage --id $CCTR_ID + $CCTR after-build --id $CCTR_ID From 056cde0ba82fbdf7bc1b77e4c6b26c31d169f704 Mon Sep 17 00:00:00 2001 From: Juapu <70485564+Juapu@users.noreply.github.com> Date: Wed, 8 Mar 2023 12:39:59 -0800 Subject: [PATCH 020/100] Update info.yml --- info.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/info.yml b/info.yml index df1335b63..40cec3e6a 100644 --- a/info.yml +++ b/info.yml @@ -23,7 +23,7 @@ project: surname: 'Pau' githubUsername: 'Juapu' pivotalUsername: 'Jupau' - herokuEmail: 'jupau@berkeley.edu ' + herokuEmail: 'jupau@berkeley.edu' member4: name: 'Shiv' surname: 'Sethi' From ae238f7ef101fd2a6c0e7f2e536e819f0ab486a3 Mon Sep 17 00:00:00 2001 From: Juapu <70485564+Juapu@users.noreply.github.com> Date: Wed, 8 Mar 2023 12:43:33 -0800 Subject: [PATCH 021/100] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d8f5a243c..6c32456ea 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ [![Test Coverage](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/test_coverage)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/test_coverage) ## Spring 2023 CS169L: +[![Bluejay Dashboard](https://img.shields.io/badge/Bluejay-Dashboard_02-blue.svg)](http://dashboard.bluejay.governify.io/dashboard/script/dashboardLoader.js?dashboardURL=https://reporter.bluejay.governify.io/api/v4/dashboards/tpa-CS169L-23-GH-cs169_snapcon/main) [![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) [![CodeQL](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml) [![build](https://github.com/cs169/snapcon/actions/workflows/main.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/main.yml) From 51f8cbdc20b01cbc853411fbccd7146dd05f7f13 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 8 Mar 2023 12:48:40 -0800 Subject: [PATCH 022/100] .env --- Gemfile 2.next | 1 + app/assets/config/manifest.js | 6 - app/assets/images/person_large.png | Bin 4312 -> 0 bytes app/assets/images/person_small.png | Bin 1054 -> 0 bytes app/assets/images/person_tiny.png | Bin 581 -> 0 bytes app/assets/images/rails.png | Bin 6646 -> 0 bytes app/assets/images/slideshare.png | Bin 6475 -> 0 bytes app/assets/images/snapcon_logo-original.png | Bin 46687 -> 0 bytes app/assets/images/snapcon_logo.png | Bin 15211 -> 0 bytes app/assets/images/snapshot-2020.png | Bin 54727 -> 0 bytes app/assets/images/speakerdeck.png | Bin 1465 -> 0 bytes app/assets/images/star-bright.png | Bin 1181 -> 0 bytes app/assets/images/star-glow.png | Bin 1015 -> 0 bytes app/assets/images/star.png | Bin 620 -> 0 bytes app/assets/images/suse.svg | 104 ------ app/assets/javascripts/application.js | 70 ---- app/assets/javascripts/fullcalendar.js.erb | 92 ----- app/assets/javascripts/osem-bootstrap.js | 24 -- app/assets/javascripts/osem-commercials.js | 33 -- app/assets/javascripts/osem-datatables.js | 49 --- app/assets/javascripts/osem-datepickers.js | 67 ---- .../javascripts/osem-revisionhistory.js | 10 - app/assets/javascripts/osem-schedule.js | 181 ---------- app/assets/javascripts/osem-survey.js | 33 -- app/assets/javascripts/osem-switch.js | 48 --- app/assets/javascripts/osem-tickets.js | 28 -- app/assets/javascripts/osem.js | 226 ------------ app/assets/stylesheets/application.css | 31 -- app/assets/stylesheets/breakpoints.scss | 21 -- app/assets/stylesheets/conferences.scss | 13 - .../jquery-ui-timepicker-addon.css | 10 - app/assets/stylesheets/osem-dashboard.scss | 65 ---- app/assets/stylesheets/osem-datatables.scss | 21 -- app/assets/stylesheets/osem-fonts.scss | 2 - app/assets/stylesheets/osem-navbar.scss | 58 ---- app/assets/stylesheets/osem-payments.scss | 3 - app/assets/stylesheets/osem-rating.scss | 17 - app/assets/stylesheets/osem-schedule.scss | 187 ---------- app/assets/stylesheets/osem-splash.scss | 213 ------------ app/assets/stylesheets/osem-variables.scss | 14 - app/assets/stylesheets/osem.scss | 145 -------- app/assets/stylesheets/strap-on.scss | 60 ---- app/helpers/admin/cfps_helper.rb | 13 - app/helpers/admin/volunteers_helper.rb | 10 - app/helpers/application_helper.rb | 209 ----------- app/helpers/chart_helper.rb | 15 - app/helpers/conference_helper.rb | 104 ------ app/helpers/date_time_helper.rb | 40 --- app/helpers/event_types_helper.rb | 23 -- app/helpers/events_helper.rb | 324 ------------------ app/helpers/format_helper.rb | 213 ------------ app/helpers/paths_helper.rb | 15 - app/helpers/users_helper.rb | 35 -- app/helpers/versions_helper.rb | 169 --------- app/services/full_calendar_formatter.rb | 49 --- app/services/mailbluster_manager.rb | 40 --- app/views/admin/booths/_all_booths.csv.haml | 19 - app/views/admin/booths/_all_booths.pdf.prawn | 26 -- .../admin/booths/_confirmed_booths.pdf.prawn | 26 -- app/views/admin/booths/booths.pdf.prawn | 5 - .../admin/comments/_posted_comments.html.haml | 12 - .../admin/comments/_unread_comments.html.haml | 12 - .../admin/conferences/_form_fields.html.haml | 99 ------ .../_recent_registrations.html.haml | 19 - .../conferences/_top_submitter.html.haml | 18 - app/views/admin/contacts/edit.html.haml | 58 ---- .../admin/difficulty_levels/edit.html.haml | 9 - app/views/admin/emails/index.html.haml | 217 ------------ app/views/admin/event_types/edit.html.haml | 9 - app/views/admin/event_types/index.html.haml | 44 --- app/views/admin/events/_all_events.pdf.prawn | 36 -- .../admin/events/_datatable_row_rating.haml | 11 - app/views/admin/events/_form.html.haml | 42 --- app/views/admin/events/_user_fields.html.haml | 5 - app/views/admin/events/events.csv.haml | 6 - app/views/admin/events/vote.js.erb | 10 - app/views/admin/lodgings/new.html.haml | 8 - app/views/admin/organizations/index.html.haml | 39 --- dotenv.example | 126 +++++++ info.yml | 2 +- 80 files changed, 128 insertions(+), 3821 deletions(-) create mode 120000 Gemfile 2.next delete mode 100644 app/assets/config/manifest.js delete mode 100644 app/assets/images/person_large.png delete mode 100644 app/assets/images/person_small.png delete mode 100644 app/assets/images/person_tiny.png delete mode 100644 app/assets/images/rails.png delete mode 100644 app/assets/images/slideshare.png delete mode 100644 app/assets/images/snapcon_logo-original.png delete mode 100644 app/assets/images/snapcon_logo.png delete mode 100644 app/assets/images/snapshot-2020.png delete mode 100644 app/assets/images/speakerdeck.png delete mode 100644 app/assets/images/star-bright.png delete mode 100644 app/assets/images/star-glow.png delete mode 100644 app/assets/images/star.png delete mode 100644 app/assets/images/suse.svg delete mode 100644 app/assets/javascripts/application.js delete mode 100644 app/assets/javascripts/fullcalendar.js.erb delete mode 100644 app/assets/javascripts/osem-bootstrap.js delete mode 100644 app/assets/javascripts/osem-commercials.js delete mode 100644 app/assets/javascripts/osem-datatables.js delete mode 100644 app/assets/javascripts/osem-datepickers.js delete mode 100644 app/assets/javascripts/osem-revisionhistory.js delete mode 100644 app/assets/javascripts/osem-schedule.js delete mode 100644 app/assets/javascripts/osem-survey.js delete mode 100644 app/assets/javascripts/osem-switch.js delete mode 100644 app/assets/javascripts/osem-tickets.js delete mode 100644 app/assets/javascripts/osem.js delete mode 100644 app/assets/stylesheets/application.css delete mode 100644 app/assets/stylesheets/breakpoints.scss delete mode 100644 app/assets/stylesheets/conferences.scss delete mode 100644 app/assets/stylesheets/jquery-ui-timepicker-addon.css delete mode 100644 app/assets/stylesheets/osem-dashboard.scss delete mode 100644 app/assets/stylesheets/osem-datatables.scss delete mode 100644 app/assets/stylesheets/osem-fonts.scss delete mode 100644 app/assets/stylesheets/osem-navbar.scss delete mode 100644 app/assets/stylesheets/osem-payments.scss delete mode 100644 app/assets/stylesheets/osem-rating.scss delete mode 100644 app/assets/stylesheets/osem-schedule.scss delete mode 100644 app/assets/stylesheets/osem-splash.scss delete mode 100644 app/assets/stylesheets/osem-variables.scss delete mode 100644 app/assets/stylesheets/osem.scss delete mode 100644 app/assets/stylesheets/strap-on.scss delete mode 100644 app/helpers/admin/cfps_helper.rb delete mode 100644 app/helpers/admin/volunteers_helper.rb delete mode 100644 app/helpers/application_helper.rb delete mode 100644 app/helpers/chart_helper.rb delete mode 100644 app/helpers/conference_helper.rb delete mode 100644 app/helpers/date_time_helper.rb delete mode 100644 app/helpers/event_types_helper.rb delete mode 100644 app/helpers/events_helper.rb delete mode 100644 app/helpers/format_helper.rb delete mode 100644 app/helpers/paths_helper.rb delete mode 100644 app/helpers/users_helper.rb delete mode 100644 app/helpers/versions_helper.rb delete mode 100644 app/services/full_calendar_formatter.rb delete mode 100644 app/services/mailbluster_manager.rb delete mode 100644 app/views/admin/booths/_all_booths.csv.haml delete mode 100644 app/views/admin/booths/_all_booths.pdf.prawn delete mode 100644 app/views/admin/booths/_confirmed_booths.pdf.prawn delete mode 100644 app/views/admin/booths/booths.pdf.prawn delete mode 100644 app/views/admin/comments/_posted_comments.html.haml delete mode 100644 app/views/admin/comments/_unread_comments.html.haml delete mode 100644 app/views/admin/conferences/_form_fields.html.haml delete mode 100644 app/views/admin/conferences/_recent_registrations.html.haml delete mode 100644 app/views/admin/conferences/_top_submitter.html.haml delete mode 100644 app/views/admin/contacts/edit.html.haml delete mode 100644 app/views/admin/difficulty_levels/edit.html.haml delete mode 100644 app/views/admin/emails/index.html.haml delete mode 100644 app/views/admin/event_types/edit.html.haml delete mode 100644 app/views/admin/event_types/index.html.haml delete mode 100644 app/views/admin/events/_all_events.pdf.prawn delete mode 100644 app/views/admin/events/_datatable_row_rating.haml delete mode 100644 app/views/admin/events/_form.html.haml delete mode 100644 app/views/admin/events/_user_fields.html.haml delete mode 100644 app/views/admin/events/events.csv.haml delete mode 100644 app/views/admin/events/vote.js.erb delete mode 100644 app/views/admin/lodgings/new.html.haml delete mode 100644 app/views/admin/organizations/index.html.haml create mode 100644 dotenv.example diff --git a/Gemfile 2.next b/Gemfile 2.next new file mode 120000 index 000000000..6ab79009c --- /dev/null +++ b/Gemfile 2.next @@ -0,0 +1 @@ +Gemfile \ No newline at end of file diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js deleted file mode 100644 index 45d64d986..000000000 --- a/app/assets/config/manifest.js +++ /dev/null @@ -1,6 +0,0 @@ -// This is a sprockets 4.0 manifest file -// -//= link application.css -//= link application.js -//= link_tree ../images - diff --git a/app/assets/images/person_large.png b/app/assets/images/person_large.png deleted file mode 100644 index 763b29b186143d97f0ba4569e84537c3edabb7c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4312 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 diff --git a/app/assets/images/person_small.png b/app/assets/images/person_small.png deleted file mode 100644 index b454217f68b2a344e44a1d381ccfd92749a3e8d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1054 zcmV+(1mXLMP)o5?7kL|Rn8d{NpcnHJ_DImlKHZ1w?YypW4 z3n)UO6wwMO^g)t3PVVB$zJ8>a78q%yR_# zW1y5OF9XQ)T)7ZpE|@O?JkNvYd0>ng5K>&44j_bp5CU12AX&sZ?eHP)d#Q zcJx9BHHUMKJkRF`V2q(ssVLV0k|aT|*TdV}8{XgF;rl+qFhm%JSX)~|Q4|J%%ogVy zLWr*c7-Ohbs}6wYdFc21xVpMB7Mf0{7>!1Fcz8eC(#4Icsy3(07`YJ zUMTDuU#r!A1YpnCHI$})tLd}=&}cNAOJ@;lHk%HB{+keD79!giV+exa*8s|l+D(Lg zhqqd-Zv)tKZJnYh{sGAQ+y>a+-*@WwJhr*HSqdOE3;_Ku*9QPVyR4a>{RYMh<+JkP;7R}HFkIYwQ~62Puh zdKXg^1*X#};y6Ye$4HU{lgR{0k{}2I)a!LH#?<0cTz9)uHhMzNx%1H>guq}hK)2gP zzu%wP-AeO)AFHdY*xA`ZqtS2xIOoQAVt*OQrj$~Or>7^pyu3J*=CvRQu)e;Im6a7_ zSytMVIsh{2PN#z;Nxmg)TeI23*49?(my*+#$8n6u$HyP_0k#c?LvYTqy}fOmECB9h z^!)t%tK!n)I94s%On@Xwkf!Oci%XkKCeCZ{04T+ej}H$3!veNQzmVrS;yC8@dR-l* YKe2I?I#@StHvj+t07*qoM6N<$f;tn``2YX_ diff --git a/app/assets/images/person_tiny.png b/app/assets/images/person_tiny.png deleted file mode 100644 index 5fab833cbe46b7f267a7b90b519afcf331b0e6b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581 zcmV-L0=oT)P)=OjSi3->;g66aAtgoXG<^QF`2<0c zC?GCkhz!D(jn{hx^4=vJcdwjz@6GD%jCy^2_56TR3S$gfYXF>cv~7#`S5(YTp|zGc zj)|fO5dom~=e})=VZ77K;T<(_oCDEKAz9 z{cWI>B1sa`G$o26US3|Pswzx*e}Cue>x<=b8D4cf9`W8I0M=TpwWMjvYPI?%?y=wR z`S|!CP1C>!5Tz7x95WaU5D_+;&6T*vcDqGH7z_r)ag0(5F~$%@5m6M;G!5tT`KH0) za6l;)_{JDSYmJD6ulpCY)+nVelSR;4Uo!al6SpuN4zFtBoTKYHy!UwTd3=1lY4Gsy z5O`hJ;haO-whc?-z32Y^p80%!C4PE(;_mJa?|sOsZCgxVsOy?Ij?r54{QS&pHY3k- zwAM_gQ%0jvm|WL&7-#^hs=6d9f-#0H%Q&4*VIQ1x6h*;gG70P4_f&sOBEskACwZR# z^=Nv|csyn_8sVI~1m8E;>osLr-V6{fN0nt6Wmz(vPA{eabzT2g{3nW{2ru{r#~SEt TIGVg)00000NkvXXu0mjf2aEp; diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png deleted file mode 100644 index d5edc04e65f555e3ba4dcdaad39dc352e75b575e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6646 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 AQ~&?~ diff --git a/app/assets/images/slideshare.png b/app/assets/images/slideshare.png deleted file mode 100644 index d730f0ae32e8dab3ae0df0df5e08dc176d143c2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6475 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 diff --git a/app/assets/images/snapcon_logo-original.png b/app/assets/images/snapcon_logo-original.png deleted file mode 100644 index 76c32bbd23675a79704af1d489421f8aa802110f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46687 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⋙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%0D@s2RNllmkusLy9*%PVOcO|#$ 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 TF2%%vALqkD*1Q7i%tq!37FAoq3D$*7T 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 IN|=QFADq0exBvhE diff --git a/app/assets/images/snapshot-2020.png b/app/assets/images/snapshot-2020.png deleted file mode 100644 index 3d9b05b1785a386b0d97bb2e2228a78ffe5aec13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54727 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 z 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 diff --git a/app/assets/images/speakerdeck.png b/app/assets/images/speakerdeck.png deleted file mode 100644 index 895ad79bdd2b5a60f06d44428111e799c9676e19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1465 zcmV;q1xEUbP)yp2 z=m}K_1$v>Na0?=wnnOb!K^2kEs02zBNK9g{*E??y&MuDGb?mg24E9K4OWxg?dFFZE zk9pT>-w)pkA9yp!2agdWs0IMp2u!MmkgGK1+VrmP5tB3mlZ^nOQ6(kq) zbWg8hGA^#?g%i|6Yd40D7l!#ylPq<$qfoJHGd2pOQm9;ktsXN~OqVvv_MZ{njEB9UUEHvsql%-Ozc&f)pvP z)fUN#YP!f+Ob>xp->tP+Yw7IlWME)`iK2 zU)U?)hz%iP*AG%n^L?LGD#d5}_Hp{m8NNDkg1Nal@~*pX91+3uJa%_=@%5=w9O>`p zr>j@_ZE})Vi;HNjt76uQ;K*Cm#mbc!xdtrSDnR=@_IpQm%%HURSZ z{5zW!d>4ElfWtjK96EG}n>TK7`}S?7(rI$J9LAUp&Q$@i$xH`f>;}ymPLmURg^1hlC|<~30g!*rc!)9ILOe@5MK-qz6DhRkhSN79x_no zgn|v|>gwYAix=rTa)di~?r?8xjK5#LL`Q706+wkI8RVc+W%(oUSQTutII!h#PY;7b zL+gGoWxbTea=A(j&_KeuT#oVk_YrH6C^kdyD#1iCO!^X;vx+UsHPC$22QkKkfxc9l1>fiH=qT5&T_c@N^ZNB`9LEVm$3_4HXCxNzCHDMZgm0`+ zm&-9ZImy$fPua0!2MY@eA)}>>ynOjGZ!#Gc78Vc@9LL!Z+0*Qq7C^o)I5Gz;K;!bg(&7VRxTD&R^2Q?<= zVWpxN25OY6KtX&88d8oSCvIIHw;V@f9YukSqi@NCo%`=30GLwzr{L5?EX5&@9n~ zND)vi3EWS1GPgUAczzw#B?V<1bXyC`agYt(0#tlW5M0r%t*F*ID}Aheh;OSYE=yHI zzRz{`=DUy&2Bg@A`1eNx$hASIx!jcN`_ujbju~5) Tfy9%000000NkvXXu0mjf1xTfa diff --git a/app/assets/images/star-bright.png b/app/assets/images/star-bright.png deleted file mode 100644 index 2dbc8ed36445f19350c03d96a707fa8faeebf411..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1181 zcmV;O1Y-M%P)P000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3Si}E+{($WS;;402y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00ad|L_t(Y$L*ERZ&Xzj$3OSJ zH#6;<>GTJ*1qwpAv`PR8CN5y5h%qK$!p4PhW8Ao8p@|!H>%y2I7(+tTgyyNmmQn(swf-dx!#Agc%=c#ya8MSz6H)IrIzl3_`d(D5aNXA zdHtT}Q79B}T^A`OQS*Cx^Pl2~4o?+Jy{k z+C#5gBWeCj7S_Q!q)~uV-p%G`KEtHR>JTBssd~MBHHsoLyz3-ZZYNxp}b3at^ch%q@319;>6iH%F6QC~F1yseZvao=TBN}V#se3coJq&o{%gN`7x z2zLXHH-*!`6`NUX+QTFvNoO8oL2G?+$BrEzuC|QsDt{ZKBXhdjfGkG1{gCr8NkAH| zAe|hpw+)+W%*qmJu*7ZO9C#308F3th=`|775G}W9-MB(89>5(tOq`C)hn#bSMbK4V$-3I$PZ0ntwjhy)STw`AK#;M^$dgk`#^vYLazCW zRVtOsagy0??HikR=dHEYhGA%HwVJL}Dq*cwOM)P<)><3gyk?jE^ET}DY^74Y_#o~2 zq;+$#73B8N>N?EM&eCW!&c$*3f|Rlx$MM01g@v!@=H~RWF3~IPqTOmCrFgK*===U* vUlRbmuy0LXv^rPO);*Tc{sfFTuk1W@3+00000NkvXXu0mjfu@WZ# diff --git a/app/assets/images/star-glow.png b/app/assets/images/star-glow.png deleted file mode 100644 index b605587a45672085a08cb580e8944619401a8041..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1015 zcmVP000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3Si}E-_zk%wGTi02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00Uo1L_t(Y$JLfkXdG1>$3HW( zo9QN#?2bt`AqgZrqyz&h6a+y8J&0CNM6H(`yoNvzqEHenqS%6|hk_-F7Y|-kuqY@9 z9)brEOGB##Ym8Gjv)eSA&7aNgWOrxZ%=0k2xI)5a6$}30!SBI?&-=dL?|t9Xlv4cH zp>r1^)=V77X}9(SFy1o%96$(hbV!?c#Wc-#W3j)Md!Bb5nCnPMGY;CB<2Wi=h1c$t=Ynj!dt+Lz!Sg&z_@MO-L`GRM~^`7P;|n%8!&r~ za-~K-pRWKp;Adb7xGaQl4};9=y8d2IPY;Pi0@E~!#bRih23Hp0m(L(M0OyR zg+d1Q_n;>Y{gW{9Fif2!=p81L$rOYTgNL{9moGzDgzi4*wV-zrLJf*{U^feX9sC;f zje^+^)e5XF!^#qTeGcASJwoB1{}iMFrCl)Mu;aq|PZ2-{5J=eDgg>rA?mA$AzYjGZ z=9b%b*J3uCb?WsxocaPP5;ii>HwHlfVFOSPB3LLPJryY|6=8mvFp$-$tc)qph2OZZ1rPO>jnt*tE|kjZ4SJN4u&_dIP000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3Si}E)$9mjk^E<02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00GoVL_t(Y$JLfWi_=gL$A2$P z8@mlOVQU2`EDYW}c<}DgZy;X$0!8p3dRbvFqI+2nf>J+#XZI_3^sd(iY9eUY9#R@C z?aO;!iUw_M7HfTF>ts|NVnZ?Q>j#nkS_2X__7U6p7_3hrCP1xIL?nN48vg4Ox8gz0o_)s z_1ldg2vADhi=ybpI*97``^CKv+yZU_*MNgC4EMq?T>9{2GGRCzegi%M?}4|#sffg@ zAa@tYD5d`RmeyLd*34!zrqd}|mZ7zNEFwqCARjuN&edA2cCMbt z^PIt8@Kr=E?z9}nt$YU}47RMj1$}b>0000 - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js deleted file mode 100644 index 8284fbf80..000000000 --- a/app/assets/javascripts/application.js +++ /dev/null @@ -1,70 +0,0 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. -// -//= require jquery -//= require jquery_ujs -//= require jquery-ui/widgets/draggable -//= require jquery-ui/widgets/droppable -//= require waypoints/jquery.waypoints - -//= require datatables/jquery.dataTables -//= require datatables/dataTables.bootstrap -//= require datatables/extensions/Buttons/dataTables.buttons -//= require datatables/extensions/Buttons/buttons.bootstrap -//= require datatables/extensions/Buttons/buttons.html5 -//= require datatables/extensions/Buttons/buttons.dataTables - -//= require cocoon -//= require bootstrap -//= require Chart.bundle -//= require chartkick -//= require osem -//= require jquery-smooth-scroll -//= require trianglify -//= require tinycolor -//= require bootstrap-markdown -//= require to-markdown -//= require markdown -//= require momentjs -//= require leaflet -//= require holderjs -//= require bootstrap-datetimepicker -//= require osem-datepickers -//= require osem-datatables -//= require osem-tickets -//= require bootstrap-switch -//= require osem-schedule -//= require osem-switch -//= require osem-bootstrap -//= require osem-revisionhistory -//= require osem-commercials -//= require unobtrusive_flash -//= require unobtrusive_flash_bootstrap -//= require countable -//= require selectize -//= require bootstrap-select -//= require osem-survey -//= require pagy -//= require fullcalendar-scheduler/main.js -//= require fullcalendar - -$(document).ready(function() { - $('a[disabled=disabled]').click(function(event){ - return false; - }); - - $('body').smoothScroll({ - delegateSelector: 'a.smoothscroll' - }); - - window.addEventListener("load", Pagy.init); -}); diff --git a/app/assets/javascripts/fullcalendar.js.erb b/app/assets/javascripts/fullcalendar.js.erb deleted file mode 100644 index e80570776..000000000 --- a/app/assets/javascripts/fullcalendar.js.erb +++ /dev/null @@ -1,92 +0,0 @@ -$( document ).ready(function() { - let calendarEl = document.getElementById('vert-schedule-full-calendar'); - if (!calendarEl) return; //check that we need a vertical schedule - let $fullCalendar = $('#fullcalendar'); - - let license_key = "<%= Rails.configuration.fullcalendar[:license_key]%>"; - - let offset = $fullCalendar.data('tzOffset'); - let interval = Math.max(5, $fullCalendar.data('minInterval')); - let localOffset = (new Date()).getTimezoneOffset()/60; - let startTime = $fullCalendar.data('startHour') - offset - localOffset; - let endTime = $fullCalendar.data('endHour') - offset - localOffset; - let startDate = new Date($fullCalendar.data('startDate')); - let endDate = new Date($fullCalendar.data('endDate')); - let event_num_days = (endDate.getTime() - startDate.getTime())/ (1000 * 3600 * 24); - let width = Math.min(4, event_num_days); - let localName = Intl.DateTimeFormat().resolvedOptions().timeZone; - // Program Hours * Minutes / Interval * Min Row Height for an event. - let contentHeight = Math.max(400, (endTime - startTime) * 60 / interval * 16); - // UTC JS offsets are "-1 *" of how they're displayed. - let operator = localOffset < 0 ? '+' : '-'; - $('.js-localTimezone').text(`(${localName} UTC ${operator}${Math.abs(localOffset)})`); - - let rightHeaderToolbar = 'resourceTimeGridDay,resourceTimeGridFourDay,listDay'; - if (event_num_days == 1) { - rightHeaderToolbar = 'resourceTimeGridDay,listDay'; - } - - // Remove subevents from the calendar. - let filterSubevents = (events) => { - return events.filter(event => event.has_parent === false) - }; - - var calendar = new FullCalendar.Calendar(calendarEl, { - schedulerLicenseKey: license_key, - nowIndicator: true, - now: $fullCalendar.data('now'), - contentHeight: contentHeight, - expandRows: true, - allDaySlot: false, - slotMinTime: startTime + ':00:00', - slotMaxTime: endTime + ':00:00', - // TODO: Set these dynamically. - slotDuration: '00:15:00', - slotLabelInterval: '00:15:00', - slotLabelFormat: { - hour: 'numeric', - minute: '2-digit', - omitZeroMinute: true, - meridiem: 'short' - }, - validRange: { - start: $fullCalendar.data('startDate'), - end: $fullCalendar.data('endDate') - }, - timeZone: 'local', - initialDate: $fullCalendar.data('day'), - initialView: 'resourceTimeGridDay', - resources: $fullCalendar.data('rooms'), - resourceOrder: 'order, title', - // TODO: Move this to a XHR. - events: filterSubevents($fullCalendar.data('events')), - displayEventEnd: false, // TODO change in list view. - displayEventTime: false, - titleFormat: { // will produce something like "Tues, September 18" - month: 'long', - day: 'numeric', - weekday: 'short' - }, - headerToolbar: { - left: 'prev,next', - center: 'title', - right: rightHeaderToolbar - }, - // TODO: Make this conference Specific? - views: { - resourceTimeGridFourDay: { - type: 'resourceTimeGrid', - duration: { days: width }, - buttonText: 'overview', - datesAboveResources: true - }, - listDay: { - type: 'listDay', - displayEventEnd: true, - displayEventTime: true - } - } - }); - - calendar.render(); -}); diff --git a/app/assets/javascripts/osem-bootstrap.js b/app/assets/javascripts/osem-bootstrap.js deleted file mode 100644 index f1335473f..000000000 --- a/app/assets/javascripts/osem-bootstrap.js +++ /dev/null @@ -1,24 +0,0 @@ -$(function() { - // add a hash to the URL when the user clicks on a tab - $('a[data-toggle="tab"]').on('click', function(e) { - history.pushState(null, null, $(this).attr('href')); - }); - // navigate to a tab when the history changes - window.addEventListener("popstate", function(e) { - var activeTab = $('a[href="' + location.hash + '"]'); - if (activeTab.length) { - activeTab.tab('show'); - } else { - $('.nav-tabs a').first().tab('show'); - } - }); -}); - -$(function() { - var hash = window.location.hash; - hash && $('ul.nav a[href="' + hash + '"]').tab('show'); -}); - -$(function () { - $('[data-toggle="popover"]').popover({html: true}) -}); diff --git a/app/assets/javascripts/osem-commercials.js b/app/assets/javascripts/osem-commercials.js deleted file mode 100644 index db2900976..000000000 --- a/app/assets/javascripts/osem-commercials.js +++ /dev/null @@ -1,33 +0,0 @@ -$(function () { - $(document).ready(function() { - $("#commercial_url").bind('paste keyup', function() { - clearTimeout($(this).data('timeout')); - - $(this).data('timeout', setTimeout(function () { - var url = $('#new_commercial').attr('action'); - url = url + '/render_commercial' - $.ajax({ - method: 'GET', - url: url, - data: { url: $('#commercial_url').val() }, - error: function(xhr, status, error) { - $('#commercial_submit_action').prop('disabled', true); - $('#resource-content').hide(); - $('#resource-placeholder').show(); - $('#commercial_error').hide(); - $('#commercial_url_input').addClass('has-error error'); - $('' + xhr.responseText + '').insertAfter('#commercial_url'); - }, - success: function(msg) { - $('#commercial_submit_action').prop('disabled', false); - $('#commercial_url_input').removeClass('has-error error'); - $('#commercial_error').hide(); - $('#resource-placeholder').hide(); - $('#resource-content').html(msg).show(); - } - }) - }, 200) - ); - }); - }); -}); diff --git a/app/assets/javascripts/osem-datatables.js b/app/assets/javascripts/osem-datatables.js deleted file mode 100644 index 5f8122330..000000000 --- a/app/assets/javascripts/osem-datatables.js +++ /dev/null @@ -1,49 +0,0 @@ -$(function () { - $.extend(true, $.fn.dataTable.defaults, { - "buttons": ["csv"], - "dom": "lBfrtip", - "stateSave": true, - "autoWidth": false, - "pagingType": "full_numbers", - "lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]], - }); - - $('.datatable:not([data-source])').DataTable(); - - $('.datatable').on('init.dt', function (e, settings, json) { - var datatableApi = $(this).dataTable().api(); - // Thanks to cale_b: https://stackoverflow.com/u/870729 - // Stack Overflow question: https://stackoverflow.com/q/5548893 - // Stack Overflow answer: https://stackoverflow.com/a/23897722 - // Grab the datatables input box and alter how it is bound to events - $(".dataTables_filter input") - .unbind() // Unbind previous default bindings - .bind("input", function(e) { // Bind our desired behavior - // If the length is 3 or more characters, or the user pressed ENTER, search - if(this.value.length >= 3 || e.keyCode == 13) { - // Call the API search function - datatableApi.search(this.value).draw(); - } - // Ensure we clear the search if they backspace far enough - if(this.value == "") { - datatableApi.search("").draw(); - } - return; - }); - }); -}); - -function truncatify(selector) { - $(selector).each(function(){ - var text = $(this).text() - $(this).html('' + text + ' ') - }); -} - -function iconize(selector, value, icon, title) { - $(selector).each(function(){ - if ($(this).text() == value) { - $(this).html(""); - } - }); -} diff --git a/app/assets/javascripts/osem-datepickers.js b/app/assets/javascripts/osem-datepickers.js deleted file mode 100644 index e5bbe10e9..000000000 --- a/app/assets/javascripts/osem-datepickers.js +++ /dev/null @@ -1,67 +0,0 @@ -$(function () { - $("input[id^='datetimepicker']").datetimepicker({ - useCurrent: false, - sideBySide: true, - format: 'YYYY-MM-DD HH:mm' - }); - - $('.datetimepicker').datetimepicker({ - useCurrent: false, - sideBySide: true, - format: 'YYYY-MM-DD HH:mm' - }); - - $("#conference-start-datepicker").datetimepicker({ - useCurrent: false, - ignoreReadonly: true, - format: "YYYY-MM-DD", - }); - - $("#conference-end-datepicker").datetimepicker({ - useCurrent: false, - ignoreReadonly: true, - format: "YYYY-MM-DD" - }); - - // start_registration <= end_registration <= end_conference - var end_conference = $('form').data('end-conference'); - - $('#registration-period-start-datepicker').datetimepicker({ - format: 'YYYY-MM-DD', - maxDate : end_conference - }); - - $('#registration-period-end-datepicker').datetimepicker({ - format: 'YYYY-MM-DD', - maxDate : end_conference - }); - - $("#conference-start-datepicker").on("dp.change",function (e) { - $('#conference-end-datepicker').data("DateTimePicker").minDate(e.date); - if (!$('#conference-end-datepicker').val()) { - $('#conference-end-datepicker').data("DateTimePicker").date(e.date); - } - }); - - $("#conference-start-datepicker").change(function (e) { - $('#conference-start-datepicker').val()?$('#conference-end-datepicker').data("DateTimePicker").minDate(e.date):$('#conference-end-datepicker').data("DateTimePicker").minDate(null); - }); - - $("#conference-end-datepicker").on("dp.change",function (e) { - $('#conference-start-datepicker').data("DateTimePicker").maxDate(e.date); - }); - - $("#conference-end-datepicker").change(function (e) { - $('#conference-end-datepicker').val()?$('#conference-start-datepicker').data("DateTimePicker").maxDate(e.date):$('#conference-start-datepicker').data("DateTimePicker").maxDate(null); - }); - - $("#registration-period-start-datepicker").on("dp.change",function (e) { - $('#registration-period-end-datepicker').data("DateTimePicker").minDate(e.date); - if (!$('#registration-period-end-datepicker').val()) { - $('#registration-period-end-datepicker').data("DateTimePicker").date(e.date); - } - }); - $("#registration-period-end-datepicker").on("dp.change",function (e) { - $('#registration-period-start-datepicker').data("DateTimePicker").maxDate(e.date); - }); -} ); diff --git a/app/assets/javascripts/osem-revisionhistory.js b/app/assets/javascripts/osem-revisionhistory.js deleted file mode 100644 index bd51435b1..000000000 --- a/app/assets/javascripts/osem-revisionhistory.js +++ /dev/null @@ -1,10 +0,0 @@ -$(document).ready(function() { - $('.show-changeset').click(function(){ - if ($(this).text() == 'View Changes'){ - $(this).text('Hide Changes'); - }else { - $(this).text('View Changes'); - } - $('#changeset-' + this.id).toggle(); - }); -}); diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js deleted file mode 100644 index 7e6fbb1e8..000000000 --- a/app/assets/javascripts/osem-schedule.js +++ /dev/null @@ -1,181 +0,0 @@ -// ADMIN SCHEDULE - -var url; // Should be initialize in Schedule.initialize -var schedule_id; // Should be initialize in Schedule.initialize - -function showError(error){ - // Delete other error messages before showing the new one - $('.unobtrusive-flash-container').empty(); - UnobtrusiveFlash.showFlashMessage(error, {type: 'error'}); -} - -var Schedule = { - initialize: function(url_param, schedule_id_param) { - url = url_param; - 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 success_callback = function(data) { - e.attr("event_schedule_id", null); - e.appendTo($(".unscheduled-events")); - e.find(".schedule-event-delete-button").hide(); - } - var error_callback = function(data) { - showError($.parseJSON(data.responseText).errors); - } - $.ajax({ - url: my_url, - type: 'DELETE', - success: success_callback, - error: error_callback, - dataType : 'json' - }); - } - else{ - showError("The event couldn't be unscheduled"); - } - }, - add: function (previous_parent, new_parent, event) { - event.appendTo(new_parent); - var event_schedule_id = 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){ - type = 'PUT'; - my_url += ('/' + event_schedule_id); - } - else{ - params['event_schedule']['event_id'] = event.attr("event_id"); - params['event_schedule']['schedule_id'] = schedule_id; - } - var success_callback = function(data) { - event.attr("event_schedule_id", data.event_schedule_id); - event.find(".schedule-event-delete-button").show(); - } - var error_callback = function(data) { - showError($.parseJSON(data.responseText).errors); - event.appendTo(previous_parent); - } - $.ajax({ - url: my_url, - type: type, - data: params, - success: success_callback, - error: error_callback, - dataType : 'json' - }); - } -}; - -$(document).ready( function() { - // hide the remove button for unscheduled and non schedulable events - $('.unscheduled-events .schedule-event-delete-button').hide(); - $('.non_schedulable .schedule-event-delete-button').hide(); - - // set events as draggable - $('.schedule-event').not('.non_schedulable').draggable({ - snap: '.schedule-room-slot', - revertDuration: 200, - revert: function (event, ui) { - return !event; - }, - stop: function(event, ui) { - this._originalPosition = this._originalPosition || ui.originalPosition; - ui.helper.animate( this._originalPosition ); - }, - opacity: 0.7, - snapMode: "inner", - zIndex: 2, - scroll: true - }); - - // set room cells as droppable - $('.schedule-room-slot').not('.non_schedulable .schedule-room-slot').droppable({ - accept: '.schedule-event', - tolerance: "pointer", - drop: function(event, ui) { - $(ui.draggable).css("left", 0); - $(ui.draggable).css("top", 0); - $(this).css("background-color", "#ffffff"); - Schedule.add($(ui.draggable).parent(), $(this), $(ui.draggable)); - }, - over: function(event, ui) { - $(this).css("background-color", "#009ED8"); - }, - out: function(event, ui) { - $(this).css("background-color", "#ffffff"); - } - }); -}); - - -// PUBLIC SCHEDULE - -function starClicked(e) { - // stops the click from propagating - if (!e) var e = window.event; - e.preventDefault(); - e.cancelBubble = true; - if (e.stopPropagation) e.stopPropagation(); - - var callback = function(data) { - $(e.target).toggleClass('fa-star fa-star-o'); - } - - var params = { favourite_user_id: $(e.target).data('user') }; - - $.ajax({ - url: $(e.target).data('url'), - type: 'PATCH', - data: params, - success: callback, - dataType : 'json' - }); -} - -function eventClicked(e, element) { - if (e.target.href) { - return; - } - var url = $(element).data('url'); - if (e.ctrlKey || e.metaKey) { - window.open(url, '_blank'); - } else { - window.location = url; - } -} - -function updateFavouriteStatus(options) { - if (options.loggedIn === false) { - $('.js-toggleEvent').hide(); - } - - options.events.forEach(function (id) { - $(`#eventFavourite-${id}`).removeClass('fa-star-o').addClass('fa-star'); - }); -} - -/* Links inside event-panel (to make ctrl + click work for these links): - = link_to text, '#', onClick: 'insideLinkClicked();', 'data-url' => url -*/ -function insideLinkClicked(event) { - // stops the click from propagating - if (!event) // for IE - var event = window.event; - event.cancelBubble = true; - if (event.stopPropagation) event.stopPropagation(); - - var url = $(event.target).data('url'); - if(event.ctrlKey || e.metaKey) - window.open(url,'_blank'); - else - window.location = url; -} diff --git a/app/assets/javascripts/osem-survey.js b/app/assets/javascripts/osem-survey.js deleted file mode 100644 index 20ed2a194..000000000 --- a/app/assets/javascripts/osem-survey.js +++ /dev/null @@ -1,33 +0,0 @@ -$(function() { - $('.selectpicker').on('changed.bs.select', function (e, clickedIndex) { - $('.kinds').addClass('hidden'); - var selected = $('.selectpicker').find('option:selected').val(); - $('.' + selected).removeClass('hidden'); - - if (selected == 'choice') { - $('.survey-possible-answers').removeClass('hidden'); - } - else - { - $('.survey-possible-answers').addClass('hidden'); - } - }); - $('#survey_question_title').on('keyup', function(){ - $('#survey_question_preview #title').text($(this).val()) - }); - - function render_possible_answers_preview() { - var options_html = ''; - var options_array = $('#survey_question_possible_answers').val().split(','); - var input_type = ($('#survey_question_min_choices').val() == 1 && - $('#survey_question_max_choices').val() == 1) ? 'radio' : 'checkbox'; - $.each(options_array, function(index, option) { - options_html += ' ' + option.trim() + '
'; - }); - $('#survey_question_preview .choice').html(options_html) - }; - - $('#survey_question_possible_answers').on('keyup', render_possible_answers_preview); - $('#survey_question_min_choices').on('change', render_possible_answers_preview); - $('#survey_question_max_choices').on('change', render_possible_answers_preview); -}); diff --git a/app/assets/javascripts/osem-switch.js b/app/assets/javascripts/osem-switch.js deleted file mode 100644 index 5e8c47921..000000000 --- a/app/assets/javascripts/osem-switch.js +++ /dev/null @@ -1,48 +0,0 @@ -function checkboxSwitch(selector){ - $(selector).bootstrapSwitch( - - ); - - $(selector).on('switchChange.bootstrapSwitch', function(event, state) { - var url = $(this).attr('url') + state; - var method = $(this).attr('method') || 'patch'; - - $.ajax({ - url: url, - type: method, - dataType: 'script' - }); - }); -} - -$(function () { - $.fn.bootstrapSwitch.defaults.onColor = 'success'; - $.fn.bootstrapSwitch.defaults.offColor = 'warning'; - $.fn.bootstrapSwitch.defaults.onText = 'Yes'; - $.fn.bootstrapSwitch.defaults.offText = 'No'; - $.fn.bootstrapSwitch.defaults.size = 'small'; - - - checkboxSwitch("[class='switch-checkbox']"); - - $("[class='switch-checkbox-schedule']").bootstrapSwitch(); - - $('input[class="switch-checkbox-schedule"]').on('switchChange.bootstrapSwitch', function(event, state) { - var url = $(this).attr('url'); - var method = $(this).attr('method') || 'patch'; - - if(state){ - url += $(this).attr('value'); - } - - var callback = function(data) { - showError($.parseJSON(data.responseText).errors); - } - $.ajax({ - url: url, - type: method, - error: callback, - dataType: 'json' - }); - }); -}); diff --git a/app/assets/javascripts/osem-tickets.js b/app/assets/javascripts/osem-tickets.js deleted file mode 100644 index 3f559c380..000000000 --- a/app/assets/javascripts/osem-tickets.js +++ /dev/null @@ -1,28 +0,0 @@ -function update_price($this){ - var id = $this.data('id'); - - // Calculate price for row - var value = $this.val(); - var price = $('#price_' + id).text(); - $('#total_row_' + id).text((value * price).toFixed(2)); - - // Calculate total price - var total = 0; - $('.total_row').each(function( index ) { - total += parseFloat($(this).text()); - }); - $('#total_price').text(total.toFixed(2)); -} - -$( document ).ready(function() { - $('.quantity').each(function() { - update_price($(this)); - }); - - $('.quantity').change(function() { - update_price($(this)); - }); - $(function () { - $('[data-toggle="tooltip"]').tooltip() - }); -}); diff --git a/app/assets/javascripts/osem.js b/app/assets/javascripts/osem.js deleted file mode 100644 index 4701f1b1a..000000000 --- a/app/assets/javascripts/osem.js +++ /dev/null @@ -1,226 +0,0 @@ -$(function () { - /** - * Update the number of words in the biography text field every time the user - * releases a key on the keyboard - */ - $("#user_biography").bind('keyup', function() { - word_count(this, 'bio-length', 150); - } ); - - /** - * Displays a modal with the questions of the registration. - */ - $(document).ready(function(){ - $(".question-btn").click(function(){ - var id = $(this).data('id'); - $("#question-modal-body").empty(); - $("#question-modal-body").html($(".question" + id).clone().show()); - $("#question-modal-header").text('Questions for ' + $(this).data('name')); - $('#questions').modal('show'); - }); - }); - - /** - * Toggles email template help below email body textarea field. - */ - $(document).ready( function() { - $(".template-help").hide(); - $(".template_help_link").click(function() { - var id = $(this).data('name'); - $("#" + id).toggle(); - }); - }); - - /** - * Randomize order of parallel elements by shuffling the decks - * Adapted from https://stackoverflow.com/questions/7070054 - */ - - $(document).ready( function() { - $.each($(".shuffle-deck"), function(index, deck) { - for(var i = deck.children.length; i >= 0; i--) { - deck.appendChild(deck.children[Math.random() * i | 0]); - } - }); - }); - - $(".select-help-toggle").change(function () { - var id = $(this).attr('id'); - $('.' + id).collapse('hide'); - - $('#' + $(this).val() + '-help.' + id).collapse('show'); - $('#' + $(this).val() + '-instructions.' + id).collapse('show'); - - }); - $('.dropdown-toggle').dropdown(); - - /** - * Adds the default template as value to the regarding email textarea field. - */ - $(".load_template").on('click', function () { - var subject_input_id = $(this).data('subject-input-id'); - var subject_input_text = $(this).data('subject-text'); - var body_input_id = $(this).data('body-input-id'); - var body_input_text = $(this).data('body-text'); - $('#' + subject_input_id).val(subject_input_text); - $('#' + body_input_id).val(body_input_text); - }); - - /** - * Toggle the required attribute on click on_send_email radio button. - */ - $('.send_on_radio').click(function () { - toggle_required_for_mail_subjects($(this)) - }); - - /** - * Adds required attribute to on_send_email radio button if necessary. - */ - $('.send_on_radio').each(function () { - toggle_required_for_mail_subjects($(this)) - }); - /** - * Toggle the required attribute helper function. - */ - function toggle_required_for_mail_subjects($this) { - var name = $this.data('name'); - if ($this.is(':checked')) { - $('#' + name).prop('required', true); - } else { - $('#' + name).removeAttr('required'); - } - } - - $(".comment-reply-link").click(function(){ - $(".comment-reply", $(this).parent()).toggle(); - return false; - }); - - $(".comment-reply").hide(); - $(".user-details-popover").popover(); - $("#comments-div").hide(); - - $('a:contains("Add track")').click(function () { - setTimeout(function () { - $("div.nested-fields:last div:nth-of-type(2) input").val(get_color()); - }, - 5) - }); - - $('a:contains("Add difficulty_level")').click(function () { - setTimeout(function () { - $("div.nested-fields:last div:nth-of-type(3) input").val(get_color()); - }, - 5) - }); - - $('a:contains("Add event_type")').click(function () { - setTimeout(function () { - $("div.nested-fields:last div:nth-of-type(5) input").val(get_color()); - }, - 5) - }); -}); - -function get_color() { - var colors = ['#000000', '#0000FF', '#00FF00', '#FF0000', '#FFFF00', '#9900CC', - '#CC0066', '#00FFFF', '#FF00FF', '#C0C0C0', '#00008B', '#FFD700', - '#FFA500', '#FF1493', '#FF00FF', '#F0FFFF', '#EE82EE', '#D2691E', - '#C0C0C0', '#A52A2A', '#9ACD32', '#9400D3', '#8B008B', '#8B0000', - '#87CEEB', '#808080', '#800080', '#008B8B', '#006400' - ]; - return colors[Math.floor(Math.random() * colors.length)]; -} - -function word_count(text, divId, maxcount) { - var area = document.getElementById(text.id) - - Countable.once(area, function(counter) { - $('#' + divId).text(counter.words); - if (counter.words > maxcount) - $('#' + divId).css('color', 'red'); - else - $('#' + divId).css('color', 'black'); - }); -}; - -function replace_defaut_submission_text(input_selector, new_text, valid_defaults) { - let $area = $(input_selector); - let current_text = $area.val(); - - if (!current_text) { - $area.val(new_text); - $area.trigger('change'); - return; - } - - valid_defaults.some(default_text => { - if (current_text == default_text) { - $area.val(new_text); - $area.trigger('change'); - return true; - } - }); -} - -/* Wait for the DOM to be ready before attaching events to the elements */ -$( document ).ready(function() { - /* Set the minimum and maximum proposal abstract and submission text word length */ - $("#event_event_type_id").change(function () { - var $selected = $("#event_event_type_id option:selected") - var max = $selected.data("max-words"); - var min = $selected.data("min-words"); - - // We replace the default text only if the current field is empty, - // or is set to the default text of another event type. - replace_defaut_submission_text( - '#event_submission_text', - $selected.data("instructions"), - $("#event_event_type_id option").toArray().map(e => $(e).data('instructions')) - ); - - $("#abstract-maximum-word-count").text(max); - $("#submission-maximum-word-count").text(max); - $("#abstract-minimum-word-count").text(min); - $("#submission-minimum-word-count").text(min); - word_count($('#event_abstract').get(0), 'abstract-count', max); - word_count($('#event_submission_text').get(0), 'submission-count', max); - }).trigger('change'); - - /* Count the proposal abstract length */ - $("#event_abstract").on('input', function() { - var $selected = $("#event_event_type_id option:selected") - var max = $selected.data("max-words"); - word_count(this, 'abstract-count', max); - } ); - - /* Count the submission text length */ - $("#event_submission_text").bind('change keyup paste input', function() { - var $selected = $("event_event_type_id option:selected") - var max = $selected.data("max-words"); - word_count(this, 'submission-count', max); - }); - - /* Listen for reset template button, wait for confirm, and reset. */ - $('.js-resetSubmissionText').click((e) => { - let $selected = $("#event_event_type_id option:selected"); - let $this = $(e.target); - let affirm = confirm($this.data('confirm')); - if (affirm) { - let sub_text = $('#event_submission_text'); - sub_text.val($selected.data('instructions')); - sub_text.trigger('change'); - } - }); -}); - -/* Commodity function for modal windows */ - -window.build_dialog = function(selector, content) { - // Close it and remove content if it's already open - $("#" + selector).modal('hide'); - $("#" + selector).remove(); - // Add new content and pops it up - $("body").append("
\n" + content + "
"); - $("#" + selector).modal(); -} diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index 7c23a0d90..000000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,31 +0,0 @@ -/* - *= require strap-on - *= require datatables/dataTables.bootstrap - *= require datatables/extensions/Buttons/buttons.dataTables - *= require datatables/extensions/Buttons/buttons.bootstrap - - *= require selectize - *= require selectize.bootstrap3 - *= require bootstrap-markdown - *= require bootstrap-datetimepicker - *= require bootstrap-select - - *= require osem - *= require osem-rating - *= require osem-schedule - *= require osem-dashboard - *= require osem-splash - *= require osem-fonts - *= require bootstrap-markdown - *= require bootstrap-datetimepicker - *= require leaflet - *= require bootstrap3-switch - *= require osem-payments - *= require osem-navbar - *= require selectize - *= require selectize.bootstrap3 - *= require bootstrap-select - *= require conferences - - *= require fullcalendar-scheduler/main.css -*/ diff --git a/app/assets/stylesheets/breakpoints.scss b/app/assets/stylesheets/breakpoints.scss deleted file mode 100644 index d3dbc7998..000000000 --- a/app/assets/stylesheets/breakpoints.scss +++ /dev/null @@ -1,21 +0,0 @@ -@mixin breakpoint($class) { - @if $class == xs { - @media (max-width: 767px) { @content; } - } - - @else if $class == sm { - @media (min-width: 768px) and (max-width: 991px) { @content; } - } - - @else if $class == md { - @media (min-width: 992px) and (max-width: 1199px) { @content; } - } - - @else if $class == lg { - @media (min-width: 1200px) { @content; } - } - - @else { - @warn "Breakpoint mixin supports: xs, sm, md, lg"; - } -} diff --git a/app/assets/stylesheets/conferences.scss b/app/assets/stylesheets/conferences.scss deleted file mode 100644 index fbf950cda..000000000 --- a/app/assets/stylesheets/conferences.scss +++ /dev/null @@ -1,13 +0,0 @@ -/* Apply CSS to views of a specific conference by encapsulating the style in - * a body tag classed for the conference's short title. - * See the example here for 'osemdemo'. - * NOTE: when overriding existing CSS, it may be necessary to use !important to - * bypass CSS specificity rules. -*/ - -body.conference-osemdemo { - // style here only applies when working with the 'osemdemo' conference. - #header { - background-color: #b58e73 !important; - } -} diff --git a/app/assets/stylesheets/jquery-ui-timepicker-addon.css b/app/assets/stylesheets/jquery-ui-timepicker-addon.css deleted file mode 100644 index b93a85f62..000000000 --- a/app/assets/stylesheets/jquery-ui-timepicker-addon.css +++ /dev/null @@ -1,10 +0,0 @@ -.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } -.ui-timepicker-div dl { text-align: left; } -.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } -.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } -.ui-timepicker-div td { font-size: 90%; } -.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } - -.ui-timepicker-rtl{ direction: rtl; } -.ui-timepicker-rtl dl { text-align: right; } -.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; } \ No newline at end of file diff --git a/app/assets/stylesheets/osem-dashboard.scss b/app/assets/stylesheets/osem-dashboard.scss deleted file mode 100644 index fbab73114..000000000 --- a/app/assets/stylesheets/osem-dashboard.scss +++ /dev/null @@ -1,65 +0,0 @@ -@import "breakpoints.scss"; - -.dashbox span { - margin-bottom: 2px; - - @include breakpoint(xs) { - font-size: 1.5em; - } - @include breakpoint(sm) { - font-size: 2em; - } - @include breakpoint(md) { - font-size: 3.5em; - } - @include breakpoint(lg) { - font-size: 5em; - } -} - -.dashbox.panel { - padding: 10px; - display: -webkit-flex; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.4rem; - line-height: 1.6rem; - - label { - line-height: 2rem; - } -} - -.todolist-missing span { - color: #ff0000; -} - -.todolist-ok { - text-decoration: line-through; -} - -.todolist-ok span { - color: #02A10F; -} - -.top-submitter img { - margin: 0 auto; -} - -#doughnut .tab-content { - padding-bottom: 20px; -} - -#submissions { - padding-bottom: 20px; -} - -.conferenceCheckboxes div { - display: inline-block; - padding: 0px 10px 0px 0px; -} - -.margin-event-table{ - margin-top: 40px !important; -} diff --git a/app/assets/stylesheets/osem-datatables.scss b/app/assets/stylesheets/osem-datatables.scss deleted file mode 100644 index 4f640f49b..000000000 --- a/app/assets/stylesheets/osem-datatables.scss +++ /dev/null @@ -1,21 +0,0 @@ -table.datatable { - @extend .table, .table-striped, .table-bordered, .table-hover; - - td.actions { - vertical-align: middle; - align: center; - - div.btn-group { - display: flex; - } - - a { - @extend .btn; - } - } - - td.truncate { - max-width: 1px; - @include text-overflow(); - } -} diff --git a/app/assets/stylesheets/osem-fonts.scss b/app/assets/stylesheets/osem-fonts.scss deleted file mode 100644 index 9ce1c5bfb..000000000 --- a/app/assets/stylesheets/osem-fonts.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "font-awesome"; - diff --git a/app/assets/stylesheets/osem-navbar.scss b/app/assets/stylesheets/osem-navbar.scss deleted file mode 100644 index 4df2423f9..000000000 --- a/app/assets/stylesheets/osem-navbar.scss +++ /dev/null @@ -1,58 +0,0 @@ -@import "breakpoints.scss"; -@import "osem-variables.scss"; - -.nav-osem { - border: none; - @include breakpoint(xs) { - .navbar-collapse { - background-color: white; - border-bottom: 2px solid #efefef; - } - .navbar-nav > li > a { - color: black; - } - .navbar-collapse.in { - overflow: hidden; - max-height: none; - } - .navbar-toggle .icon-bar { - background-color: white; - } - .navbar-nav .open .dropdown-toggle:focus { - color: black; - background-color: #eeeeee; - } - .btn-group { - width: 100%; - text-align: center; - } - .navbar-nav .open .dropdown-menu > li > a { - color: black; - } - } - .dropdown-menu { - min-width: 225px; - } - - &.navbar-default { - .navbar-nav > .open > a { - &:hover, &:focus { - color: $navbar-default-link-color; - } - } - } - - .navbar-brand img { - max-height: 100%; - } - - .trapezoid { - border-top-color: $navbar-default-bg; - } - - .profile-thumbnail { - border-radius: 3px; - max-height: 20px; - max-width: 20px; - } -} diff --git a/app/assets/stylesheets/osem-payments.scss b/app/assets/stylesheets/osem-payments.scss deleted file mode 100644 index cfa451b40..000000000 --- a/app/assets/stylesheets/osem-payments.scss +++ /dev/null @@ -1,3 +0,0 @@ -.stripe-button-el { - float: right; -} diff --git a/app/assets/stylesheets/osem-rating.scss b/app/assets/stylesheets/osem-rating.scss deleted file mode 100644 index b4a43c28f..000000000 --- a/app/assets/stylesheets/osem-rating.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* Styling for voting on proposals*/ -.rating { - background: image-url("star.png") 0 0; - background-size: 20px; - width: 20px; - height: 20px; - display: inline-block; - float: left; - - &.bright { - background-image: image-url("star-bright.png"); - } - - &.glow { - background-image: image-url("star-glow.png"); - } -} diff --git a/app/assets/stylesheets/osem-schedule.scss b/app/assets/stylesheets/osem-schedule.scss deleted file mode 100644 index f722a41e9..000000000 --- a/app/assets/stylesheets/osem-schedule.scss +++ /dev/null @@ -1,187 +0,0 @@ -.room-name { - font-weight: bold; - padding:10px; - margin-top: 30px; - border: 1px solid #848484; - background-color: #E6E6E6; - - text-overflow: ellipsis; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - height: 40px; - line-height: 25px; - overflow: hidden; -} - -.schedule-room-slot { - padding: 2px 3px; - border: 1px solid #848484; - height: 58px; - line-height: 20px; - font-size: 13px; -} - -.schedule-event { - padding: 7px; - border: 2px solid #151515; - position:relative; - z-index:1; - cursor: move; - - a { - color: black; - } -} - -.schedule-room-slot.compact { - line-height: 12px; - font-size: 10px; - padding: 1px; -} - -// When an event is in a room. -.schedule-room-slot .schedule-event.compact { - margin-top: -14px; - width: 85%; - margin-left: 15%; -} - -.schedule-event.compact { - font-size: 75%; - padding: 0; - border-width: 1px; - - .schedule-event-text { - line-height: 12px; - overflow: hidden; - } -} - -.schedule-event-text { - display: -webkit-box; - text-overflow: ellipsis; - -webkit-box-orient: vertical; - line-height: 23px; - overflow: hidden; -} - -.schedule-event.compact { - .schedule-event-delete-button { - padding: 1px; - margin-right: 3px; - } -} - -.schedule-event-delete-button { - font-weight: bold; - cursor: pointer; - padding: 0 3px; - margin-right: 5px; - color: black; -} - - -.flexvideo { - position: relative; - padding-bottom: 56.25%; /* 16:9 */ - padding-top: 25px; - height: 0; -} -.flexvideo iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -.speakerinfo { - margin-top: 40px; -} - -.speakerbio { - margin-top: 10px; -} - - -.program-dropdown { - margin-left: 20%; - margin-right: 20%; - margin-top: 10px; - margin-bottom: 20px; -} - -.program-dropdown > button, -.program-dropdown > .dropdown-menu { - width: 100%; -} - -.elipsis{ - text-overflow: ellipsis; - display: -webkit-box; - -webkit-box-orient: vertical; -} - -.break-words{ - /* These are technically the same, but use both */ - overflow-wrap: break-word; - word-wrap: break-word; - - /* Warning: Needed for oldIE support, but words are broken up letter-by-letter */ - -ms-word-break: break-all; - word-break: break-all; - - /* Non standard for webkit */ - word-break: break-word; - - -ms-hyphens: auto; - -moz-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -.date-title{ - font-size: 23px; - font-weight: bold; - padding-bottom: 2px; - display:inline-block; -} - -.date-content { - border-bottom: 1px solid #6E6E6E; - margin: 30px 0 20px; -} - -.unscheduled-event{ - margin-top: 8px; -} - -.track { - padding: 0px 5px 0px 5px; - display: inline-block; - } - -.no-events-day { - color: #D8D8D8 !important; -} - -.non_schedulable{ - opacity: 0.5; -} - -.event-panel-title { - margin: auto 3px; - flex: 1; - line-height: 1.4; - - // Override some bootstrap - small { - line-height: 1.6; - } -} - -// Override Boostrap setting. -h3.event-panel-title small { - line-height: 1.4; -} diff --git a/app/assets/stylesheets/osem-splash.scss b/app/assets/stylesheets/osem-splash.scss deleted file mode 100644 index 15354cfc2..000000000 --- a/app/assets/stylesheets/osem-splash.scss +++ /dev/null @@ -1,213 +0,0 @@ -@import "breakpoints.scss"; -@import "osem-variables.scss"; - -#splash { - // Counter the general padding for #content - margin-bottom: -65px; - - section { - padding-top: 60px; - padding-bottom: 60px; - - .trapezoid { - top: 60px + $trapezoid-height; - } - } - - section:nth-child(even) { - background: none repeat scroll 0 0 #eee; - color: #333; - - .trapezoid { - border-top-color: #eee; - } - } - section:nth-child(odd) { - background: none repeat scroll 0 0 #ffffff; - color: #333; - .thumbnail { - background: none repeat scroll 0 0 #e0e0e0; - } - - .trapezoid { - border-top-color: #fff; - } - } - - .cta-button { - padding-top: 30px; - } - - #banner{ - background-repeat: no-repeat; - background-position: center center; - background-size: cover; - margin-top: -10px; - padding-top: 100px; - padding-bottom: 100px; - .container { - #header-no-image { - background-color: rgba(224, 224, 224,.80); - padding-top: 20px; - padding-bottom: 20px; - } - - #header-image { - color: #FFF; - } - } - } - - #program { - h3 { - padding-left: 5px; - } - .track { - padding: 20px; - } - .track:hover{ - background-color: #F0FFFF; - } - } - - #callforpapers { - .timer-box{ - width: 130px; - margin: 35px 24px 20px 0; - border-radius: 50%; - border:4px solid #989797; - } - } - - #venue { - padding-top: 0px; - padding-bottom: 0px; - #venue-pic { - margin-top: 20px; - } - // The venue section has less padding. - .trapezoid { - top: $trapezoid-height; - } - } - - #lodging { - @include breakpoint(xs) { - .img-lodging{ - max-width: 280px; - } - } - @include breakpoint(sm) { - .img-lodging{ - max-width: 200px; - } - } - @include breakpoint(sm) { - .img-lodging{ - max-width: 260px; - } - } - @include breakpoint(lg) { - .img-lodging{ - max-width: 300px; - max-height: 200px; - } - } - } - - #tickets { - a { - &:hover, &:focus { - text-decoration: none; - } - } - } - - #sponsors { - .img-sponsor { - max-height: 100px; - margin: 2% auto; - } - .img-sponsor-1 { - max-height: 200px; - } - .img-sponsor-2 { - max-height: 150px; - } - .img-sponsor-3 { - max-height: 120px; - } - @include breakpoint(xs) { - .img-sponsor, .img-sponsor-1, .img-sponsor-3, .img-sponsor-3{ - max-height: unset; - max-width: 280px; - } - } - } - - .social-media.trapezoid { - border-top-color: #0C3559; // TODO: Use @conference color? - } - - #social-media{ - background: none repeat scroll 0 0 #0C3559; - padding: 50px 20px; - i{ - color: #FFF; - } - i:hover{ - color: #F2E205; - } - a{ - padding-left: 100px; - } - a:nth-child(-n+1) { - padding-left: 0px; - } - @include breakpoint(xs) { - a{ - padding-left: 10px; - } - } - } - - .scroll-top-wrapper { - position: fixed; - opacity: 0; - visibility: hidden; - overflow: hidden; - text-align: center; - z-index: 99999999; - background-color: #424242; - width: 50px; - height: 48px; - line-height: 48px; - right: 30px; - bottom: 30px; - padding-top: 2px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; - border-bottom-left-radius: 10px; - -webkit-transition: all 0.5s ease-in-out; - -moz-transition: all 0.5s ease-in-out; - -ms-transition: all 0.5s ease-in-out; - -o-transition: all 0.5s ease-in-out; - transition: all 0.5s ease-in-out; - i.fa-solid { - line-height: inherit; - } - // &.show { - // visibility:visible; - // cursor:pointer; - // opacity: 1.0; - // } - a { - color: white; - } - } -} - -.publicorprivate { - padding-top: 30px; -} diff --git a/app/assets/stylesheets/osem-variables.scss b/app/assets/stylesheets/osem-variables.scss deleted file mode 100644 index 1ae23c039..000000000 --- a/app/assets/stylesheets/osem-variables.scss +++ /dev/null @@ -1,14 +0,0 @@ -// This file is included *early* in CSS order! -// These variables change bootstrap defaults. - -$navbar-default-bg: #0C3559; -$navbar-default-link-active-bg: rgb(8,8,8); -$navbar-default-border: 1px solid #d4d4d4; -$navbar-default-color: #FFF; -$navbar-default-link-color: #FFF; -$navbar-default-link-hover-color: #F2E205; -$navbar-default-link-active-hover-color: #FFF; - - -// The hiegh of the section tabs that are like Snap! blocks. -$trapezoid-height: 14px; diff --git a/app/assets/stylesheets/osem.scss b/app/assets/stylesheets/osem.scss deleted file mode 100644 index 4c7c28988..000000000 --- a/app/assets/stylesheets/osem.scss +++ /dev/null @@ -1,145 +0,0 @@ -@import "osem-variables"; -@import "bootstrap/mixins"; - -html { - position: relative; - min-height: 100%; -} - -body { - // Specifically remove Helevtica Neue from BS3 stack. - font-family: sans-serif; - /* Margin bottom by 2 times the footer height */ - margin-bottom: 85px; - padding-top: 60px; - font-size: 16px; -} - -#content { - padding-bottom: 60px; -} - -// Designed to be the last element in a section / nav -// Makes a little "puzzle piece" connector. -.trapezoid { - position: relative; - margin-left: 60px; - border-top: $trapezoid-height solid; - border-left: 9px solid transparent; - border-right: 9px solid transparent; - height: 0; - width: 64px; - margin-top: -1 * $trapezoid-height; - top: $trapezoid-height; - z-index: 1000; -} - -#footer { - position: absolute; - bottom: 0; - width: 100%; - background-color: #f5f5f5; - .container { - padding: 15px; - } -} - -.nav-tabs { - margin-bottom: 15px; -} - -.img-center { - margin: 0 auto; -} - -/* centered columns styles */ -.row-centered { - text-align:center; -} -.col-centered { - display:inline-block; - float:none; - /* reset the text-align */ - text-align:left; - /* inline-block space fix */ - margin-right:-4px; -} - -.col-top { - vertical-align:top; -} - -fieldset { - margin: 20px 0 20px 0; -} - -.bootstrap-switch { - height: 1.7em -} - -.comment-reply{ - padding-top: 40px; -} - -p.comment-body { - padding-top: 20px; -} - -.well.comment-section { - padding-bottom: 40px; -} - -#account-already { - font-size: 0.6em; -} - -/* comments views pane: use padding-bottom before next comment box */ -.panel.panel-default .panel-body .notifications { - padding-bottom: 20px; -} - -/* comments views pane: use padding-bottom after each comment */ -.panel.panel-default .panel-body .notifications p { - padding-bottom: 20px; -} - -#proposal-info div dt, #proposal-info div dd { - display: inline-block; -} - -.changeset{ - display: none; -} - -.box{ - height: 230px; -} - -/* sidebar hamburger btn */ -.side-nav-btn{ - margin-left: 10px; - float: left; - } - -.qr-image{ - margin-left: 120px; -} - -.g-recaptcha { - @include clearfix; - padding-bottom: 12px; - - div { - float: right; - } -} - -/* omniauth btn grp */ -#openid-btn-grp{ - display: flex; - justify-content: center; -} - -.word_break { - word-wrap: break-word; -} diff --git a/app/assets/stylesheets/strap-on.scss b/app/assets/stylesheets/strap-on.scss deleted file mode 100644 index bd2d3b38b..000000000 --- a/app/assets/stylesheets/strap-on.scss +++ /dev/null @@ -1,60 +0,0 @@ -// Place bootstrap variable customizations in the file below. -@import "osem-variables"; -@import 'bootstrap-datetimepicker'; -@import "bootstrap-sprockets"; - -// -// Bootstrap components -// -// Core variables and mixins -@import "bootstrap/variables"; -@import "bootstrap/mixins"; - -// Reset and dependencies -@import "bootstrap/normalize"; -@import "bootstrap/print"; -@import "bootstrap/glyphicons"; - -// Core CSS -@import "bootstrap/scaffolding"; -@import "bootstrap/type"; -@import "bootstrap/code"; -@import "bootstrap/grid"; -@import "bootstrap/tables"; -@import "bootstrap/forms"; -@import "bootstrap/buttons"; - -// Components -@import "bootstrap/component-animations"; -@import "bootstrap/dropdowns"; -@import "bootstrap/button-groups"; -@import "bootstrap/input-groups"; -@import "bootstrap/navs"; -@import "bootstrap/navbar"; -@import "bootstrap/breadcrumbs"; -@import "bootstrap/pagination"; -@import "bootstrap/pager"; -@import "bootstrap/labels"; -@import "bootstrap/badges"; -@import "bootstrap/jumbotron"; -@import "bootstrap/thumbnails"; -@import "bootstrap/alerts"; -@import "bootstrap/progress-bars"; -@import "bootstrap/media"; -@import "bootstrap/list-group"; -@import "bootstrap/panels"; -@import "bootstrap/responsive-embed"; -@import "bootstrap/wells"; -@import "bootstrap/close"; - -// Components w/ JavaScript -@import "bootstrap/modals"; -@import "bootstrap/tooltip"; -@import "bootstrap/popovers"; - -// Utility classes -@import "bootstrap/utilities"; -@import "bootstrap/responsive-utilities"; - -// semantic classes -@import "osem-datatables"; diff --git a/app/helpers/admin/cfps_helper.rb b/app/helpers/admin/cfps_helper.rb deleted file mode 100644 index 7b62fc20d..000000000 --- a/app/helpers/admin/cfps_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Admin - module CfpsHelper - def cfp_form_url(cfp, conference) - if cfp.new_record? - admin_conference_program_cfps_path - else - admin_conference_program_cfp_path(conference, cfp) - end - end - end -end diff --git a/app/helpers/admin/volunteers_helper.rb b/app/helpers/admin/volunteers_helper.rb deleted file mode 100644 index cf0ed7129..000000000 --- a/app/helpers/admin/volunteers_helper.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Admin - module VolunteersHelper - def can_manage_volunteers?(conference) - current_user.has_cached_role?(:organizer, - conference) || current_user.has_cached_role?(:volunteers_coordinator, conference) - end - end -end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb deleted file mode 100644 index de02aed90..000000000 --- a/app/helpers/application_helper.rb +++ /dev/null @@ -1,209 +0,0 @@ -# frozen_string_literal: true - -DEFAULT_LOGO = Rails.configuration.conference[:default_logo_filename] - -# TODO-SNAPCON: Refactor this module. Move chunks to a dates_help, some events_helper -module ApplicationHelper - include Pagy::Frontend - # Returns a string build from the start and end date of the given conference. - # - # If the conference is only one day long - # * %B %d %Y (January 17 2014) - # If the conference starts and ends in the same month and year - # * %B %d - %d, %Y (January 17 - 21 2014) - # If the conference ends in another month but in the same year - # * %B %d - %B %d, %Y (January 31 - February 02 2014) - # All other cases - # * %B %d, %Y - %B %d, %Y (December 30, 2013 - January 02, 2014) - def date_string(start_date, end_date) - startstr = 'Unknown - ' - endstr = 'Unknown' - # When the conference is in the same month - if start_date.month == end_date.month && start_date.year == end_date.year - if start_date.day == end_date.day - startstr = start_date.strftime('%B %d') - endstr = end_date.strftime(' %Y') - else - startstr = start_date.strftime('%B %d - ') - endstr = end_date.strftime('%d, %Y') - end - elsif start_date.month != end_date.month && start_date.year == end_date.year - startstr = start_date.strftime('%B %d - ') - endstr = end_date.strftime('%B %d, %Y') - else - startstr = start_date.strftime('%B %d, %Y - ') - endstr = end_date.strftime('%B %d, %Y') - end - - startstr + endstr - end - - # Returns time with conference timezone - def time_with_timezone(time) - time.strftime('%F %R') + ' ' + @conference.timezone.to_s - end - - # Set resource_name for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposals#new) - def resource_name - :user - end - - def add_association_link(association_name, form_builder, div_class, html_options = {}) - link_to_add_association 'Add ' + association_name.to_s.singularize, form_builder, div_class, - html_options.merge(class: 'assoc btn btn-success') - end - - def remove_association_link(association_name, form_builder) - link_to_remove_association('Remove ' + association_name.to_s.singularize, form_builder, - class: 'assoc btn btn-danger') + tag.hr - end - - def dynamic_association(association_name, title, form_builder, options = {}) - render 'shared/dynamic_association', association_name: association_name, title: title, f: form_builder, -hint: options[:hint] - end - - def tracks(conference) - conference.confirmed_tracks.collect(&:name).to_sentence - end - - def difficulty_levels(conference) - conference.program.difficulty_levels.map(&:title).to_sentence - end - - def unread_notifications(user) - Comment.accessible_by(current_ability).find_since_last_login(user) - end - - # Receives a PaperTrail::Version object - # Outputs the list of attributes that were changed in the version (ignoring changes from one blank value to another) - # Eg: If version.changeset = '{"title"=>[nil, "Premium"], "description"=>[nil, "Premium = Super cool"], "conference_id"=>[nil, 3]}' - # Output will be 'title, description and conference' - def updated_attributes(version) - version.changeset - .reject { |_, values| values[0].blank? && values[1].blank? } - .keys.map { |key| key.gsub('_id', '').tr('_', ' ') }.join(', ') - .reverse.sub(',', ' dna ').reverse - end - - def normalize_array_length(hashmap, length) - hashmap.each_value do |value| - value.fill(value[-1], value.length...length) if value.length < length - end - end - - # TODO: Move to the event model. - def concurrent_events(event) - return nil unless event.scheduled? && event.program.selected_event_schedules - - event_schedule = event.program.selected_event_schedules.find { |es| es.event == event } - other_event_schedules = event.program.selected_event_schedules.reject do |other_event_schedule| - other_event_schedule == event_schedule - end - concurrent_events = [] - - event_time_range = (event_schedule.start_time.strftime '%Y-%m-%d %H:%M')...(event_schedule.end_time.strftime '%Y-%m-%d %H:%M') - other_event_schedules.each do |other_event_schedule| - next unless other_event_schedule.event.confirmed? - - other_event_time_range = (other_event_schedule.start_time.strftime '%Y-%m-%d %H:%M')...(other_event_schedule.end_time.strftime '%Y-%m-%d %H:%M') - concurrent_events << other_event_schedule.event if (event_time_range.to_a & other_event_time_range.to_a).present? - end - concurrent_events.sort_by { |schedule| schedule.room&.order } - end - - def speaker_links(event) - safe_join(event.speakers.map { |speaker| link_to speaker.name, admin_user_path(speaker) }, ',') - end - - def volunteer_links(event) - safe_join(event.volunteers.map do |volunteer| - link_to(volunteer.name, admin_user_path(volunteer)) - end, ', ') - end - - def event_types_sentence(conference) - conference.event_types.map { |et| et.title.pluralize }.to_sentence - end - - def sign_in_path - if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' - new_user_ichain_session_path - else - new_user_session_path - end - end - - def rescheduling_hint(affected_event_count) - if affected_event_count > 0 - "You have #{affected_event_count} scheduled #{'event'.pluralize(affected_event_count)}. Changing the conference hours will unschedule those scheduled outside the conference hours." - end - end - - ## - # ====Gets - # a conference object - # ==== Returns - # class hidden if conference is over - def hidden_if_conference_over(conference) - 'hidden' if Date.today > conference.end_date - end - - # TODO-SNAPCON: Replace this with a search for a conference logo. - # TODO: If conference is defined, the alt text should be conference name. - def nav_root_link_for(conference = nil) - path = conference&.id.present? ? conference_path(conference) : root_path - link_to( - image_tag(conference_logo_url(conference), alt: nav_link_text(conference)), - path, - class: 'navbar-brand', - title: nav_link_text(conference) - ) - end - - # TODO-SNAPCON: This should be the conference title. - def nav_link_text(conference = nil) - conference.try(:organization).try(:name) || ENV.fetch('OSEM_NAME', 'OSEM') - end - - # TODO: Consider Renaming this? - # TODO: Allow passing in an organization - def conference_logo_url(conference = nil) - return DEFAULT_LOGO unless conference - - if conference.picture.present? - conference.picture.thumb.url - elsif conference.organization&.picture.present? - conference.organization.picture.thumb.url - else - DEFAULT_LOGO - end - end - - # returns the url to be used for logo on basis of sponsorship level position - def get_logo(object) - if object.try(:sponsorship_level) - if object.sponsorship_level.position == 1 - object.picture.first.url - elsif object.sponsorship_level.position == 2 - object.picture.second.url - else - object.picture.others.url - end - else - object.picture.large.url - end - end - - # Embed links with a localized timezone URL - # Timestamps are stored at UTC but in the real timezone. - # We must convert then shift the time back to get the correct value. - # TODO: just take in an object? - def inyourtz(time, timezone, &block) - time = time.in_time_zone(timezone) - time -= time.utc_offset - link_to "https://inyourtime.zone/t?#{time.to_i}", target: '_blank', rel: 'noopener' do - block.call - end - end -end diff --git a/app/helpers/chart_helper.rb b/app/helpers/chart_helper.rb deleted file mode 100644 index ac350dceb..000000000 --- a/app/helpers/chart_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module ChartHelper - def chart_values(distribution_hash) - distribution_hash.collect do |key, data| - [key, data['value']] - end.to_h - end - - def chart_colors(distribution_hash) - distribution_hash.collect do |_key, data| - data['color'] - end - end -end diff --git a/app/helpers/conference_helper.rb b/app/helpers/conference_helper.rb deleted file mode 100644 index d393b6099..000000000 --- a/app/helpers/conference_helper.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -module ConferenceHelper - # Return true if only call_for_papers or call_for_tracks or call_for_booths is open - def one_call_open(*calls) - calls.one? { |call| call.try(:open?) } - end - # Return true if exactly two of those calls are open: call_for_papers , call_for_tracks , call_for_booths - - def two_calls_open(*calls) - calls.count { |call| call.try(:open?) } == 2 - end - - # URL for sponsorship emails - def sponsorship_mailto(conference) - [ - 'mailto:', - conference.contact.sponsor_email, - '?subject=', - url_encode(conference.short_title), - '%20Sponsorship' - ].join - end - - def short_ticket_description(ticket) - return unless ticket.description - - markdown(ticket.description.split("\n").first&.strip) - end - - def conference_color(conference) - conference.color.presence || Rails.configuration.conference[:default_color] - end - - # adds events to icalendar for proposals in a conference - def icalendar_proposals(calendar, proposals, conference) - proposals.each do |proposal| - calendar.event do |e| - e.dtstart = proposal.time - e.dtend = proposal.time + (proposal.event_type.length * 60) - e.duration = "PT#{proposal.event_type.length}M" - e.created = proposal.created_at - e.last_modified = proposal.updated_at - e.summary = proposal.title - e.description = proposal.abstract - e.uid = proposal.guid - e.url = conference_program_proposal_url(conference.short_title, proposal.id) - v = conference.venue - if v - e.geo = v.latitude, v.longitude if v.latitude && v.longitude - location = '' - location += "#{proposal.room.name} - " if proposal.room.name - location += " - #{v.street}, " if v.street - location += "#{v.postalcode} #{v.city}, " if v.postalcode && v.city - location += "#{v.country_name}, " if v.country_name - e.location = location - end - e.categories = conference.title, "Difficulty: #{proposal.difficulty_level.title}", - "Track: #{proposal.track.name}" - end - end - calendar - end - - def get_happening_now_events_schedules(conference) - events_schedules = filter_events_schedules(conference, :happening_now?) - events_schedules ||= [] - events_schedules - end - - def get_happening_next_events_schedules(conference) - events_schedules = filter_events_schedules(conference, :happening_later?) - - return [] if events_schedules.empty? - - # events_schedules have been sorted by start_time in selected_event_schedules - happening_next_time = events_schedules[0].start_time - events_schedules.select { |s| s.start_time == happening_next_time } - end - - def load_happening_now - events_schedules_list = get_happening_now_events_schedules(@conference) - @is_happening_next = false - if events_schedules_list.empty? - events_schedules_list = get_happening_next_events_schedules(@conference) - @is_happening_next = true - end - @events_schedules_limit = Rails.configuration.conference[:events_per_page] - @events_schedules_length = events_schedules_list.length - @pagy, @events_schedules = pagy_array(events_schedules_list, - items: @events_schedules_limit, - link_extra: 'data-remote="true"') - end - - private - - # TODO: Move this to using the cached method on program/schedule - def filter_events_schedules(conference, filter) - conference.program.selected_event_schedules( - includes: [:event, :room, { event: - %i[event_type speakers speaker_event_users track program] }] - ).select(&filter) - end -end diff --git a/app/helpers/date_time_helper.rb b/app/helpers/date_time_helper.rb deleted file mode 100644 index 420b086a8..000000000 --- a/app/helpers/date_time_helper.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module DateTimeHelper - ## - # Includes functions related to date or time manipulations - ## - ## - # Gets an EventType object, and returns its length in timestamp format (HH:MM) - # ====Gets - # * +Integer+ -> 30 - # ====Returns - # * +String+ -> "00:30" - def length_timestamp(length) - [length / 60, length % 60].map { |t| t.to_s.rjust(2, '0') }.join(':') - end - - ## - # Gets a datetime object - # ====Returns - # * +String+ -> formated datetime object - def format_datetime(obj) - return unless obj - - obj.strftime('%Y-%m-%d %H:%M') - end - - def show_time(length) - return '0 h 0 min' if length.blank? - - h, min = length.divmod(60) - - if h == 0 - "#{min.round} min" - elsif min == 0 - "#{h} h" - else - "#{h} h #{min.round} min" - end - end -end diff --git a/app/helpers/event_types_helper.rb b/app/helpers/event_types_helper.rb deleted file mode 100644 index f6e4abc11..000000000 --- a/app/helpers/event_types_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module EventTypesHelper - ## - # Includes functions related to event_types - ## - ## - # ====Returns - # * +String+ -> number of registrations / max allowed registrations - def event_type_select_options(event_types = {}) - event_types.map do |type| - [ - "#{type.title} - #{show_time(type.length)}", - type.id, - { data: { - min_words: type.minimum_abstract_length, - max_words: type.maximum_abstract_length, - instructions: type.submission_instructions - } } - ] - end - end -end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb deleted file mode 100644 index 921edf50e..000000000 --- a/app/helpers/events_helper.rb +++ /dev/null @@ -1,324 +0,0 @@ -# frozen_string_literal: true - -# TODO: Split this module into smaller modules -# rubocop:disable Metrics/ModuleLength -module EventsHelper - ## - # Includes functions related to events - ## - ## - # ====Returns - # * +String+ -> number of registrations / max allowed registrations - def registered_text(event) - return "Registered: #{event.registrations.count}/#{event.max_attendees}" if event.max_attendees - - "Registered: #{event.registrations.count}" - end - - # TODO-SNAPCON: Move to admin helper - def rating_stars(rating, max, options = {}) - Array.new(max) do |counter| - content_tag( - 'label', - '', - class: "rating#{' bright' if rating.to_f > counter}", - **options - ) - end.join.html_safe - end - - # TODO-SNAPCON: Move to admin helper - def rating_fraction(rating, max, options = {}) - content_tag('span', "#{rating}/#{max}", **options) - end - - def replacement_event_notice(event_schedule, styles: '') - if event_schedule.present? && event_schedule.replacement?(@withdrawn_event_schedules) - replaced_event = event_schedule.replaced_event_schedule.try(:event) - content_tag :span do - concat content_tag :span, 'Please note that this event replaces ' - concat link_to replaced_event.title, - conference_program_proposal_path(@conference.short_title, replaced_event.id), style: styles - end - end - end - - def canceled_replacement_event_label(event, event_schedule, *label_classes) - if event.state == 'canceled' || event.state == 'withdrawn' - content_tag :span, 'CANCELED', class: (%w[label label-danger] + label_classes) - elsif event_schedule.present? && event_schedule.replacement?(@withdrawn_event_schedules) - content_tag :span, 'REPLACEMENT', class: (%w[label label-info] + label_classes) - end - end - - def rating_tooltip(event, max_rating) - "#{event.average_rating}/#{max_rating}, #{pluralize(event.voters.length, 'vote')}" - end - - # TODO-SNAPCON: Move to admin helper - def event_type_dropdown(event, event_types, conference_id) - selection = event.event_type.try(:title) || 'Event Type' - options = event_types.collect do |event_type| - [ - event_type.title, - admin_conference_program_event_path( - conference_id, - event, - event: { event_type_id: event_type.id } - ) - ] - end - active_dropdown(selection, options) - end - - # TODO-SNAPCON: Move to admin helper - def track_dropdown(event, tracks, conference_id) - selection = event.track.try(:name) || 'Track' - options = tracks.collect do |track| - [ - track.name, - admin_conference_program_event_path( - conference_id, - event, - event: { track_id: track.id } - ) - ] - end - active_dropdown(selection, options) - end - - # TODO-SNAPCON: Move to admin helper - def difficulty_dropdown(event, difficulties, conference_id) - selection = event.difficulty_level.try(:title) || 'Difficulty' - options = difficulties.collect do |difficulty| - [ - difficulty.title, - admin_conference_program_event_path( - conference_id, - event, - event: { difficulty_level_id: difficulty.id } - ) - ] - end - active_dropdown(selection, options) - end - - # TODO-SNAPCON: Move to admin helper - def state_dropdown(event, conference_id, email_settings) - selection = event.state.humanize - options = [] - if event.transition_possible? :accept - options << [ - 'Accept', - accept_admin_conference_program_event_path(conference_id, event) - ] - if email_settings.send_on_accepted? - options << [ - 'Accept (without email)', - accept_admin_conference_program_event_path( - conference_id, - event, - send_mail: false - ) - ] - end - end - if event.transition_possible? :reject - options << [ - 'Reject', - reject_admin_conference_program_event_path(conference_id, event) - ] - if email_settings.send_on_rejected? - options << [ - 'Reject (without email)', - reject_admin_conference_program_event_path( - conference_id, - event, - send_mail: false - ) - ] - end - end - if event.transition_possible? :restart - options << [ - 'Start review', - restart_admin_conference_program_event_path(conference_id, event) - ] - end - if event.transition_possible? :confirm - options << [ - 'Confirm', - confirm_admin_conference_program_event_path(conference_id, event) - ] - end - if event.transition_possible? :cancel - options << [ - 'Cancel', - cancel_admin_conference_program_event_path(conference_id, event) - ] - end - active_dropdown(selection, options) - end - - # TODO-SNAPCON: Move to admin helper - def event_switch_checkbox(event, attribute, conference_id) - check_box_tag( - conference_id, - event.id, - event.send(attribute), - url: admin_conference_program_event_path( - conference_id, - event, - event: { attribute => nil } - ), - class: 'switch-checkbox' - ) - end - - def event_favourited?(event, current_user) - return false unless current_user - - event.favourite_users.exists?(current_user.id) - end - - # TODO-SNAPCON: These need to be refactored. - # It's not clear which should be an object vs when to use a tz string. - def display_timezone(user, conference) - return conference.timezone unless user - - user.timezone.presence || conference.timezone - end - - def timezone_offset(object) - Time.now.in_time_zone(object.timezone).utc_offset / 1.hour - end - - def timezone_text(object) - Time.now.in_time_zone(object.timezone).strftime('%Z') - end - - # timezone: Eastern Time (US & Canada) (UTC -5) - def timezone_mapping(timezone) - return unless timezone - - offset = Time.now.in_time_zone(timezone).utc_offset / 1.hour - text = Time.now.in_time_zone(timezone).strftime('%Z') - "#{text} (UTC #{offset})" - end - - def convert_timezone(date, old_timezone, new_timezone) - if date && old_timezone && new_timezone - date.strftime('%Y-%m-%dT%H:%M:%S').in_time_zone(old_timezone).in_time_zone(new_timezone) - end - end - - def join_event_link(event, event_schedule, current_user, small: false) - return unless current_user && event_schedule && event_schedule.room_url.present? - return if event.ended? - - conference = event.conference - is_now = event_schedule.happening_now? # 30 minute threshold. - is_registered = conference.user_registered?(current_user) - admin = current_user.roles.where(id: conference.roles).any? - # is_presenter = event.speakers.include?(current_user) || event.volunteers.include?(current_user) - - if admin || (is_now && is_registered) - link_to("Join Event Now #{'(Early)' unless is_now}", - join_conference_program_proposal_path(conference, event), - target: '_blank', class: "btn btn-primary #{'btn-xs' if small}", - 'aria-label': "Join #{event.title}", rel: 'noopener') - elsif is_registered - content_tag :span, class: 'btn btn-default btn-xs disabled' do - 'Click to Join During Event' - end - else - link_to('Register for the conference to join this event.', - conference_conference_registration_path(conference), - class: 'btn btn-default btn-xs', - 'aria-label': "Register for #{event.title}") - end - end - - def calendar_timestamp(timestamp, _timezone) - timestamp = timestamp.in_time_zone('GMT') - timestamp -= timestamp.utc_offset - timestamp.strftime('%Y%m%dT%H%M%S') - end - - def google_calendar_link(event_schedule) - event = event_schedule.event - conference = event.conference - calendar_base = 'https://www.google.com/calendar/render' - start_timestamp = calendar_timestamp(event_schedule.start_time, conference.timezone) - end_timestamp = calendar_timestamp(event_schedule.end_time, conference.timezone) - event_details = { - action: 'TEMPLATE', - text: "#{event.title} at #{conference.title}", - details: calendar_event_text(event, event_schedule, conference), - location: "#{event_schedule.room.name} #{event_schedule.room_url}", - dates: "#{start_timestamp}/#{end_timestamp}", - ctz: event_schedule.timezone - } - "#{calendar_base}?#{event_details.to_param}" - end - - def css_background_color(color) - "background-color: #{color}; color: #{contrast_color(color)};" - end - - def user_options_for_dropdown(event, column) - users = event.send(column).pluck(:id, :name, :username, :email) - options_for_select(users.map { |u| ["#{u[1]} (#{u[2]} #{u[3]})", u[0]] }, users.map(&:first)) - end - - def committee_only_actions(user, conference, roles: %i[organizer cfp], &block) - role_map = roles.map { |role| { name: role, resource: conference } } - return unless user&.has_any_role?(:admin, *role_map) - - content_tag(:div, class: 'panel panel-info') do - concat content_tag(:div, 'Conference Organizers', class: 'panel-heading') - concat content_tag(:div, capture(&block), class: 'panel-body') - end - end - - private - - def calendar_event_text(event, event_schedule, conference) - <<~TEXT - #{conference.title} - #{event.title} - #{event_schedule.start_time.strftime('%Y %B %e - %H:%M')} #{event_schedule.timezone} - - More Info: #{conference_program_proposal_url(conference, event)} - Join: #{event.url} - - #{truncate(event.abstract, length: 200)} - TEXT - end - - def active_dropdown(selection, options) - # Consistent rendering of dropdown lists that submit patched changes - # - # Selection is the string to show by default, which is clicked to expose the - # dropdown options. - # Options is a list of 2-item lists; for each entry: - # * [0] is the text of the option, - # * [1] is the link url for the options - content_tag('div', class: 'dropdown') do - content_tag( - 'a', - class: 'dropdown-toggle', - href: '#', - data: { toggle: 'dropdown' } - ) do - content_tag('span', selection) + - content_tag('span', '', class: 'caret') - end + - content_tag('ul', class: 'dropdown-menu') do - options.collect do |option| - content_tag('li', link_to(option[0], option[1], method: :patch)) - end.join.html_safe - end - end - end -end -# rubocop:enable Metrics/ModuleLength diff --git a/app/helpers/format_helper.rb b/app/helpers/format_helper.rb deleted file mode 100644 index 4f320682d..000000000 --- a/app/helpers/format_helper.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -require 'redcarpet/render_strip' - -module FormatHelper - ## - # Includes functions related to formatting (like adding classes, colors) - ## - def status_icon(object) - case object.state - when 'new', 'to_reject', 'to_accept' - 'fa-eye' - when 'unconfirmed', 'accepted' - 'fa-check text-muted' - when 'confirmed' - 'fa-check text-success' - when 'rejected', 'withdrawn', 'canceled' - 'fa-ban' - end - end - - def event_progress_color(progress) - progress = progress.to_i - if progress == 100 - 'progress-bar-success' - elsif progress >= 85 - 'progress-bar-info' - elsif progress >= 71 - 'progress-bar-warning' - else - 'progress-bar-danger' - end - end - - def variant_from_delta(delta, reverse: false) - if delta.to_i.positive? - reverse ? 'warning' : 'success' - elsif delta.to_i.negative? - reverse ? 'success' : 'warning' - else - 'info' - end - end - - def target_progress_color(progress) - progress = progress.to_i - if progress >= 90 - 'green' - elsif progress < 90 && progress >= 80 - 'orange' - else - 'red' - end - end - - def days_left_color(days_left) - days_left = days_left.to_i - if days_left > 30 - 'green' - elsif days_left < 30 && days_left > 10 - 'orange' - else - 'red' - end - end - - def bootstrap_class_for(flash_type) - case flash_type - when 'success' - 'alert-success' - when 'error' - 'alert-danger' - when 'alert' - 'alert-warning' - when 'notice' - 'alert-info' - else - 'alert-warning' - end - end - - def label_for(event_state) - result = '' - case event_state - when 'new' - result = 'label label-primary' - when 'withdrawn' - result = 'label label-danger' - when 'unconfirmed' - result = 'label label-success' - when 'confirmed' - result = 'label label-success' - when 'rejected' - result = 'label label-warning' - when 'canceled' - result = 'label label-danger' - end - result - end - - def icon_for_todo(bool) - if bool - 'fa-solid fa-check' - else - 'fa-solid fa-xmark' - end - end - - def class_for_todo(bool) - bool ? 'todolist-ok' : 'todolist-missing' - end - - def word_pluralize(count, singular, plural = nil) - if count == 1 || count =~ /^1(\.0+)?$/ - singular - else - plural || singular.pluralize - end - end - - # Returns black or white deppending on what of them contrast more with the - # given color. Useful to print text in a coloured background. - # hexcolor is a hex color of 7 characters, being the first one '#'. - # Reference: https://24ways.org/2010/calculating-color-contrast - def contrast_color(hexcolor) - r = hexcolor[1..2].to_i(16) - g = hexcolor[3..4].to_i(16) - b = hexcolor[5..6].to_i(16) - yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000 - yiq >= 128 ? 'black' : 'white' - end - - def td_height(rooms) - td_height = 500 / rooms.length - # we want all least 3 lines in events and td padding = 3px, speaker picture height >= 25px - # and line-height = 17px => (17 * 3) + 6 + 25 = 82 - td_height < 82 ? 82 : td_height - end - - def room_height(rooms) - room_lines(rooms) * 17 - end - - def room_lines(rooms) - # line-height = 17px, td padding = 3px - (td_height(rooms) - 6) / 17 - end - - def event_height(rooms) - event_lines(rooms) * 17 - end - - def event_lines(rooms) - # line-height = 17px, td padding = 3px, speaker picture height >= 25px - (td_height(rooms) - 31) / 17 - end - - def speaker_height(rooms) - # td padding = 3px - speaker_height = td_height(rooms) - 6 - event_height(rooms) - # The speaker picture is a circle and the width must be <= 37 to avoid making the cell widther - speaker_height >= 37 ? 37 : speaker_height - end - - def speaker_width(rooms) - # speaker picture padding: 4px 2px; and we want the picture to be a circle - speaker_height(rooms) - 4 - end - - def selected_scheduled?(schedule) - schedule == @selected_schedule ? 'Yes' : 'No' - end - - def markdown(text, escape_html = true) - return '' if text.nil? - - markdown_options = { - autolink: true, - space_after_headers: true, - # no_intra_emphasis: true, # SNAPCON - fenced_code_blocks: true, - disable_indented_code_blocks: true, - tables: true, # SNAPCON - strikethrough: true, # SNAPCON - footnotes: true, # SNAPCON - superscript: true # SNAPCON - } - render_options = { - escape_html: escape_html, - safe_links_only: true - } - markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(render_options), markdown_options) - sanitize(markdown.render(text)) - end - - def markdown_hint(text = '') - markdown( - "#{text} Please look at #{link_to '**Markdown Syntax**', 'https://daringfireball.net/projects/markdown/syntax', - target: '_blank', rel: 'noopener'} to format your text", false - ) - end - - # Return a plain text markdown stripped of formatting. - def plain_text(content) - Redcarpet::Markdown.new(Redcarpet::Render::StripDown).render(content) - end - - def quantity_left_of(resource) - return '-/-' if resource.quantity.blank? - - "#{resource.quantity - resource.used}/#{resource.quantity}" - end -end diff --git a/app/helpers/paths_helper.rb b/app/helpers/paths_helper.rb deleted file mode 100644 index cfedc2bd1..000000000 --- a/app/helpers/paths_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module PathsHelper - ## - # Includes functions related to links or redirects - ## - - def active_nav_li(link) - if current_page?(link) - 'active' - else - '' - end - end -end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb deleted file mode 100644 index 11c455c2b..000000000 --- a/app/helpers/users_helper.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module UsersHelper - ## - # Includes functions related to users - ## - # Set devise_mapping for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposals#new) - def devise_mapping - @devise_mapping ||= Devise.mappings[:user] - end - - def omniauth_configured - return Devise.omniauth_providers if OmniAuth.config.test_mode - - providers = [] - Devise.omniauth_providers.each do |provider| - provider_key = "#{provider}_key" - provider_secret = "#{provider}_secret" - unless Rails.application.secrets.send(provider_key).blank? || Rails.application.secrets.send(provider_secret).blank? - providers << provider - end - providers << provider if ENV.fetch("OSEM_#{provider.upcase}_KEY", - nil).present? && ENV.fetch("OSEM_#{provider.upcase}_SECRET", nil).present? - end - - providers.uniq - end - - # Receives a hash, generated from User model, function get_roles - # Outputs the roles of a user, including the conferences for which the user has the roles - # Eg. organizer(oSC13, oSC14), cfp(oSC12, oSC13) - def show_roles(roles) - roles.map { |x| x[0].titleize + ' (' + x[1].join(', ') + ')' }.join ', ' - end -end diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb deleted file mode 100644 index f3753c1a6..000000000 --- a/app/helpers/versions_helper.rb +++ /dev/null @@ -1,169 +0,0 @@ -# frozen_string_literal: true - -module VersionsHelper - ## - # Groups functions related to change description - ## - def link_if_alive(version, link_text, link_url, conference) - version.item && conference ? link_to(link_text, link_url) : "#{link_text} with ID #{version.item_id}" - end - - def link_to_organization(organization_id) - return 'deleted organization' unless organization_id - - org = Organization.find_by(id: organization_id) - return current_or_last_object_state('Organization', organization_id).try(:name) unless org - - org.name.to_s - end - - def link_to_conference(conference_id) - return 'deleted conference' if conference_id.nil? - - conference = Conference.find_by(id: conference_id) - if conference - link_to conference.short_title, - edit_admin_conference_path(conference.short_title) - else - short_title = current_or_last_object_state('Conference', conference_id).try(:short_title) || '' - " #{short_title} with ID #{conference_id}" - end - end - - def link_to_user(user_id) - return 'Someone (probably via the console)' unless user_id - - user = User.find_by(id: user_id) - if user - link_to user.name, admin_user_path(id: user_id) - else - name = current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset['name'].second if PaperTrail::Version.where(item_type: 'User', item_id: user_id).any? - "#{name || 'Unknown user'} with ID #{user_id}" - end - end - - # Receives a model_name and id - # Returns nil if model_name is invalid - # Returns object in its current state if its alive - # Otherwise Returns object state just before deletion - def current_or_last_object_state(model_name, id) - return nil unless id.present? && model_name.present? - - begin - object = model_name.constantize.find_by(id: id) - rescue NameError - return nil - end - - if object.nil? - object_last_version = PaperTrail::Version.where(item_type: model_name, item_id: id).last - object = object_last_version.reify if object_last_version - end - object - end - - def subscription_change_description(version) - user_id = current_or_last_object_state(version.item_type, version.item_id).user_id - user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second unless user_id.to_s == version.whodunnit - version.event == 'create' ? "subscribed #{user_name} to" : "unsubscribed #{user_name} from" - end - - def registration_change_description(version) - if version.item_type == 'Registration' - user_id = current_or_last_object_state(version.item_type, version.item_id).user_id - elsif version.item_type == 'EventsRegistration' - registration_id = current_or_last_object_state(version.item_type, version.item_id).registration_id - user_id = current_or_last_object_state('Registration', registration_id).user_id - end - user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second - - if user_id.to_s == version.whodunnit - case version.event - when 'create' then 'registered to' - when 'update' then "updated #{updated_attributes(version)} of the registration for" - when 'destroy' then 'unregistered from' - end - else - case version.event - when 'create' then "registered #{user_name} to" - when 'update' then "updated #{updated_attributes(version)} of #{user_name}'s registration for" - when 'destroy' then "unregistered #{user_name} from" - end - end - end - - def comment_change_description(version) - user = current_or_last_object_state(version.item_type, version.item_id).user - if version.event == 'create' - version.previous.nil? ? 'commented on' : "re-added #{user.name}'s comment on" - else - "deleted #{user.name}'s comment on" - end - end - - def vote_change_description(version) - user = current_or_last_object_state(version.item_type, version.item_id).user - if version.event == 'create' - version.previous.nil? ? 'voted on' : "re-added #{user.name}'s vote on" - elsif version.event == 'update' - "updated #{user.name}'s vote on" - else - "deleted #{user.name}'s vote on" - end - end - - def general_change_description(version) - if version.event == 'create' - 'created new' - elsif version.event == 'update' - "updated #{updated_attributes(version)} of" - else - 'deleted' - end - end - - def event_change_description(version) - if version.event == 'create' - 'submitted new' - - elsif version.changeset['state'] - case version.changeset['state'][1] - when 'unconfirmed' then 'accepted' - when 'withdrawn' then 'withdrew' - when 'canceled', 'rejected', 'confirmed' then version.changeset['state'][1] - when 'new' then 'resubmitted' - end - - else - "updated #{updated_attributes(version)} of" - end - end - - def event_schedule_change_description(version) - case version.event - when 'create' then 'scheduled' - when 'update' then 'rescheduled' - when 'destroy' then 'unscheduled' - end - end - - def user_change_description(version) - if version.event == 'create' - link_to_user(version.item_id) + ' signed up' - elsif version.event == 'update' - if version.changeset.keys.include?('reset_password_sent_at') - 'Someone requested password reset of' - elsif version.changeset.keys.include?('confirmed_at') && version.changeset['confirmed_at'][0].nil? - (version.whodunnit.nil? ? link_to_user(version.item_id) : link_to_user(version.whodunnit)) + ' confirmed account of' - elsif version.changeset.keys.include?('confirmed_at') && version.changeset['confirmed_at'][1].nil? - link_to_user(version.whodunnit) + ' unconfirmed account of' - else - link_to_user(version.whodunnit) + " updated #{updated_attributes(version)} of" - end - end - end - - def users_role_change_description(version) - version.event == 'create' ? 'added' : 'removed' - end -end diff --git a/app/services/full_calendar_formatter.rb b/app/services/full_calendar_formatter.rb deleted file mode 100644 index 7f9a21594..000000000 --- a/app/services/full_calendar_formatter.rb +++ /dev/null @@ -1,49 +0,0 @@ -class FullCalendarFormatter - def self.rooms_to_resources(rooms) - rooms.map { |room| room_to_resource(room) }.to_json - end - - def self.event_schedules_to_resources(event_schedules) - return '[]' if event_schedules.empty? - - conference = event_schedules.first.schedule.program.conference - event_schedules.map { |event_schedule| event_schedule_to_resource(conference, event_schedule) }.to_json - end - - class << self - include FormatHelper - - private - - def room_to_resource(room) - { - id: room.guid, - title: room.name, - order: room.order - } - end - - def event_schedule_to_resource(conference, event_schedule) - event = event_schedule.event - event_type_color = event.event_type.color - url = Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event.id) - background_event = event.event_type.title.match(/Break/i) - rooms = background_event ? conference.venue.rooms.pluck(:guid) : [event_schedule.room.guid] - - { - id: event.guid, - title: event.title, - start: event_schedule.start_time_in_conference_timezone, - end: event_schedule.end_time_in_conference_timezone, - resourceIds: rooms, - url: url, - borderColor: event_type_color, - backgroundColor: event_type_color, - textColor: contrast_color(event_type_color), - className: "fc-event-track-#{event.track&.short_name || 'none'}", - display: background_event ? 'background' : 'auto', - has_parent: event.parent_event.present? - } - end - end -end diff --git a/app/services/mailbluster_manager.rb b/app/services/mailbluster_manager.rb deleted file mode 100644 index 2bb06f4e9..000000000 --- a/app/services/mailbluster_manager.rb +++ /dev/null @@ -1,40 +0,0 @@ -class MailblusterManager - include HTTParty - base_uri 'https://api.mailbluster.com/api/leads/' - @auth_headers = { - headers: { - 'Content-Type' => 'application/json', - 'Authorization' => ENV.fetch('MAILBLUSTER_API_KEY', nil) - } - } - - def self.query_api(method, path, body: {}) - options = @auth_headers.merge(body: body.to_json) - send(method, path, options).parsed_response - end - - def self.create_lead(user) - query_api(:post, '/', body: { - 'email' => user.email, - 'firstName' => user.name, - 'overrideExisting' => true, - 'subscribed' => true, - 'tags' => [ENV.fetch('OSEM_NAME', 'snapcon')] - }) - end - - def self.edit_lead(user, add_tags: [], remove_tags: [], old_email: nil) - email_hash = Digest::MD5.hexdigest(old_email.presence || user.email) - query_api(:put, "/#{email_hash}", body: { - 'email' => user.email, - 'firstName' => user.name, - 'addTags' => add_tags, - 'removeTags' => remove_tags - }) - end - - def self.delete_lead(email) - email_hash = Digest::MD5.hexdigest email - query_api(:delete, "/#{email_hash}") - end -end diff --git a/app/views/admin/booths/_all_booths.csv.haml b/app/views/admin/booths/_all_booths.csv.haml deleted file mode 100644 index 334918ac7..000000000 --- a/app/views/admin/booths/_all_booths.csv.haml +++ /dev/null @@ -1,19 +0,0 @@ -- headers = ["#{(t'booth').capitalize } ID", - 'Title', - 'Description', - 'Reasoning', - 'Submitter Name', - 'Submitter Relationship', - 'Website Url', - 'State'] -= CSV.generate_line ["All #{(t'booth').pluralize}"] -= CSV.generate_line headers -- @booths.each do |booth| - = CSV.generate_line([booth.id, - booth.title, - booth.description, - booth.reasoning, - booth.submitter.name, - booth.submitter_relationship, - booth.website_url, - booth.state]).html_safe diff --git a/app/views/admin/booths/_all_booths.pdf.prawn b/app/views/admin/booths/_all_booths.pdf.prawn deleted file mode 100644 index df034b794..000000000 --- a/app/views/admin/booths/_all_booths.pdf.prawn +++ /dev/null @@ -1,26 +0,0 @@ -prawn_document(force_download: true, filename: "#{@file_name}.pdf", page_layout: :landscape) do |pdf| - booths_array = [] - header_array = ["#{(t'booth').capitalize } ID", - 'Title', - 'Description', - 'Reasoning', - 'Submitter Name', - 'Submitter Relationship', - 'Website Url', - 'State'] - booths_array << header_array - @booths.each do |booth| - row = [] - row << booth.id - row << booth.title - row << booth.description - row << booth.reasoning - row << booth.submitter.name - row << booth.submitter_relationship - row << booth.website_url - row << booth.state - booths_array << row - end - pdf.text "#{@conference.short_title} #{(t'booth').pluralize}", font_size: 25, align: :center - pdf.table booths_array, header: true, cell_style: {size: 8, border_width: 1},column_widths: [45,70,153,152,70,80,105,45] -end diff --git a/app/views/admin/booths/_confirmed_booths.pdf.prawn b/app/views/admin/booths/_confirmed_booths.pdf.prawn deleted file mode 100644 index b3902ff7d..000000000 --- a/app/views/admin/booths/_confirmed_booths.pdf.prawn +++ /dev/null @@ -1,26 +0,0 @@ -prawn_document(force_download: true, filename: "#{@file_name}.pdf", page_layout: :landscape) do |pdf| - booths_array = [] - header_array = ["#{(t'booth').capitalize } ID", - 'Title', - 'Description', - 'Reasoning', - 'Submitter Name', - 'Submitter Relationship', - 'Website Url', - 'State'] - booths_array << header_array - @booths.confirmed.each do |booth| - row = [] - row << booth.id - row << booth.title - row << booth.description - row << booth.reasoning - row << booth.submitter.name - row << booth.submitter_relationship - row << booth.website_url - row << booth.state - booths_array << row - end - pdf.text "#{@conference.short_title} booths", font_size: 25, align: :center - pdf.table booths_array, header: true, cell_style: {size: 8, border_width: 1},column_widths: [45,70,153,152,70,80,105,45] -end diff --git a/app/views/admin/booths/booths.pdf.prawn b/app/views/admin/booths/booths.pdf.prawn deleted file mode 100644 index 5c8f9bd26..000000000 --- a/app/views/admin/booths/booths.pdf.prawn +++ /dev/null @@ -1,5 +0,0 @@ -if @booth_export_option == 'confirmed' - render 'confirmed_booths' -elsif @booth_export_option == 'all' - render 'all_booths' -end diff --git a/app/views/admin/comments/_posted_comments.html.haml b/app/views/admin/comments/_posted_comments.html.haml deleted file mode 100644 index d274f9292..000000000 --- a/app/views/admin/comments/_posted_comments.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- @posted_comments.each do |conference, events| - .panel.panel-default - .panel-heading - %h4.title.panel-title= conference.title - .panel-body - - events.each do |event, comments| - .notifications - %h4.title= link_to event.title, admin_conference_program_event_path(event.program.conference.short_title, event) - %hr - - comments.each do |comment| - %h5.strong Created at: #{comment.created_at} - %p= comment.body diff --git a/app/views/admin/comments/_unread_comments.html.haml b/app/views/admin/comments/_unread_comments.html.haml deleted file mode 100644 index 5facdc4ba..000000000 --- a/app/views/admin/comments/_unread_comments.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- @unread_comments.each do |conference, events| - .panel.panel-default - .panel-heading - %h4.title.panel-title= conference.title - .panel-body - - events.each do |event, comments| - .notifications - %h4.title= link_to event.title, admin_conference_program_event_path(event.program.conference.short_title, event) - %hr - - comments.each do |comment| - %h5.strong Posted by: #{comment.user.name} | Created at: #{comment.created_at} - %p= comment.body diff --git a/app/views/admin/conferences/_form_fields.html.haml b/app/views/admin/conferences/_form_fields.html.haml deleted file mode 100644 index b16692aa8..000000000 --- a/app/views/admin/conferences/_form_fields.html.haml +++ /dev/null @@ -1,99 +0,0 @@ -%h4 Basic Information -%hr -- if f.object.new_record? - .form-group - = f.label :organization, "Organization" - = f.select :organization_id, Organization.accessible_by(current_ability, :update).pluck(:name, :id) -.form-group - = f.label :title - %abbr{title: 'This field is required'} * - = f.text_field :title, required: true, class: 'form-control', placeholder: 'Title' - %span.help-block - The name of your conference as it shall appear throughout the site. Example: 'openSUSE Conference 2013' -.form-group - = f.label :short_title - %abbr{title: 'This field is required'} * - = f.text_field :short_title, required: true, pattern: '[a-zA-Z0-9_-]+', title: 'Only letters, numbers, underscores, and dashes.', prepend: conferences_url + '/', class: 'form-control', placeholder: 'Short Title' - %span.help-block - A short and unique handle for your conference, using only letters, numbers, underscores, and dashes. This will be used to identify your conference in URLs etc. Example: - %em - froscon2011 -- unless f.object.new_record? # We are showing more fields on the edit form - .form-group - = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' - %span.help-block - = markdown_hint('A description of the conference.') - .form-group - = f.color_field :color, size: 6, class: 'form-control' - %span.help-block - The color will be used for the dashboard, for instance. - .form-group - = f.label :picture, 'Conference Logo' - - if f.object.picture? - = image_tag f.object.picture.thumb.url - = f.file_field :picture - %span.help-block - This will be shown in the navigation bar and emails. - .form-group - = f.label :custom_css, "Custom CSS" - = f.text_area :custom_css, rows: 10, class: 'form-control', html: { style: 'font-family: monospace' } - %span.help-block - Add custon CSS to all pages within the conference. The class - %code .conference-#{@conference.short_title} - is included on the body element. - .form-group - = f.label :ticket_layout - = 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'}")}. -%p.text-right - - if f.object.new_record? - = f.submit nil, { class: 'btn btn-success' } - - else - = f.submit nil, { class: 'btn btn-success', data: { confirm: 'Are you sure you want to proceed?' } } diff --git a/app/views/admin/conferences/_recent_registrations.html.haml b/app/views/admin/conferences/_recent_registrations.html.haml deleted file mode 100644 index 9b750c835..000000000 --- a/app/views/admin/conferences/_recent_registrations.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -- if recent_registrations && !recent_registrations.empty? - .table-responsive - %table.table - %thead - %tr - %th # - %th Public Name - %th Conference - %th Date - - recent_registrations.each_with_index do |registration, index| - %tbody - %tr - %td= index + 1 - %td= link_to registration.name, admin_user_path(registration.user.id) - %td= link_to registration.conference.title, admin_conference_path(registration.conference.short_title) - %td= registration.created_at.strftime('%m/%d/%Y') -- else - %h5.text-warning.text-center - No registrations! diff --git a/app/views/admin/conferences/_top_submitter.html.haml b/app/views/admin/conferences/_top_submitter.html.haml deleted file mode 100644 index b4f714fd5..000000000 --- a/app/views/admin/conferences/_top_submitter.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -.row - .col-md-12 - %h3 - Top Submitter - - if @top_submitter && !@top_submitter.empty? - - @top_submitter.each do |key, value| - .row.top-submitter - .col-md-2 - = image_tag(key.profile_picture(size: '25'), title: "Yo #{key.name}!", alt: '', 'class' => 'img-circle img-responsive text-center') - .col-md-10 - %h4 - = link_to key.name, admin_user_path(key) - %div - %small - = link_to pluralize(value, 'submission'), admin_user_path(key, tab: 'submissions-content') - - else - %h4.text-warning - No submissions! diff --git a/app/views/admin/contacts/edit.html.haml b/app/views/admin/contacts/edit.html.haml deleted file mode 100644 index 1a67342ca..000000000 --- a/app/views/admin/contacts/edit.html.haml +++ /dev/null @@ -1,58 +0,0 @@ -.row - .col-md-12 - .page-header - %h1 Contact - %p.text-muted - How people can contact you -.row - .col-md-8 - = form_for(@contact, url: admin_conference_contact_path(@conference.short_title), html: {multipart: true}) do |f| - %h4 - Mail - %hr - .form-group - = f.label :email - = f.email_field :email, autofocus: true, class: 'form-control' - %span.help-block - Contact email address for your conference. Will be used as reply-to address in emails sent out by the system. - = f.label :sponsor_email - = f.email_field :sponsor_email, class: 'form-control' - %span.help-block - This will appear in the sponsor segment of the splash for the sponsors to contact to the organizers. - %h4 - Social Media - %hr - .form-group - = f.label :social_tag - = f.text_field :social_tag, class: 'form-control' - %span.help-block - The hashtag you'll use on Twitter and Google+. Don't include the '#' sign! - = f.label :blog, 'Blog URL' - = f.url_field :blog, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to your conference blog. - = f.label :facebook, 'Facebook URL' - = f.url_field :facebook, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to the Facebook page of your Conference' - = f.label :googleplus, 'Google+ URL' - = f.url_field :googleplus, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to the Google+ Page of your Conference' - = f.label :twitter, 'Twitter URL' - = f.url_field :twitter, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to the Twitter Page of your Conference' - = f.label :instagram, 'Insta URL' - = f.url_field :instagram, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to the Instagram Page of your Conference - = f.label :mastodon, 'Mastodon URL' - = f.url_field :mastodon, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to the Mastodon Page of your Conference - = f.label :youtube, 'Youtube URL' - = f.url_field :youtube, class: 'form-control' - %span.help-block - This will appear in the social media section as a link to the YouTube channel for your conference. - = f.submit nil, class: 'btn btn-primary' diff --git a/app/views/admin/difficulty_levels/edit.html.haml b/app/views/admin/difficulty_levels/edit.html.haml deleted file mode 100644 index baec7d471..000000000 --- a/app/views/admin/difficulty_levels/edit.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -.row - .col-md-12 - .page-header - %h1 - Edit Difficulty Level - = @difficulty_level.title -.row - .col-md-8 - = render partial: 'form' diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml deleted file mode 100644 index 41a43825e..000000000 --- a/app/views/admin/emails/index.html.haml +++ /dev/null @@ -1,217 +0,0 @@ -.row - .col-md-8 - = form_for(@settings, url: admin_conference_email_path(@conference.short_title, @conference.email_settings), html: {multipart: true}) do |f| - .row - .col-md-12 - %div{ role: 'tabpanel' } - / Nav tabs - %ul.nav.nav-tabs{ role: 'tablist' } - %li.active{ role: 'presentation' } - %a{ 'aria-controls' => 'onboarding', 'data-toggle' => 'tab', href: '#onboarding', role: 'tab' } Onboarding - %li{ role: 'presentation' } - %a{ 'aria-controls' => 'proposal', 'data-toggle' => 'tab', href: '#proposal', role: 'tab' } Proposal - %li{ role: 'presentation' } - %a{ 'aria-controls' => 'notifications', 'data-toggle' => 'tab', href: '#notifications', role: 'tab' } Update Notifications - %li{ role: 'presentation' } - %a{ 'aria-controls' => 'cfp', 'data-toggle' => 'tab', href: '#cfp', role: 'tab' } Call for Papers - %li{ role: 'presentation' } - %a{ 'aria-controls' => 'booths', 'data-toggle' => 'tab', href: '#booth', role: 'tab' } Booth - / Tab panes - .tab-content - #onboarding.tab-pane.active{ role: 'tabpanel' } - .checkbox - %label - = f.check_box :send_on_registration, data: {name: 'email_settings_registration_subject'}, class: 'send_on_radio' - Send an email when the user registers for the conference? - .form-group - = f.label :registration_subject, 'Subject' - = f.text_field :registration_subject, class: 'form-control' - .form-group - = f.label :registration_body, 'Body' - = f.text_area :registration_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_registration_subject', 'data-subject-text' => 'Thank you for registering', - 'data-body-input-id' => 'email_settings_registration_body', - 'data-body-text' => "Dear {name},\n\nThank you for Registering for the conference {conference}.\nPlease complete your registration by filling out your travel information.\n\nIf you are unable to attend please unregister online:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\nWe look forward to see you there.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'registration_help' } Show Help - = render partial: 'help', locals: { id: 'registration_help', show_event_variables: false } - #proposal.tab-pane{ role: 'tabpanel' } - .checkbox - %label - = f.check_box :send_on_submitted_proposal, data: { name: 'email_settings_proposal_submited_subject'}, class: 'send_on_radio' - Send an email when the proposal is submitted? - .form-group - = f.label :submitted_proposal_subject, 'Subject' - = f.text_field :submitted_proposal_subject, class: 'form-control' - .form-group - = f.label :submitted_proposal_body, 'Body' - = f.text_area :submitted_proposal_body, input_html: { rows: 10, cols: 20 }, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_submitted_proposal_subject', 'data-subject-text' => 'Your proposal has been submitted successfully', 'data-body-input-id' => 'email_settings_submitted_proposal_body', 'data-body-text' => "Dear {name}\n\nThank you for submitting your proposal {eventtitle}.\n\nOur team will review it and get back to you as soon as possible.\n\nFeel free to contact us with any questions or concerns.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'submitted_proposal_help' } Show Help - = render partial: 'help', locals: { id: 'submitted_proposal_help', show_event_variables: true } - .checkbox - %label - = f.check_box :send_on_accepted, data: { name: 'email_settings_accepted_subject' }, class: 'send_on_radio' - Send an email when the proposal is accepted? - .form-group - = f.label :accepted_subject, 'Subject' - = f.text_field :accepted_subject, class: 'form-control' - .form-group - = f.label :accepted_body, 'Body' - = f.text_area :accepted_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_accepted_subject', - 'data-subject-text' => 'Your submission has been accepted', - 'data-body-input-id' => 'email_settings_accepted_body', - 'data-body-text' => "Dear {name}\n\nWe are very pleased to inform you that your submission {eventtitle} has been accepted for the conference {conference}.\n\nThe public page of your submission can be found at:\n{proposalslink}\nIf you haven´t already registered for {conference}, please do as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help - = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } - .checkbox - %label - = f.check_box :send_on_rejected, data: { name: 'email_settings_rejected_subject'}, class: 'send_on_radio' - Send an email when the proposal is rejected? - .form-group - = f.label :rejected_subject, 'Subject' - = f.text_field :rejected_subject, class: 'form-control' - .form-group - = f.label :rejected_body, 'Body' - = f.text_area :rejected_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_rejected_subject', - 'data-subject-text' => 'Your submission has been rejected', - 'data-body-input-id' => 'email_settings_rejected_body', - 'data-body-text' => "Dear {name},\n\nThank you for your submission {eventtitle} for the conference {conference}.\nAfter careful consideration we are sorry to inform you that your submission has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'rejected_help' } Show Help - = render partial: 'help', locals: {id: 'rejected_help', show_event_variables: true} - .checkbox - %label - = f.check_box :send_on_confirmed_without_registration, data: {name: 'email_settings_confirmed_without_registration_subject'}, class: 'send_on_radio' - Send an email when a user has a confirmed proposal, but isn't yet registered? - .form-group - = f.label :confirmed_without_registration_subject, 'Subject' - = f.text_field :confirmed_without_registration_subject, class: 'form-control' - .form-group - = f.label :confirmed_without_registration_body, 'Body' - = f.text_area :confirmed_without_registration_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_confirmed_without_registration_subject', - 'data-subject-text' => 'Your proposal has been confirmed without registration', - 'data-body-input-id' => 'email_settings_confirmed_without_registration_body', - 'data-body-text' => "Dear {name},\n\nThank you for the confirmation of {eventtitle}. Unfortunately you are not registered for the conference {conference}. Please register as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'confirmed_help' } Show Help - = render partial: 'help', locals: {id: 'confirmed_help', show_event_variables: true} - #notifications.tab-pane{ role: 'tabpanel' } - .checkbox - %label - = f.check_box :send_on_conference_dates_updated, data: { name: 'email_settings_conference_dates_updated_subject'}, class: 'send_on_radio' - Send an email to all participants if the conference dates are changed? - .form-group - = f.label :conference_dates_updated_subject, 'Subject' - = f.text_field :conference_dates_updated_subject, class: 'form-control' - .form-group - = f.label :conference_dates_updated_body, 'Body' - = f.text_area :conference_dates_updated_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_conference_dates_updated_subject', - 'data-subject-text' => 'The dates of the conference have changed', - 'data-body-input-id' => 'email_settings_conference_dates_updated_body', - 'data-body-text' => "Dear {name},\n\nThe date of {conference} has changed.\n New Dates : {conference_start_date} - {conference_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_dates_help' } Show Help - = render partial: 'help', locals: {id: 'updated_dates_help', show_event_variables: false} - .checkbox - %label - = f.check_box :send_on_conference_registration_dates_updated, data: { name: 'email_settings_conference_registration_dates_updated_subject' }, class: 'send_on_radio' - Send an email to all participants if the conference registration dates changed? - .form-group - = f.label :conference_registration_dates_updated_subject, 'Subject' - = f.text_field :conference_registration_dates_updated_subject, class: 'form-control' - .form-group - = f.label :conference_registration_dates_updated_body, 'Body' - = f.text_area :conference_registration_dates_updated_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_conference_registration_dates_updated_subject', - 'data-subject-text' => 'The registration dates have changed', - 'data-body-input-id' => 'email_settings_conference_registration_dates_updated_body', - 'data-body-text' => "Dear {name},\n\nThe registration date of {conference} has changed.\n New Dates : {registration_start_date} - {registration_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_registrations_dates_help' } Show Help - = render partial: 'help', locals: {id: 'updated_registrations_dates_help', show_event_variables: false} - .checkbox - %label - = f.check_box :send_on_venue_updated, data: { name: 'email_settings_venue_updated_subject'}, class: 'send_on_radio' - Send an email on updating the venue? - .form-group - = f.label :venue_updated_subject, 'Subject' - = f.text_field :venue_updated_subject, class: 'form-control' - .form-group - = f.label :venue_updated_body, 'Body' - = f.text_area :venue_updated_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_venue_updated_subject', - 'data-subject-text' => 'The venue has changed', - 'data-body-input-id' => 'email_settings_venue_updated_body', - 'data-body-text' => "Dear {name},\n\nThe Conference venue of {conference} has changed. New location is: {venue}.\n Address: {venue_address}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_venue_help' } Show Help - = render partial: 'help', locals: {id: 'updated_venue_help', show_event_variables: false} - #cfp.tab-pane{ role: 'tabpanel' } - .checkbox - %label - = f.check_box :send_on_program_schedule_public - Send an email to all participants if the schedule is made public? - .form-group - = f.label :program_schedule_public_subject, 'Subject' - = f.text_field :program_schedule_public_subject, class: 'form-control' - .form-group - = f.label :program_schedule_public_body, 'Body' - = f.text_area :program_schedule_public_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_program_schedule_public_subject', - 'data-subject-text' => 'The schedule has been released', - 'data-body-input-id' => 'email_settings_program_schedule_public_body', - 'data-body-text' => "Dear {name},\n\nThe schedule for {conference} has been announced.\nLink to Schedule {schedule_link}\n\nBest wishes\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help - = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} - .checkbox - %label - = f.check_box :send_on_cfp_dates_updated - Send an email to all participants if call for paper dates are updated? - .form-group - = f.label :cfp_dates_updated_subject, 'Subject' - = f.text_field :cfp_dates_updated_subject, class: 'form-control' - .form-group - = f.label :cfp_dates_updated_body, 'Body' - = f.text_area :cfp_dates_updated_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_cfp_dates_updated_subject', - 'data-subject-text' => 'The Call for Papers dates have changed', - 'data-body-input-id' => 'email_settings_cfp_dates_updated_body', - 'data-body-text' => "Dear {name},\n\nThe Conference Call for Papers Details of {conference} has changed.\nNew Dates : {cfp_start_date} - {cfp_end_date}.\n Link to Schedule {schedule_link} \n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help - = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} - #booth.tab-pane{ role: 'tabpanel' } - .checkbox - %label - = f.check_box :send_on_booths_acceptance - Send an email when the booth is accepted? - .form-group - = f.label :booths_acceptance_subject, 'Subject' - = f.text_field :booths_acceptance_subject, class: 'form-control' - .form-group - = f.label :booths_acceptance_body, 'Body' - = f.text_area :booths_acceptance_body, rows:10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_acceptance_subject', - 'data-subject-text' => "Your #{t'booth'} has been accepted!", - 'data-body-input-id' => 'email_settings_booths_acceptance_body', - 'data-body-text' => "Dear {name},\n\nWe are pleased to inform you that your #{t'booth'} request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe are looking forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_acceptance_help' } Show help - = render partial: 'help', locals: {id: 'booth_acceptance_help', show_event_variables: false} - .checkbox - %label - = f.check_box :send_on_booths_rejection - Send an email when the booth is rejected? - .form-group - = f.label :booths_rejection_subject, 'Subject' - = f.text_field :booths_rejection_subject, class: 'form-control' - .form-group - = f.label :booths_rejection_body, 'Body' - = f.text_area :booths_rejection_body, rows:10, cols:20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_rejection_subject', - 'data-subject-text' => "Your #{t'booth'} request has been rejected", - 'data-body-input-id' => 'email_settings_booths_rejection_body', - 'data-body-text' => "Dear {name},\n\nThank you for your #{t'booth'} request {booth_title} for the conference {conference}.\n\nUnfortunately, we are sorry to inform you that your request has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template - %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_rejection_help' } Show help - = render partial: 'help', locals: {id: 'booth_rejection_help', show_event_variables: false} - - .row - .col-md-12 - = f.submit nil, { class: 'btn btn-primary' } diff --git a/app/views/admin/event_types/edit.html.haml b/app/views/admin/event_types/edit.html.haml deleted file mode 100644 index ebf17c09d..000000000 --- a/app/views/admin/event_types/edit.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -.row - .col-md-12 - .page-header - %h1 - Edit Event Type - = @event_type.title -.row - .col-md-8 - = render partial: 'form' diff --git a/app/views/admin/event_types/index.html.haml b/app/views/admin/event_types/index.html.haml deleted file mode 100644 index 015a33975..000000000 --- a/app/views/admin/event_types/index.html.haml +++ /dev/null @@ -1,44 +0,0 @@ -.row - .col-md-12 - .page-header - %h1 Event Types - %p.text-muted - The character of events in your conference -.row - .col-md-12 - %table.table.table-hover#event_types - %thead - %th Title - %th Description - %th Instructions - %th Length - %th Abstract Length - %th Color - %th Actions - %tbody - - @conference.program.event_types.each do |event_type| - %tr - %td - = event_type.title - %td - = markdown(event_type.description) - %td - = markdown(event_type.submission_instructions) - %td - = event_type.length - Minutes - %td - = "#{event_type.minimum_abstract_length} - #{event_type.maximum_abstract_length}" - Words - %td - %span.label{ style: "background-color: #{event_type.color}; color: #{ contrast_color(event_type.color) };" } - = event_type.color - %td - .btn-group{ role: 'group' } - = link_to 'Edit', edit_admin_conference_program_event_type_path(@conference.short_title, event_type.id), - method: :get, class: 'btn btn-primary' - = link_to 'Delete', admin_conference_program_event_type_path(@conference.short_title, event_type.id), - method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete #{event_type.name}?" } -.row - .col-md-12.text-right - = link_to 'Add Event Type', new_admin_conference_program_event_type_path(@conference.short_title), class: 'btn btn-primary' diff --git a/app/views/admin/events/_all_events.pdf.prawn b/app/views/admin/events/_all_events.pdf.prawn deleted file mode 100644 index 3f1c0d786..000000000 --- a/app/views/admin/events/_all_events.pdf.prawn +++ /dev/null @@ -1,36 +0,0 @@ -prawn_document(force_download: true, filename: "#{@file_name}.pdf", page_layout: :landscape) do |pdf| - events_array = [] - header_array = ['Event ID', - 'Title', - 'Abstract', - 'Start time', - 'Submitter', - 'Speaker', - 'Speaker Email', - 'Event Type', - 'Track', - 'Difficulty Level', - 'Room', - 'State' - ] - events_array << header_array - @events.each do |event| - row = [] - row << event.id - row << event.title - row << event.abstract - row << (event.time.present? ? "#{event.time.strftime("%Y-%m-%d")} #{event.time.strftime("%I:%M%p")} " : '') - row << event.submitter.name - row << event.speaker_names - row << event.speaker_emails - row << event.event_type.title - row << (event.track.present? ? event.track.name : '') - row << (event.difficulty_level.present? ? event.difficulty_level.title : '') - row << (event.room.present? ? event.room.name : '') - row << event.state - events_array << row - end - - pdf.text "#{@conference.short_title} Events", font_size: 25, align: :center - pdf.table events_array, header: true, cell_style: {size: 8, border_width: 1},column_widths: [40,60,90,50,70,65,85,50,55,50,60,45] -end diff --git a/app/views/admin/events/_datatable_row_rating.haml b/app/views/admin/events/_datatable_row_rating.haml deleted file mode 100644 index e2183c5e3..000000000 --- a/app/views/admin/events/_datatable_row_rating.haml +++ /dev/null @@ -1,11 +0,0 @@ -- if show_votes - %span{ data: { toggle: 'tooltip' }, title: rating_tooltip(event, max_rating) }< - = rating_stars(event.average_rating, max_rating, avgrate: true) - -- if event.voted?(current_user) - %span.label.label-success<> - You voted: - = rating_fraction(event.user_rating(current_user), max_rating) -- else - %span.label.label-danger<> - Not rated diff --git a/app/views/admin/events/_form.html.haml b/app/views/admin/events/_form.html.haml deleted file mode 100644 index 538d842a6..000000000 --- a/app/views/admin/events/_form.html.haml +++ /dev/null @@ -1,42 +0,0 @@ -.container - .row - .col-md-12 - .tabbable - %ul.nav.nav-tabs - %li.active - = link_to 'Proposal', '#proposal-content', 'data-toggle' => 'tab' - %li - = link_to 'Materials', '#commercials-content', 'data-toggle' => 'tab' - .tab-content - .tab-pane.active#proposal-content - = form_for(@event, url: @url) do |f| - = render 'proposals/form', f: f - = render 'shared/user_selectize' - .tab-pane#commercials-content - - if @event.persisted? - %p.text-muted - You can add materals for your proposal. These materials will be displayed on the - = link_to 'public proposal page.', conference_program_proposal_path(@conference.short_title, @event) - If you don't add any materials, the conference materials will be displayed. - - if can? :create, @event.commercials.new - .row - .col-md-6 - #resource-content - #resource-placeholder{ style: 'background-color:#d3d3d3; float: left; width: 400px; height: 250px; margin: 5px; border-width: 1px; border-style: solid; border-color: rgba(0,0,0,.2);' } - .row - .col-md-6 - = form_for(@event.commercials.new, url: conference_program_proposal_commercials_path(conference_id: @conference.short_title, proposal_id: @event)) do |f| - = render 'proposals/commercial_form_fields', f: f, commercial: @event.commercials.build - %hr - - @event.commercials.each_slice(3) do |slice| - .row - - slice.each do |commercial| - - if commercial.persisted? - .col-md-4 - .panel.panel-default - %div{ id: "resource-content-#{commercial.id}"} - = render partial: 'shared/media_item', locals: { commercial: commercial } - .caption.panel-footer - - if can? :update, commercial - = form_for(commercial, url: conference_program_proposal_commercial_path(conference_id: @conference.short_title, proposal_id: @event, id: commercial)) do |f| - = render 'proposals/commercial_form_fields', f: f, commercial: commercial diff --git a/app/views/admin/events/_user_fields.html.haml b/app/views/admin/events/_user_fields.html.haml deleted file mode 100644 index e39ecf26a..000000000 --- a/app/views/admin/events/_user_fields.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -.nested-fields - = f.inputs do - = f.input :email - = f.input :name - = remove_association_link :user, f diff --git a/app/views/admin/events/events.csv.haml b/app/views/admin/events/events.csv.haml deleted file mode 100644 index e440a49ea..000000000 --- a/app/views/admin/events/events.csv.haml +++ /dev/null @@ -1,6 +0,0 @@ -- if @event_export_option == 'confirmed' - = render 'confirmed_events' -- elsif @event_export_option == 'all' - = render 'all_events' -- elsif @event_export_option == 'all_with_comments' - = render 'all_with_comments' diff --git a/app/views/admin/events/vote.js.erb b/app/views/admin/events/vote.js.erb deleted file mode 100644 index 46849859b..000000000 --- a/app/views/admin/events/vote.js.erb +++ /dev/null @@ -1,10 +0,0 @@ -$('table#myrating').replaceWith( - "<%= escape_javascript(render 'voting', \ - event: @event, \ - show_votes: @program.show_voting?, \ - max_rating: @program.rating, \ - voting_period: @program.voting_period?, \ - votes: @votes, \ - conference_id: @conference.short_title \ - ) %>" -); diff --git a/app/views/admin/lodgings/new.html.haml b/app/views/admin/lodgings/new.html.haml deleted file mode 100644 index 23a97b039..000000000 --- a/app/views/admin/lodgings/new.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.row - .col-md-12 - .page-header - %h1 - Create Lodging -.row - .col-md-8 - = render partial: 'form' diff --git a/app/views/admin/organizations/index.html.haml b/app/views/admin/organizations/index.html.haml deleted file mode 100644 index d7ebe7c67..000000000 --- a/app/views/admin/organizations/index.html.haml +++ /dev/null @@ -1,39 +0,0 @@ -.row - .col-md-12 - .page-header - %h1 Organizations - - if can? :manage, :all - .btn-group.pull-right - = link_to 'Create Organization', new_admin_organization_path, class: 'btn btn-success pull-right' - %p.text-muted - Manage organizations in OSEM -.row - .col-md-12 - %table.datatable - %thead - %th Name - %th Upcoming Conferences - %th Past Conferences - %th Code of Conduct? - %th Actions - %tbody - - @organizations.each do |organization| - %tr{ id: "organization-#{organization.id}" } - %td - = organization.name - %td - = organization.conferences.upcoming.count - %td - = organization.conferences.past.count - %td.text-center - - unless organization.code_of_conduct.blank? - = icon 'fa-solid', 'check', title: 'yes' - %td - .btn-group - = link_to 'Admins', admins_admin_organization_path(organization), - method: :get, class: 'btn btn-success' - = link_to 'Edit', edit_admin_organization_path(organization), - method: :get, class: 'btn btn-primary' - = link_to 'Delete', admin_organization_path(organization), - method: :delete, class: 'btn btn-danger', data: { confirm: "Warning: This will delete #{organization.name} and all its data which includes data for all conferences within #{organization.name}. Do you really want to continue?" } - = link_to 'Add Conference', new_admin_conference_path, method: :get, class: 'btn btn-info' diff --git a/dotenv.example b/dotenv.example new file mode 100644 index 000000000..372800ec9 --- /dev/null +++ b/dotenv.example @@ -0,0 +1,126 @@ +# Set environment variables for OSEM in this file and copy it to .env or +# .env.$environment (like env.production or env.development) +# +# The following is a list of variables that OSEM uses to +# configure the system/application. Uncomment them to change +# the defaults. + +# Your secret key base. You can generate a secure one with +# bundle exec rake secret +# SECRET_KEY_BASE=12345 + +# The type of database to use (postgresql, mysql2, sqlite3) +# OSEM_DB_ADAPTER=mysql2 + +# The name of the host the database runs on +# OSEM_DB_HOST=database + +# The port the databse runs on +# OSEM_DB_PORT=3306 + +# The user to access the database with +# OSEM_DB_USER=root + +# The password to access the database with +# OSEM_DB_PASSWORD=mysecretpassword + +# The name of the database +# OSEM_DB_NAME=osem_production + +# The memached servers to use, default is a file based cache +# OSEM_MEMCACHED_SERVERS='cache-1.example.com,cache-2.example.com' +# OSEM_MEMCACHED_USERNAME='root' +# OSEM_MEMCACHED_PASSWORD='1234' + +# Set this if you want to deviate from our 'standard' ruby version +# OSEM_RUBY_VERSION=3.1.2 + +# What time is it? +# OSEM_TIME_ZONE="UTC" + +# The name of your OSEM installation +# OSEM_NAME=OSEM + +# The host this OSEM instance runs on. Used for +# generating urls in emails sent +# OSEM_HOSTNAME=osem.example.com + +# The address OSEM uses for sending mails +# OSEM_EMAIL_ADDRESS="no-reply@example.com" + +# The api key for transifex.com. +# See https://github.com/openSUSE/osem/wiki/Translation +# OSEM_TRANSIFEX_APIKEY=1234 + +# sentry.io DSN key +# OSEM_SENTRY_DSN=1234 + +# OMNIAUTH Developer Key/Secret for GOOGLE +# OSEM_GOOGLE_KEY=1234 +# OSEM_GOOGLE_SECRET=5678 + +# OMNIAUTH Developer Key/Secret for Facebook +# OSEM_FACEBOOK_KEY=1234 +# OSEM_FACEBOOK_SECRET=5678 + +# OMNIAUTH Developer Key/Secret for GitHub +# OSEM_GITHUB_KEY=1234 +# OSEM_GITHUB_SECRET=5678 + +# OMNIAUTH Developer KEY/Secret for SUSE/openSUSE +# OSEM_SUSE_KEY=1234 +# OSEM_SUSE_SECRET=5678 + +# STRIPE Publishable/Secret keys +# -> https://github.com/openSUSE/osem/wiki/Stripe +# STRIPE_PUBLISHABLE_KEY=1234 +# STRIPE_SECRET_KEY=5678 + +# MATOMO/PIWIK CONFIGURATION +# OSEM_PIWIK_ID=12345 +# OSEM_PIWIK_URL=localhost +# OSEM_PIWIK_ASYNC=false +# OSEM_PIWIK_DISABLED=true +# OSEM_PIWIK_HOSTNAME=localhost + +# The smtp configuration. See the rails guides for more +# http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration +# OSEM_SMTP_ADDRESS=smtp.gmail.com +# OSEM_SMTP_PORT=587 +# OSEM_SMTP_DOMAIN=example.com +# OSEM_SMTP_USERNAME=user1 +# OSEM_SMTP_PASSWORD=password123 +# OSEM_SMTP_AUTHENTICATION=plain +# OSEM_SMTP_ENABLE_STARTTLS_AUTO=true +# OSEM_SMTP_OPENSSL_VERIFY_MODE=peer + +# Enable the usage of the devise ichain plugin +# OSEM_ICHAIN_ENABLED=false +# OSEM_ICHAIN_BASE_URL=https://example.com + +# ReCAPTCHA keys +# RECAPTCHA_SITE_KEY=1234 +# RECAPTCHA_SECRET_KEY=5678 + +# The Conference#short_title to redirect the root URL to +# OSEM_ROOT_CONFERENCE=osc18 + +# log level in production environment +# OSEM_LOG_LEVEL=info + +# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. +# FORCE_SSL=true + +# Your skylight.io keys +# SKYLIGHT_AUTHENTICATION=1234 +# SKYLIGHT_PUBLIC_DASHBOARD_URL='https://oss.skylight.io/app/applications/xxxxxxxxxxxx' + +# How should browser tests be performed? +# For headless Chrome (default): +# OSEM_TEST_DRIVER=chrome_headless +# For Chrome: +# OSEM_TEST_DRIVER=chrome +# For headless Firefox: +# OSEM_TEST_DRIVER=firefox_headless +# For Firefox: +# OSEM_TEST_DRIVER=firefox diff --git a/info.yml b/info.yml index df1335b63..40cec3e6a 100644 --- a/info.yml +++ b/info.yml @@ -23,7 +23,7 @@ project: surname: 'Pau' githubUsername: 'Juapu' pivotalUsername: 'Jupau' - herokuEmail: 'jupau@berkeley.edu ' + herokuEmail: 'jupau@berkeley.edu' member4: name: 'Shiv' surname: 'Sethi' From 7b664f0e16325d866395bb336c65ea62a054bdd2 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 8 Mar 2023 12:55:04 -0800 Subject: [PATCH 023/100] Revert "Merge branch 'main' of https://github.com/cs169/snapcon" This reverts commit 59f1dc8f71ee98ab2d47ea38afe0351964d7bdc6, reversing changes made to 51f8cbdc20b01cbc853411fbccd7146dd05f7f13. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6c32456ea..d8f5a243c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ [![Test Coverage](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/test_coverage)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/test_coverage) ## Spring 2023 CS169L: -[![Bluejay Dashboard](https://img.shields.io/badge/Bluejay-Dashboard_02-blue.svg)](http://dashboard.bluejay.governify.io/dashboard/script/dashboardLoader.js?dashboardURL=https://reporter.bluejay.governify.io/api/v4/dashboards/tpa-CS169L-23-GH-cs169_snapcon/main) [![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) [![CodeQL](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml) [![build](https://github.com/cs169/snapcon/actions/workflows/main.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/main.yml) From 96ab2d8e88c361e5951618d08a9847b5a372de19 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 8 Mar 2023 13:12:33 -0800 Subject: [PATCH 024/100] Revert "Revert "Merge branch 'main' of https://github.com/cs169/snapcon"" This reverts commit 7b664f0e16325d866395bb336c65ea62a054bdd2. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d8f5a243c..6c32456ea 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ [![Test Coverage](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/test_coverage)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/test_coverage) ## Spring 2023 CS169L: +[![Bluejay Dashboard](https://img.shields.io/badge/Bluejay-Dashboard_02-blue.svg)](http://dashboard.bluejay.governify.io/dashboard/script/dashboardLoader.js?dashboardURL=https://reporter.bluejay.governify.io/api/v4/dashboards/tpa-CS169L-23-GH-cs169_snapcon/main) [![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) [![CodeQL](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml) [![build](https://github.com/cs169/snapcon/actions/workflows/main.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/main.yml) From b7e6ac5823132f2184c85dfd20315ed6d86941f0 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 8 Mar 2023 13:15:03 -0800 Subject: [PATCH 025/100] goes back to 5731f8a --- Gemfile 2.next | 1 - app/assets/config/manifest.js | 6 + app/assets/images/person_large.png | Bin 0 -> 4312 bytes app/assets/images/person_small.png | Bin 0 -> 1054 bytes app/assets/images/person_tiny.png | Bin 0 -> 581 bytes app/assets/images/rails.png | Bin 0 -> 6646 bytes app/assets/images/slideshare.png | Bin 0 -> 6475 bytes app/assets/images/snapcon_logo-original.png | Bin 0 -> 46687 bytes app/assets/images/snapcon_logo.png | Bin 0 -> 15211 bytes app/assets/images/snapshot-2020.png | Bin 0 -> 54727 bytes app/assets/images/speakerdeck.png | Bin 0 -> 1465 bytes app/assets/images/star-bright.png | Bin 0 -> 1181 bytes app/assets/images/star-glow.png | Bin 0 -> 1015 bytes app/assets/images/star.png | Bin 0 -> 620 bytes app/assets/images/suse.svg | 104 ++++++ app/assets/javascripts/application.js | 70 ++++ app/assets/javascripts/fullcalendar.js.erb | 92 +++++ app/assets/javascripts/osem-bootstrap.js | 24 ++ app/assets/javascripts/osem-commercials.js | 33 ++ app/assets/javascripts/osem-datatables.js | 49 +++ app/assets/javascripts/osem-datepickers.js | 67 ++++ .../javascripts/osem-revisionhistory.js | 10 + app/assets/javascripts/osem-schedule.js | 181 ++++++++++ app/assets/javascripts/osem-survey.js | 33 ++ app/assets/javascripts/osem-switch.js | 48 +++ app/assets/javascripts/osem-tickets.js | 28 ++ app/assets/javascripts/osem.js | 226 ++++++++++++ app/assets/stylesheets/application.css | 31 ++ app/assets/stylesheets/breakpoints.scss | 21 ++ app/assets/stylesheets/conferences.scss | 13 + .../jquery-ui-timepicker-addon.css | 10 + app/assets/stylesheets/osem-dashboard.scss | 65 ++++ app/assets/stylesheets/osem-datatables.scss | 21 ++ app/assets/stylesheets/osem-fonts.scss | 2 + app/assets/stylesheets/osem-navbar.scss | 58 ++++ app/assets/stylesheets/osem-payments.scss | 3 + app/assets/stylesheets/osem-rating.scss | 17 + app/assets/stylesheets/osem-schedule.scss | 187 ++++++++++ app/assets/stylesheets/osem-splash.scss | 213 ++++++++++++ app/assets/stylesheets/osem-variables.scss | 14 + app/assets/stylesheets/osem.scss | 145 ++++++++ app/assets/stylesheets/strap-on.scss | 60 ++++ app/helpers/admin/cfps_helper.rb | 13 + app/helpers/admin/volunteers_helper.rb | 10 + app/helpers/application_helper.rb | 209 +++++++++++ app/helpers/chart_helper.rb | 15 + app/helpers/conference_helper.rb | 104 ++++++ app/helpers/date_time_helper.rb | 40 +++ app/helpers/event_types_helper.rb | 23 ++ app/helpers/events_helper.rb | 324 ++++++++++++++++++ app/helpers/format_helper.rb | 213 ++++++++++++ app/helpers/paths_helper.rb | 15 + app/helpers/users_helper.rb | 35 ++ app/helpers/versions_helper.rb | 169 +++++++++ app/services/full_calendar_formatter.rb | 49 +++ app/services/mailbluster_manager.rb | 40 +++ app/views/admin/booths/_all_booths.csv.haml | 19 + app/views/admin/booths/_all_booths.pdf.prawn | 26 ++ .../admin/booths/_confirmed_booths.pdf.prawn | 26 ++ app/views/admin/booths/booths.pdf.prawn | 5 + .../admin/comments/_posted_comments.html.haml | 12 + .../admin/comments/_unread_comments.html.haml | 12 + .../admin/conferences/_form_fields.html.haml | 99 ++++++ .../_recent_registrations.html.haml | 19 + .../conferences/_top_submitter.html.haml | 18 + app/views/admin/contacts/edit.html.haml | 58 ++++ .../admin/difficulty_levels/edit.html.haml | 9 + app/views/admin/emails/index.html.haml | 217 ++++++++++++ app/views/admin/event_types/edit.html.haml | 9 + app/views/admin/event_types/index.html.haml | 44 +++ app/views/admin/events/_all_events.pdf.prawn | 36 ++ .../admin/events/_datatable_row_rating.haml | 11 + app/views/admin/events/_form.html.haml | 42 +++ app/views/admin/events/_user_fields.html.haml | 5 + app/views/admin/events/events.csv.haml | 6 + app/views/admin/events/vote.js.erb | 10 + app/views/admin/lodgings/new.html.haml | 8 + app/views/admin/organizations/index.html.haml | 39 +++ info.yml | 2 +- package-lock.json | 171 +-------- 80 files changed, 3829 insertions(+), 165 deletions(-) delete mode 120000 Gemfile 2.next create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/person_large.png create mode 100644 app/assets/images/person_small.png create mode 100644 app/assets/images/person_tiny.png create mode 100644 app/assets/images/rails.png create mode 100644 app/assets/images/slideshare.png create mode 100644 app/assets/images/snapcon_logo-original.png create mode 100644 app/assets/images/snapcon_logo.png create mode 100644 app/assets/images/snapshot-2020.png create mode 100644 app/assets/images/speakerdeck.png create mode 100644 app/assets/images/star-bright.png create mode 100644 app/assets/images/star-glow.png create mode 100644 app/assets/images/star.png create mode 100644 app/assets/images/suse.svg create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/fullcalendar.js.erb create mode 100644 app/assets/javascripts/osem-bootstrap.js create mode 100644 app/assets/javascripts/osem-commercials.js create mode 100644 app/assets/javascripts/osem-datatables.js create mode 100644 app/assets/javascripts/osem-datepickers.js create mode 100644 app/assets/javascripts/osem-revisionhistory.js create mode 100644 app/assets/javascripts/osem-schedule.js create mode 100644 app/assets/javascripts/osem-survey.js create mode 100644 app/assets/javascripts/osem-switch.js create mode 100644 app/assets/javascripts/osem-tickets.js create mode 100644 app/assets/javascripts/osem.js create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/assets/stylesheets/breakpoints.scss create mode 100644 app/assets/stylesheets/conferences.scss create mode 100644 app/assets/stylesheets/jquery-ui-timepicker-addon.css create mode 100644 app/assets/stylesheets/osem-dashboard.scss create mode 100644 app/assets/stylesheets/osem-datatables.scss create mode 100644 app/assets/stylesheets/osem-fonts.scss create mode 100644 app/assets/stylesheets/osem-navbar.scss create mode 100644 app/assets/stylesheets/osem-payments.scss create mode 100644 app/assets/stylesheets/osem-rating.scss create mode 100644 app/assets/stylesheets/osem-schedule.scss create mode 100644 app/assets/stylesheets/osem-splash.scss create mode 100644 app/assets/stylesheets/osem-variables.scss create mode 100644 app/assets/stylesheets/osem.scss create mode 100644 app/assets/stylesheets/strap-on.scss create mode 100644 app/helpers/admin/cfps_helper.rb create mode 100644 app/helpers/admin/volunteers_helper.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/chart_helper.rb create mode 100644 app/helpers/conference_helper.rb create mode 100644 app/helpers/date_time_helper.rb create mode 100644 app/helpers/event_types_helper.rb create mode 100644 app/helpers/events_helper.rb create mode 100644 app/helpers/format_helper.rb create mode 100644 app/helpers/paths_helper.rb create mode 100644 app/helpers/users_helper.rb create mode 100644 app/helpers/versions_helper.rb create mode 100644 app/services/full_calendar_formatter.rb create mode 100644 app/services/mailbluster_manager.rb create mode 100644 app/views/admin/booths/_all_booths.csv.haml create mode 100644 app/views/admin/booths/_all_booths.pdf.prawn create mode 100644 app/views/admin/booths/_confirmed_booths.pdf.prawn create mode 100644 app/views/admin/booths/booths.pdf.prawn create mode 100644 app/views/admin/comments/_posted_comments.html.haml create mode 100644 app/views/admin/comments/_unread_comments.html.haml create mode 100644 app/views/admin/conferences/_form_fields.html.haml create mode 100644 app/views/admin/conferences/_recent_registrations.html.haml create mode 100644 app/views/admin/conferences/_top_submitter.html.haml create mode 100644 app/views/admin/contacts/edit.html.haml create mode 100644 app/views/admin/difficulty_levels/edit.html.haml create mode 100644 app/views/admin/emails/index.html.haml create mode 100644 app/views/admin/event_types/edit.html.haml create mode 100644 app/views/admin/event_types/index.html.haml create mode 100644 app/views/admin/events/_all_events.pdf.prawn create mode 100644 app/views/admin/events/_datatable_row_rating.haml create mode 100644 app/views/admin/events/_form.html.haml create mode 100644 app/views/admin/events/_user_fields.html.haml create mode 100644 app/views/admin/events/events.csv.haml create mode 100644 app/views/admin/events/vote.js.erb create mode 100644 app/views/admin/lodgings/new.html.haml create mode 100644 app/views/admin/organizations/index.html.haml diff --git a/Gemfile 2.next b/Gemfile 2.next deleted file mode 120000 index 6ab79009c..000000000 --- a/Gemfile 2.next +++ /dev/null @@ -1 +0,0 @@ -Gemfile \ No newline at end of file diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 000000000..45d64d986 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,6 @@ +// This is a sprockets 4.0 manifest file +// +//= link application.css +//= link application.js +//= link_tree ../images + diff --git a/app/assets/images/person_large.png b/app/assets/images/person_large.png new file mode 100644 index 0000000000000000000000000000000000000000..763b29b186143d97f0ba4569e84537c3edabb7c4 GIT binary patch literal 4312 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/person_small.png b/app/assets/images/person_small.png new file mode 100644 index 0000000000000000000000000000000000000000..b454217f68b2a344e44a1d381ccfd92749a3e8d6 GIT binary patch literal 1054 zcmV+(1mXLMP)o5?7kL|Rn8d{NpcnHJ_DImlKHZ1w?YypW4 z3n)UO6wwMO^g)t3PVVB$zJ8>a78q%yR_# zW1y5OF9XQ)T)7ZpE|@O?JkNvYd0>ng5K>&44j_bp5CU12AX&sZ?eHP)d#Q zcJx9BHHUMKJkRF`V2q(ssVLV0k|aT|*TdV}8{XgF;rl+qFhm%JSX)~|Q4|J%%ogVy zLWr*c7-Ohbs}6wYdFc21xVpMB7Mf0{7>!1Fcz8eC(#4Icsy3(07`YJ zUMTDuU#r!A1YpnCHI$})tLd}=&}cNAOJ@;lHk%HB{+keD79!giV+exa*8s|l+D(Lg zhqqd-Zv)tKZJnYh{sGAQ+y>a+-*@WwJhr*HSqdOE3;_Ku*9QPVyR4a>{RYMh<+JkP;7R}HFkIYwQ~62Puh zdKXg^1*X#};y6Ye$4HU{lgR{0k{}2I)a!LH#?<0cTz9)uHhMzNx%1H>guq}hK)2gP zzu%wP-AeO)AFHdY*xA`ZqtS2xIOoQAVt*OQrj$~Or>7^pyu3J*=CvRQu)e;Im6a7_ zSytMVIsh{2PN#z;Nxmg)TeI23*49?(my*+#$8n6u$HyP_0k#c?LvYTqy}fOmECB9h z^!)t%tK!n)I94s%On@Xwkf!Oci%XkKCeCZ{04T+ej}H$3!veNQzmVrS;yC8@dR-l* YKe2I?I#@StHvj+t07*qoM6N<$f;tn``2YX_ literal 0 HcmV?d00001 diff --git a/app/assets/images/person_tiny.png b/app/assets/images/person_tiny.png new file mode 100644 index 0000000000000000000000000000000000000000..5fab833cbe46b7f267a7b90b519afcf331b0e6b3 GIT binary patch literal 581 zcmV-L0=oT)P)=OjSi3->;g66aAtgoXG<^QF`2<0c zC?GCkhz!D(jn{hx^4=vJcdwjz@6GD%jCy^2_56TR3S$gfYXF>cv~7#`S5(YTp|zGc zj)|fO5dom~=e})=VZ77K;T<(_oCDEKAz9 z{cWI>B1sa`G$o26US3|Pswzx*e}Cue>x<=b8D4cf9`W8I0M=TpwWMjvYPI?%?y=wR z`S|!CP1C>!5Tz7x95WaU5D_+;&6T*vcDqGH7z_r)ag0(5F~$%@5m6M;G!5tT`KH0) za6l;)_{JDSYmJD6ulpCY)+nVelSR;4Uo!al6SpuN4zFtBoTKYHy!UwTd3=1lY4Gsy z5O`hJ;haO-whc?-z32Y^p80%!C4PE(;_mJa?|sOsZCgxVsOy?Ij?r54{QS&pHY3k- zwAM_gQ%0jvm|WL&7-#^hs=6d9f-#0H%Q&4*VIQ1x6h*;gG70P4_f&sOBEskACwZR# z^=Nv|csyn_8sVI~1m8E;>osLr-V6{fN0nt6Wmz(vPA{eabzT2g{3nW{2ru{r#~SEt TIGVg)00000NkvXXu0mjf2aEp; literal 0 HcmV?d00001 diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png new file mode 100644 index 0000000000000000000000000000000000000000..d5edc04e65f555e3ba4dcdaad39dc352e75b575e GIT binary patch literal 6646 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 AQ~&?~ literal 0 HcmV?d00001 diff --git a/app/assets/images/slideshare.png b/app/assets/images/slideshare.png new file mode 100644 index 0000000000000000000000000000000000000000..d730f0ae32e8dab3ae0df0df5e08dc176d143c2d GIT binary patch literal 6475 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/snapcon_logo-original.png b/app/assets/images/snapcon_logo-original.png new file mode 100644 index 0000000000000000000000000000000000000000..76c32bbd23675a79704af1d489421f8aa802110f GIT binary patch literal 46687 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⋙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%0D@s2RNllmkusLy9*%PVOcO|#$ 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 TF2%%vALqkD*1Q7i%tq!37FAoq3D$*7T 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 IN|=QFADq0exBvhE literal 0 HcmV?d00001 diff --git a/app/assets/images/snapshot-2020.png b/app/assets/images/snapshot-2020.png new file mode 100644 index 0000000000000000000000000000000000000000..3d9b05b1785a386b0d97bb2e2228a78ffe5aec13 GIT binary patch literal 54727 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 z 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/speakerdeck.png b/app/assets/images/speakerdeck.png new file mode 100644 index 0000000000000000000000000000000000000000..895ad79bdd2b5a60f06d44428111e799c9676e19 GIT binary patch literal 1465 zcmV;q1xEUbP)yp2 z=m}K_1$v>Na0?=wnnOb!K^2kEs02zBNK9g{*E??y&MuDGb?mg24E9K4OWxg?dFFZE zk9pT>-w)pkA9yp!2agdWs0IMp2u!MmkgGK1+VrmP5tB3mlZ^nOQ6(kq) zbWg8hGA^#?g%i|6Yd40D7l!#ylPq<$qfoJHGd2pOQm9;ktsXN~OqVvv_MZ{njEB9UUEHvsql%-Ozc&f)pvP z)fUN#YP!f+Ob>xp->tP+Yw7IlWME)`iK2 zU)U?)hz%iP*AG%n^L?LGD#d5}_Hp{m8NNDkg1Nal@~*pX91+3uJa%_=@%5=w9O>`p zr>j@_ZE})Vi;HNjt76uQ;K*Cm#mbc!xdtrSDnR=@_IpQm%%HURSZ z{5zW!d>4ElfWtjK96EG}n>TK7`}S?7(rI$J9LAUp&Q$@i$xH`f>;}ymPLmURg^1hlC|<~30g!*rc!)9ILOe@5MK-qz6DhRkhSN79x_no zgn|v|>gwYAix=rTa)di~?r?8xjK5#LL`Q706+wkI8RVc+W%(oUSQTutII!h#PY;7b zL+gGoWxbTea=A(j&_KeuT#oVk_YrH6C^kdyD#1iCO!^X;vx+UsHPC$22QkKkfxc9l1>fiH=qT5&T_c@N^ZNB`9LEVm$3_4HXCxNzCHDMZgm0`+ zm&-9ZImy$fPua0!2MY@eA)}>>ynOjGZ!#Gc78Vc@9LL!Z+0*Qq7C^o)I5Gz;K;!bg(&7VRxTD&R^2Q?<= zVWpxN25OY6KtX&88d8oSCvIIHw;V@f9YukSqi@NCo%`=30GLwzr{L5?EX5&@9n~ zND)vi3EWS1GPgUAczzw#B?V<1bXyC`agYt(0#tlW5M0r%t*F*ID}Aheh;OSYE=yHI zzRz{`=DUy&2Bg@A`1eNx$hASIx!jcN`_ujbju~5) Tfy9%000000NkvXXu0mjf1xTfa literal 0 HcmV?d00001 diff --git a/app/assets/images/star-bright.png b/app/assets/images/star-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..2dbc8ed36445f19350c03d96a707fa8faeebf411 GIT binary patch literal 1181 zcmV;O1Y-M%P)P000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3Si}E+{($WS;;402y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00ad|L_t(Y$L*ERZ&Xzj$3OSJ zH#6;<>GTJ*1qwpAv`PR8CN5y5h%qK$!p4PhW8Ao8p@|!H>%y2I7(+tTgyyNmmQn(swf-dx!#Agc%=c#ya8MSz6H)IrIzl3_`d(D5aNXA zdHtT}Q79B}T^A`OQS*Cx^Pl2~4o?+Jy{k z+C#5gBWeCj7S_Q!q)~uV-p%G`KEtHR>JTBssd~MBHHsoLyz3-ZZYNxp}b3at^ch%q@319;>6iH%F6QC~F1yseZvao=TBN}V#se3coJq&o{%gN`7x z2zLXHH-*!`6`NUX+QTFvNoO8oL2G?+$BrEzuC|QsDt{ZKBXhdjfGkG1{gCr8NkAH| zAe|hpw+)+W%*qmJu*7ZO9C#308F3th=`|775G}W9-MB(89>5(tOq`C)hn#bSMbK4V$-3I$PZ0ntwjhy)STw`AK#;M^$dgk`#^vYLazCW zRVtOsagy0??HikR=dHEYhGA%HwVJL}Dq*cwOM)P<)><3gyk?jE^ET}DY^74Y_#o~2 zq;+$#73B8N>N?EM&eCW!&c$*3f|Rlx$MM01g@v!@=H~RWF3~IPqTOmCrFgK*===U* vUlRbmuy0LXv^rPO);*Tc{sfFTuk1W@3+00000NkvXXu0mjfu@WZ# literal 0 HcmV?d00001 diff --git a/app/assets/images/star-glow.png b/app/assets/images/star-glow.png new file mode 100644 index 0000000000000000000000000000000000000000..b605587a45672085a08cb580e8944619401a8041 GIT binary patch literal 1015 zcmVP000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3Si}E-_zk%wGTi02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00Uo1L_t(Y$JLfkXdG1>$3HW( zo9QN#?2bt`AqgZrqyz&h6a+y8J&0CNM6H(`yoNvzqEHenqS%6|hk_-F7Y|-kuqY@9 z9)brEOGB##Ym8Gjv)eSA&7aNgWOrxZ%=0k2xI)5a6$}30!SBI?&-=dL?|t9Xlv4cH zp>r1^)=V77X}9(SFy1o%96$(hbV!?c#Wc-#W3j)Md!Bb5nCnPMGY;CB<2Wi=h1c$t=Ynj!dt+Lz!Sg&z_@MO-L`GRM~^`7P;|n%8!&r~ za-~K-pRWKp;Adb7xGaQl4};9=y8d2IPY;Pi0@E~!#bRih23Hp0m(L(M0OyR zg+d1Q_n;>Y{gW{9Fif2!=p81L$rOYTgNL{9moGzDgzi4*wV-zrLJf*{U^feX9sC;f zje^+^)e5XF!^#qTeGcASJwoB1{}iMFrCl)Mu;aq|PZ2-{5J=eDgg>rA?mA$AzYjGZ z=9b%b*J3uCb?WsxocaPP5;ii>HwHlfVFOSPB3LLPJryY|6=8mvFp$-$tc)qph2OZZ1rPO>jnt*tE|kjZ4SJN4u&_dIP000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3Si}E)$9mjk^E<02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00GoVL_t(Y$JLfWi_=gL$A2$P z8@mlOVQU2`EDYW}c<}DgZy;X$0!8p3dRbvFqI+2nf>J+#XZI_3^sd(iY9eUY9#R@C z?aO;!iUw_M7HfTF>ts|NVnZ?Q>j#nkS_2X__7U6p7_3hrCP1xIL?nN48vg4Ox8gz0o_)s z_1ldg2vADhi=ybpI*97``^CKv+yZU_*MNgC4EMq?T>9{2GGRCzegi%M?}4|#sffg@ zAa@tYD5d`RmeyLd*34!zrqd}|mZ7zNEFwqCARjuN&edA2cCMbt z^PIt8@Kr=E?z9}nt$YU}47RMj1$}b>0000 + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 000000000..8284fbf80 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,70 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require jquery +//= require jquery_ujs +//= require jquery-ui/widgets/draggable +//= require jquery-ui/widgets/droppable +//= require waypoints/jquery.waypoints + +//= require datatables/jquery.dataTables +//= require datatables/dataTables.bootstrap +//= require datatables/extensions/Buttons/dataTables.buttons +//= require datatables/extensions/Buttons/buttons.bootstrap +//= require datatables/extensions/Buttons/buttons.html5 +//= require datatables/extensions/Buttons/buttons.dataTables + +//= require cocoon +//= require bootstrap +//= require Chart.bundle +//= require chartkick +//= require osem +//= require jquery-smooth-scroll +//= require trianglify +//= require tinycolor +//= require bootstrap-markdown +//= require to-markdown +//= require markdown +//= require momentjs +//= require leaflet +//= require holderjs +//= require bootstrap-datetimepicker +//= require osem-datepickers +//= require osem-datatables +//= require osem-tickets +//= require bootstrap-switch +//= require osem-schedule +//= require osem-switch +//= require osem-bootstrap +//= require osem-revisionhistory +//= require osem-commercials +//= require unobtrusive_flash +//= require unobtrusive_flash_bootstrap +//= require countable +//= require selectize +//= require bootstrap-select +//= require osem-survey +//= require pagy +//= require fullcalendar-scheduler/main.js +//= require fullcalendar + +$(document).ready(function() { + $('a[disabled=disabled]').click(function(event){ + return false; + }); + + $('body').smoothScroll({ + delegateSelector: 'a.smoothscroll' + }); + + window.addEventListener("load", Pagy.init); +}); diff --git a/app/assets/javascripts/fullcalendar.js.erb b/app/assets/javascripts/fullcalendar.js.erb new file mode 100644 index 000000000..e80570776 --- /dev/null +++ b/app/assets/javascripts/fullcalendar.js.erb @@ -0,0 +1,92 @@ +$( document ).ready(function() { + let calendarEl = document.getElementById('vert-schedule-full-calendar'); + if (!calendarEl) return; //check that we need a vertical schedule + let $fullCalendar = $('#fullcalendar'); + + let license_key = "<%= Rails.configuration.fullcalendar[:license_key]%>"; + + let offset = $fullCalendar.data('tzOffset'); + let interval = Math.max(5, $fullCalendar.data('minInterval')); + let localOffset = (new Date()).getTimezoneOffset()/60; + let startTime = $fullCalendar.data('startHour') - offset - localOffset; + let endTime = $fullCalendar.data('endHour') - offset - localOffset; + let startDate = new Date($fullCalendar.data('startDate')); + let endDate = new Date($fullCalendar.data('endDate')); + let event_num_days = (endDate.getTime() - startDate.getTime())/ (1000 * 3600 * 24); + let width = Math.min(4, event_num_days); + let localName = Intl.DateTimeFormat().resolvedOptions().timeZone; + // Program Hours * Minutes / Interval * Min Row Height for an event. + let contentHeight = Math.max(400, (endTime - startTime) * 60 / interval * 16); + // UTC JS offsets are "-1 *" of how they're displayed. + let operator = localOffset < 0 ? '+' : '-'; + $('.js-localTimezone').text(`(${localName} UTC ${operator}${Math.abs(localOffset)})`); + + let rightHeaderToolbar = 'resourceTimeGridDay,resourceTimeGridFourDay,listDay'; + if (event_num_days == 1) { + rightHeaderToolbar = 'resourceTimeGridDay,listDay'; + } + + // Remove subevents from the calendar. + let filterSubevents = (events) => { + return events.filter(event => event.has_parent === false) + }; + + var calendar = new FullCalendar.Calendar(calendarEl, { + schedulerLicenseKey: license_key, + nowIndicator: true, + now: $fullCalendar.data('now'), + contentHeight: contentHeight, + expandRows: true, + allDaySlot: false, + slotMinTime: startTime + ':00:00', + slotMaxTime: endTime + ':00:00', + // TODO: Set these dynamically. + slotDuration: '00:15:00', + slotLabelInterval: '00:15:00', + slotLabelFormat: { + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'short' + }, + validRange: { + start: $fullCalendar.data('startDate'), + end: $fullCalendar.data('endDate') + }, + timeZone: 'local', + initialDate: $fullCalendar.data('day'), + initialView: 'resourceTimeGridDay', + resources: $fullCalendar.data('rooms'), + resourceOrder: 'order, title', + // TODO: Move this to a XHR. + events: filterSubevents($fullCalendar.data('events')), + displayEventEnd: false, // TODO change in list view. + displayEventTime: false, + titleFormat: { // will produce something like "Tues, September 18" + month: 'long', + day: 'numeric', + weekday: 'short' + }, + headerToolbar: { + left: 'prev,next', + center: 'title', + right: rightHeaderToolbar + }, + // TODO: Make this conference Specific? + views: { + resourceTimeGridFourDay: { + type: 'resourceTimeGrid', + duration: { days: width }, + buttonText: 'overview', + datesAboveResources: true + }, + listDay: { + type: 'listDay', + displayEventEnd: true, + displayEventTime: true + } + } + }); + + calendar.render(); +}); diff --git a/app/assets/javascripts/osem-bootstrap.js b/app/assets/javascripts/osem-bootstrap.js new file mode 100644 index 000000000..f1335473f --- /dev/null +++ b/app/assets/javascripts/osem-bootstrap.js @@ -0,0 +1,24 @@ +$(function() { + // add a hash to the URL when the user clicks on a tab + $('a[data-toggle="tab"]').on('click', function(e) { + history.pushState(null, null, $(this).attr('href')); + }); + // navigate to a tab when the history changes + window.addEventListener("popstate", function(e) { + var activeTab = $('a[href="' + location.hash + '"]'); + if (activeTab.length) { + activeTab.tab('show'); + } else { + $('.nav-tabs a').first().tab('show'); + } + }); +}); + +$(function() { + var hash = window.location.hash; + hash && $('ul.nav a[href="' + hash + '"]').tab('show'); +}); + +$(function () { + $('[data-toggle="popover"]').popover({html: true}) +}); diff --git a/app/assets/javascripts/osem-commercials.js b/app/assets/javascripts/osem-commercials.js new file mode 100644 index 000000000..db2900976 --- /dev/null +++ b/app/assets/javascripts/osem-commercials.js @@ -0,0 +1,33 @@ +$(function () { + $(document).ready(function() { + $("#commercial_url").bind('paste keyup', function() { + clearTimeout($(this).data('timeout')); + + $(this).data('timeout', setTimeout(function () { + var url = $('#new_commercial').attr('action'); + url = url + '/render_commercial' + $.ajax({ + method: 'GET', + url: url, + data: { url: $('#commercial_url').val() }, + error: function(xhr, status, error) { + $('#commercial_submit_action').prop('disabled', true); + $('#resource-content').hide(); + $('#resource-placeholder').show(); + $('#commercial_error').hide(); + $('#commercial_url_input').addClass('has-error error'); + $('' + xhr.responseText + '').insertAfter('#commercial_url'); + }, + success: function(msg) { + $('#commercial_submit_action').prop('disabled', false); + $('#commercial_url_input').removeClass('has-error error'); + $('#commercial_error').hide(); + $('#resource-placeholder').hide(); + $('#resource-content').html(msg).show(); + } + }) + }, 200) + ); + }); + }); +}); diff --git a/app/assets/javascripts/osem-datatables.js b/app/assets/javascripts/osem-datatables.js new file mode 100644 index 000000000..5f8122330 --- /dev/null +++ b/app/assets/javascripts/osem-datatables.js @@ -0,0 +1,49 @@ +$(function () { + $.extend(true, $.fn.dataTable.defaults, { + "buttons": ["csv"], + "dom": "lBfrtip", + "stateSave": true, + "autoWidth": false, + "pagingType": "full_numbers", + "lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]], + }); + + $('.datatable:not([data-source])').DataTable(); + + $('.datatable').on('init.dt', function (e, settings, json) { + var datatableApi = $(this).dataTable().api(); + // Thanks to cale_b: https://stackoverflow.com/u/870729 + // Stack Overflow question: https://stackoverflow.com/q/5548893 + // Stack Overflow answer: https://stackoverflow.com/a/23897722 + // Grab the datatables input box and alter how it is bound to events + $(".dataTables_filter input") + .unbind() // Unbind previous default bindings + .bind("input", function(e) { // Bind our desired behavior + // If the length is 3 or more characters, or the user pressed ENTER, search + if(this.value.length >= 3 || e.keyCode == 13) { + // Call the API search function + datatableApi.search(this.value).draw(); + } + // Ensure we clear the search if they backspace far enough + if(this.value == "") { + datatableApi.search("").draw(); + } + return; + }); + }); +}); + +function truncatify(selector) { + $(selector).each(function(){ + var text = $(this).text() + $(this).html('' + text + ' ') + }); +} + +function iconize(selector, value, icon, title) { + $(selector).each(function(){ + if ($(this).text() == value) { + $(this).html(""); + } + }); +} diff --git a/app/assets/javascripts/osem-datepickers.js b/app/assets/javascripts/osem-datepickers.js new file mode 100644 index 000000000..e5bbe10e9 --- /dev/null +++ b/app/assets/javascripts/osem-datepickers.js @@ -0,0 +1,67 @@ +$(function () { + $("input[id^='datetimepicker']").datetimepicker({ + useCurrent: false, + sideBySide: true, + format: 'YYYY-MM-DD HH:mm' + }); + + $('.datetimepicker').datetimepicker({ + useCurrent: false, + sideBySide: true, + format: 'YYYY-MM-DD HH:mm' + }); + + $("#conference-start-datepicker").datetimepicker({ + useCurrent: false, + ignoreReadonly: true, + format: "YYYY-MM-DD", + }); + + $("#conference-end-datepicker").datetimepicker({ + useCurrent: false, + ignoreReadonly: true, + format: "YYYY-MM-DD" + }); + + // start_registration <= end_registration <= end_conference + var end_conference = $('form').data('end-conference'); + + $('#registration-period-start-datepicker').datetimepicker({ + format: 'YYYY-MM-DD', + maxDate : end_conference + }); + + $('#registration-period-end-datepicker').datetimepicker({ + format: 'YYYY-MM-DD', + maxDate : end_conference + }); + + $("#conference-start-datepicker").on("dp.change",function (e) { + $('#conference-end-datepicker').data("DateTimePicker").minDate(e.date); + if (!$('#conference-end-datepicker').val()) { + $('#conference-end-datepicker').data("DateTimePicker").date(e.date); + } + }); + + $("#conference-start-datepicker").change(function (e) { + $('#conference-start-datepicker').val()?$('#conference-end-datepicker').data("DateTimePicker").minDate(e.date):$('#conference-end-datepicker').data("DateTimePicker").minDate(null); + }); + + $("#conference-end-datepicker").on("dp.change",function (e) { + $('#conference-start-datepicker').data("DateTimePicker").maxDate(e.date); + }); + + $("#conference-end-datepicker").change(function (e) { + $('#conference-end-datepicker').val()?$('#conference-start-datepicker').data("DateTimePicker").maxDate(e.date):$('#conference-start-datepicker').data("DateTimePicker").maxDate(null); + }); + + $("#registration-period-start-datepicker").on("dp.change",function (e) { + $('#registration-period-end-datepicker').data("DateTimePicker").minDate(e.date); + if (!$('#registration-period-end-datepicker').val()) { + $('#registration-period-end-datepicker').data("DateTimePicker").date(e.date); + } + }); + $("#registration-period-end-datepicker").on("dp.change",function (e) { + $('#registration-period-start-datepicker').data("DateTimePicker").maxDate(e.date); + }); +} ); diff --git a/app/assets/javascripts/osem-revisionhistory.js b/app/assets/javascripts/osem-revisionhistory.js new file mode 100644 index 000000000..bd51435b1 --- /dev/null +++ b/app/assets/javascripts/osem-revisionhistory.js @@ -0,0 +1,10 @@ +$(document).ready(function() { + $('.show-changeset').click(function(){ + if ($(this).text() == 'View Changes'){ + $(this).text('Hide Changes'); + }else { + $(this).text('View Changes'); + } + $('#changeset-' + this.id).toggle(); + }); +}); diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js new file mode 100644 index 000000000..7e6fbb1e8 --- /dev/null +++ b/app/assets/javascripts/osem-schedule.js @@ -0,0 +1,181 @@ +// ADMIN SCHEDULE + +var url; // Should be initialize in Schedule.initialize +var schedule_id; // Should be initialize in Schedule.initialize + +function showError(error){ + // Delete other error messages before showing the new one + $('.unobtrusive-flash-container').empty(); + UnobtrusiveFlash.showFlashMessage(error, {type: 'error'}); +} + +var Schedule = { + initialize: function(url_param, schedule_id_param) { + url = url_param; + 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 success_callback = function(data) { + e.attr("event_schedule_id", null); + e.appendTo($(".unscheduled-events")); + e.find(".schedule-event-delete-button").hide(); + } + var error_callback = function(data) { + showError($.parseJSON(data.responseText).errors); + } + $.ajax({ + url: my_url, + type: 'DELETE', + success: success_callback, + error: error_callback, + dataType : 'json' + }); + } + else{ + showError("The event couldn't be unscheduled"); + } + }, + add: function (previous_parent, new_parent, event) { + event.appendTo(new_parent); + var event_schedule_id = 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){ + type = 'PUT'; + my_url += ('/' + event_schedule_id); + } + else{ + params['event_schedule']['event_id'] = event.attr("event_id"); + params['event_schedule']['schedule_id'] = schedule_id; + } + var success_callback = function(data) { + event.attr("event_schedule_id", data.event_schedule_id); + event.find(".schedule-event-delete-button").show(); + } + var error_callback = function(data) { + showError($.parseJSON(data.responseText).errors); + event.appendTo(previous_parent); + } + $.ajax({ + url: my_url, + type: type, + data: params, + success: success_callback, + error: error_callback, + dataType : 'json' + }); + } +}; + +$(document).ready( function() { + // hide the remove button for unscheduled and non schedulable events + $('.unscheduled-events .schedule-event-delete-button').hide(); + $('.non_schedulable .schedule-event-delete-button').hide(); + + // set events as draggable + $('.schedule-event').not('.non_schedulable').draggable({ + snap: '.schedule-room-slot', + revertDuration: 200, + revert: function (event, ui) { + return !event; + }, + stop: function(event, ui) { + this._originalPosition = this._originalPosition || ui.originalPosition; + ui.helper.animate( this._originalPosition ); + }, + opacity: 0.7, + snapMode: "inner", + zIndex: 2, + scroll: true + }); + + // set room cells as droppable + $('.schedule-room-slot').not('.non_schedulable .schedule-room-slot').droppable({ + accept: '.schedule-event', + tolerance: "pointer", + drop: function(event, ui) { + $(ui.draggable).css("left", 0); + $(ui.draggable).css("top", 0); + $(this).css("background-color", "#ffffff"); + Schedule.add($(ui.draggable).parent(), $(this), $(ui.draggable)); + }, + over: function(event, ui) { + $(this).css("background-color", "#009ED8"); + }, + out: function(event, ui) { + $(this).css("background-color", "#ffffff"); + } + }); +}); + + +// PUBLIC SCHEDULE + +function starClicked(e) { + // stops the click from propagating + if (!e) var e = window.event; + e.preventDefault(); + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + + var callback = function(data) { + $(e.target).toggleClass('fa-star fa-star-o'); + } + + var params = { favourite_user_id: $(e.target).data('user') }; + + $.ajax({ + url: $(e.target).data('url'), + type: 'PATCH', + data: params, + success: callback, + dataType : 'json' + }); +} + +function eventClicked(e, element) { + if (e.target.href) { + return; + } + var url = $(element).data('url'); + if (e.ctrlKey || e.metaKey) { + window.open(url, '_blank'); + } else { + window.location = url; + } +} + +function updateFavouriteStatus(options) { + if (options.loggedIn === false) { + $('.js-toggleEvent').hide(); + } + + options.events.forEach(function (id) { + $(`#eventFavourite-${id}`).removeClass('fa-star-o').addClass('fa-star'); + }); +} + +/* Links inside event-panel (to make ctrl + click work for these links): + = link_to text, '#', onClick: 'insideLinkClicked();', 'data-url' => url +*/ +function insideLinkClicked(event) { + // stops the click from propagating + if (!event) // for IE + var event = window.event; + event.cancelBubble = true; + if (event.stopPropagation) event.stopPropagation(); + + var url = $(event.target).data('url'); + if(event.ctrlKey || e.metaKey) + window.open(url,'_blank'); + else + window.location = url; +} diff --git a/app/assets/javascripts/osem-survey.js b/app/assets/javascripts/osem-survey.js new file mode 100644 index 000000000..20ed2a194 --- /dev/null +++ b/app/assets/javascripts/osem-survey.js @@ -0,0 +1,33 @@ +$(function() { + $('.selectpicker').on('changed.bs.select', function (e, clickedIndex) { + $('.kinds').addClass('hidden'); + var selected = $('.selectpicker').find('option:selected').val(); + $('.' + selected).removeClass('hidden'); + + if (selected == 'choice') { + $('.survey-possible-answers').removeClass('hidden'); + } + else + { + $('.survey-possible-answers').addClass('hidden'); + } + }); + $('#survey_question_title').on('keyup', function(){ + $('#survey_question_preview #title').text($(this).val()) + }); + + function render_possible_answers_preview() { + var options_html = ''; + var options_array = $('#survey_question_possible_answers').val().split(','); + var input_type = ($('#survey_question_min_choices').val() == 1 && + $('#survey_question_max_choices').val() == 1) ? 'radio' : 'checkbox'; + $.each(options_array, function(index, option) { + options_html += ' ' + option.trim() + '
'; + }); + $('#survey_question_preview .choice').html(options_html) + }; + + $('#survey_question_possible_answers').on('keyup', render_possible_answers_preview); + $('#survey_question_min_choices').on('change', render_possible_answers_preview); + $('#survey_question_max_choices').on('change', render_possible_answers_preview); +}); diff --git a/app/assets/javascripts/osem-switch.js b/app/assets/javascripts/osem-switch.js new file mode 100644 index 000000000..5e8c47921 --- /dev/null +++ b/app/assets/javascripts/osem-switch.js @@ -0,0 +1,48 @@ +function checkboxSwitch(selector){ + $(selector).bootstrapSwitch( + + ); + + $(selector).on('switchChange.bootstrapSwitch', function(event, state) { + var url = $(this).attr('url') + state; + var method = $(this).attr('method') || 'patch'; + + $.ajax({ + url: url, + type: method, + dataType: 'script' + }); + }); +} + +$(function () { + $.fn.bootstrapSwitch.defaults.onColor = 'success'; + $.fn.bootstrapSwitch.defaults.offColor = 'warning'; + $.fn.bootstrapSwitch.defaults.onText = 'Yes'; + $.fn.bootstrapSwitch.defaults.offText = 'No'; + $.fn.bootstrapSwitch.defaults.size = 'small'; + + + checkboxSwitch("[class='switch-checkbox']"); + + $("[class='switch-checkbox-schedule']").bootstrapSwitch(); + + $('input[class="switch-checkbox-schedule"]').on('switchChange.bootstrapSwitch', function(event, state) { + var url = $(this).attr('url'); + var method = $(this).attr('method') || 'patch'; + + if(state){ + url += $(this).attr('value'); + } + + var callback = function(data) { + showError($.parseJSON(data.responseText).errors); + } + $.ajax({ + url: url, + type: method, + error: callback, + dataType: 'json' + }); + }); +}); diff --git a/app/assets/javascripts/osem-tickets.js b/app/assets/javascripts/osem-tickets.js new file mode 100644 index 000000000..3f559c380 --- /dev/null +++ b/app/assets/javascripts/osem-tickets.js @@ -0,0 +1,28 @@ +function update_price($this){ + var id = $this.data('id'); + + // Calculate price for row + var value = $this.val(); + var price = $('#price_' + id).text(); + $('#total_row_' + id).text((value * price).toFixed(2)); + + // Calculate total price + var total = 0; + $('.total_row').each(function( index ) { + total += parseFloat($(this).text()); + }); + $('#total_price').text(total.toFixed(2)); +} + +$( document ).ready(function() { + $('.quantity').each(function() { + update_price($(this)); + }); + + $('.quantity').change(function() { + update_price($(this)); + }); + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }); +}); diff --git a/app/assets/javascripts/osem.js b/app/assets/javascripts/osem.js new file mode 100644 index 000000000..4701f1b1a --- /dev/null +++ b/app/assets/javascripts/osem.js @@ -0,0 +1,226 @@ +$(function () { + /** + * Update the number of words in the biography text field every time the user + * releases a key on the keyboard + */ + $("#user_biography").bind('keyup', function() { + word_count(this, 'bio-length', 150); + } ); + + /** + * Displays a modal with the questions of the registration. + */ + $(document).ready(function(){ + $(".question-btn").click(function(){ + var id = $(this).data('id'); + $("#question-modal-body").empty(); + $("#question-modal-body").html($(".question" + id).clone().show()); + $("#question-modal-header").text('Questions for ' + $(this).data('name')); + $('#questions').modal('show'); + }); + }); + + /** + * Toggles email template help below email body textarea field. + */ + $(document).ready( function() { + $(".template-help").hide(); + $(".template_help_link").click(function() { + var id = $(this).data('name'); + $("#" + id).toggle(); + }); + }); + + /** + * Randomize order of parallel elements by shuffling the decks + * Adapted from https://stackoverflow.com/questions/7070054 + */ + + $(document).ready( function() { + $.each($(".shuffle-deck"), function(index, deck) { + for(var i = deck.children.length; i >= 0; i--) { + deck.appendChild(deck.children[Math.random() * i | 0]); + } + }); + }); + + $(".select-help-toggle").change(function () { + var id = $(this).attr('id'); + $('.' + id).collapse('hide'); + + $('#' + $(this).val() + '-help.' + id).collapse('show'); + $('#' + $(this).val() + '-instructions.' + id).collapse('show'); + + }); + $('.dropdown-toggle').dropdown(); + + /** + * Adds the default template as value to the regarding email textarea field. + */ + $(".load_template").on('click', function () { + var subject_input_id = $(this).data('subject-input-id'); + var subject_input_text = $(this).data('subject-text'); + var body_input_id = $(this).data('body-input-id'); + var body_input_text = $(this).data('body-text'); + $('#' + subject_input_id).val(subject_input_text); + $('#' + body_input_id).val(body_input_text); + }); + + /** + * Toggle the required attribute on click on_send_email radio button. + */ + $('.send_on_radio').click(function () { + toggle_required_for_mail_subjects($(this)) + }); + + /** + * Adds required attribute to on_send_email radio button if necessary. + */ + $('.send_on_radio').each(function () { + toggle_required_for_mail_subjects($(this)) + }); + /** + * Toggle the required attribute helper function. + */ + function toggle_required_for_mail_subjects($this) { + var name = $this.data('name'); + if ($this.is(':checked')) { + $('#' + name).prop('required', true); + } else { + $('#' + name).removeAttr('required'); + } + } + + $(".comment-reply-link").click(function(){ + $(".comment-reply", $(this).parent()).toggle(); + return false; + }); + + $(".comment-reply").hide(); + $(".user-details-popover").popover(); + $("#comments-div").hide(); + + $('a:contains("Add track")').click(function () { + setTimeout(function () { + $("div.nested-fields:last div:nth-of-type(2) input").val(get_color()); + }, + 5) + }); + + $('a:contains("Add difficulty_level")').click(function () { + setTimeout(function () { + $("div.nested-fields:last div:nth-of-type(3) input").val(get_color()); + }, + 5) + }); + + $('a:contains("Add event_type")').click(function () { + setTimeout(function () { + $("div.nested-fields:last div:nth-of-type(5) input").val(get_color()); + }, + 5) + }); +}); + +function get_color() { + var colors = ['#000000', '#0000FF', '#00FF00', '#FF0000', '#FFFF00', '#9900CC', + '#CC0066', '#00FFFF', '#FF00FF', '#C0C0C0', '#00008B', '#FFD700', + '#FFA500', '#FF1493', '#FF00FF', '#F0FFFF', '#EE82EE', '#D2691E', + '#C0C0C0', '#A52A2A', '#9ACD32', '#9400D3', '#8B008B', '#8B0000', + '#87CEEB', '#808080', '#800080', '#008B8B', '#006400' + ]; + return colors[Math.floor(Math.random() * colors.length)]; +} + +function word_count(text, divId, maxcount) { + var area = document.getElementById(text.id) + + Countable.once(area, function(counter) { + $('#' + divId).text(counter.words); + if (counter.words > maxcount) + $('#' + divId).css('color', 'red'); + else + $('#' + divId).css('color', 'black'); + }); +}; + +function replace_defaut_submission_text(input_selector, new_text, valid_defaults) { + let $area = $(input_selector); + let current_text = $area.val(); + + if (!current_text) { + $area.val(new_text); + $area.trigger('change'); + return; + } + + valid_defaults.some(default_text => { + if (current_text == default_text) { + $area.val(new_text); + $area.trigger('change'); + return true; + } + }); +} + +/* Wait for the DOM to be ready before attaching events to the elements */ +$( document ).ready(function() { + /* Set the minimum and maximum proposal abstract and submission text word length */ + $("#event_event_type_id").change(function () { + var $selected = $("#event_event_type_id option:selected") + var max = $selected.data("max-words"); + var min = $selected.data("min-words"); + + // We replace the default text only if the current field is empty, + // or is set to the default text of another event type. + replace_defaut_submission_text( + '#event_submission_text', + $selected.data("instructions"), + $("#event_event_type_id option").toArray().map(e => $(e).data('instructions')) + ); + + $("#abstract-maximum-word-count").text(max); + $("#submission-maximum-word-count").text(max); + $("#abstract-minimum-word-count").text(min); + $("#submission-minimum-word-count").text(min); + word_count($('#event_abstract').get(0), 'abstract-count', max); + word_count($('#event_submission_text').get(0), 'submission-count', max); + }).trigger('change'); + + /* Count the proposal abstract length */ + $("#event_abstract").on('input', function() { + var $selected = $("#event_event_type_id option:selected") + var max = $selected.data("max-words"); + word_count(this, 'abstract-count', max); + } ); + + /* Count the submission text length */ + $("#event_submission_text").bind('change keyup paste input', function() { + var $selected = $("event_event_type_id option:selected") + var max = $selected.data("max-words"); + word_count(this, 'submission-count', max); + }); + + /* Listen for reset template button, wait for confirm, and reset. */ + $('.js-resetSubmissionText').click((e) => { + let $selected = $("#event_event_type_id option:selected"); + let $this = $(e.target); + let affirm = confirm($this.data('confirm')); + if (affirm) { + let sub_text = $('#event_submission_text'); + sub_text.val($selected.data('instructions')); + sub_text.trigger('change'); + } + }); +}); + +/* Commodity function for modal windows */ + +window.build_dialog = function(selector, content) { + // Close it and remove content if it's already open + $("#" + selector).modal('hide'); + $("#" + selector).remove(); + // Add new content and pops it up + $("body").append("
\n" + content + "
"); + $("#" + selector).modal(); +} diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 000000000..7c23a0d90 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,31 @@ +/* + *= require strap-on + *= require datatables/dataTables.bootstrap + *= require datatables/extensions/Buttons/buttons.dataTables + *= require datatables/extensions/Buttons/buttons.bootstrap + + *= require selectize + *= require selectize.bootstrap3 + *= require bootstrap-markdown + *= require bootstrap-datetimepicker + *= require bootstrap-select + + *= require osem + *= require osem-rating + *= require osem-schedule + *= require osem-dashboard + *= require osem-splash + *= require osem-fonts + *= require bootstrap-markdown + *= require bootstrap-datetimepicker + *= require leaflet + *= require bootstrap3-switch + *= require osem-payments + *= require osem-navbar + *= require selectize + *= require selectize.bootstrap3 + *= require bootstrap-select + *= require conferences + + *= require fullcalendar-scheduler/main.css +*/ diff --git a/app/assets/stylesheets/breakpoints.scss b/app/assets/stylesheets/breakpoints.scss new file mode 100644 index 000000000..d3dbc7998 --- /dev/null +++ b/app/assets/stylesheets/breakpoints.scss @@ -0,0 +1,21 @@ +@mixin breakpoint($class) { + @if $class == xs { + @media (max-width: 767px) { @content; } + } + + @else if $class == sm { + @media (min-width: 768px) and (max-width: 991px) { @content; } + } + + @else if $class == md { + @media (min-width: 992px) and (max-width: 1199px) { @content; } + } + + @else if $class == lg { + @media (min-width: 1200px) { @content; } + } + + @else { + @warn "Breakpoint mixin supports: xs, sm, md, lg"; + } +} diff --git a/app/assets/stylesheets/conferences.scss b/app/assets/stylesheets/conferences.scss new file mode 100644 index 000000000..fbf950cda --- /dev/null +++ b/app/assets/stylesheets/conferences.scss @@ -0,0 +1,13 @@ +/* Apply CSS to views of a specific conference by encapsulating the style in + * a body tag classed for the conference's short title. + * See the example here for 'osemdemo'. + * NOTE: when overriding existing CSS, it may be necessary to use !important to + * bypass CSS specificity rules. +*/ + +body.conference-osemdemo { + // style here only applies when working with the 'osemdemo' conference. + #header { + background-color: #b58e73 !important; + } +} diff --git a/app/assets/stylesheets/jquery-ui-timepicker-addon.css b/app/assets/stylesheets/jquery-ui-timepicker-addon.css new file mode 100644 index 000000000..b93a85f62 --- /dev/null +++ b/app/assets/stylesheets/jquery-ui-timepicker-addon.css @@ -0,0 +1,10 @@ +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +.ui-timepicker-div dl { text-align: left; } +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } +.ui-timepicker-div td { font-size: 90%; } +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } + +.ui-timepicker-rtl{ direction: rtl; } +.ui-timepicker-rtl dl { text-align: right; } +.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; } \ No newline at end of file diff --git a/app/assets/stylesheets/osem-dashboard.scss b/app/assets/stylesheets/osem-dashboard.scss new file mode 100644 index 000000000..fbab73114 --- /dev/null +++ b/app/assets/stylesheets/osem-dashboard.scss @@ -0,0 +1,65 @@ +@import "breakpoints.scss"; + +.dashbox span { + margin-bottom: 2px; + + @include breakpoint(xs) { + font-size: 1.5em; + } + @include breakpoint(sm) { + font-size: 2em; + } + @include breakpoint(md) { + font-size: 3.5em; + } + @include breakpoint(lg) { + font-size: 5em; + } +} + +.dashbox.panel { + padding: 10px; + display: -webkit-flex; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + line-height: 1.6rem; + + label { + line-height: 2rem; + } +} + +.todolist-missing span { + color: #ff0000; +} + +.todolist-ok { + text-decoration: line-through; +} + +.todolist-ok span { + color: #02A10F; +} + +.top-submitter img { + margin: 0 auto; +} + +#doughnut .tab-content { + padding-bottom: 20px; +} + +#submissions { + padding-bottom: 20px; +} + +.conferenceCheckboxes div { + display: inline-block; + padding: 0px 10px 0px 0px; +} + +.margin-event-table{ + margin-top: 40px !important; +} diff --git a/app/assets/stylesheets/osem-datatables.scss b/app/assets/stylesheets/osem-datatables.scss new file mode 100644 index 000000000..4f640f49b --- /dev/null +++ b/app/assets/stylesheets/osem-datatables.scss @@ -0,0 +1,21 @@ +table.datatable { + @extend .table, .table-striped, .table-bordered, .table-hover; + + td.actions { + vertical-align: middle; + align: center; + + div.btn-group { + display: flex; + } + + a { + @extend .btn; + } + } + + td.truncate { + max-width: 1px; + @include text-overflow(); + } +} diff --git a/app/assets/stylesheets/osem-fonts.scss b/app/assets/stylesheets/osem-fonts.scss new file mode 100644 index 000000000..9ce1c5bfb --- /dev/null +++ b/app/assets/stylesheets/osem-fonts.scss @@ -0,0 +1,2 @@ +@import "font-awesome"; + diff --git a/app/assets/stylesheets/osem-navbar.scss b/app/assets/stylesheets/osem-navbar.scss new file mode 100644 index 000000000..4df2423f9 --- /dev/null +++ b/app/assets/stylesheets/osem-navbar.scss @@ -0,0 +1,58 @@ +@import "breakpoints.scss"; +@import "osem-variables.scss"; + +.nav-osem { + border: none; + @include breakpoint(xs) { + .navbar-collapse { + background-color: white; + border-bottom: 2px solid #efefef; + } + .navbar-nav > li > a { + color: black; + } + .navbar-collapse.in { + overflow: hidden; + max-height: none; + } + .navbar-toggle .icon-bar { + background-color: white; + } + .navbar-nav .open .dropdown-toggle:focus { + color: black; + background-color: #eeeeee; + } + .btn-group { + width: 100%; + text-align: center; + } + .navbar-nav .open .dropdown-menu > li > a { + color: black; + } + } + .dropdown-menu { + min-width: 225px; + } + + &.navbar-default { + .navbar-nav > .open > a { + &:hover, &:focus { + color: $navbar-default-link-color; + } + } + } + + .navbar-brand img { + max-height: 100%; + } + + .trapezoid { + border-top-color: $navbar-default-bg; + } + + .profile-thumbnail { + border-radius: 3px; + max-height: 20px; + max-width: 20px; + } +} diff --git a/app/assets/stylesheets/osem-payments.scss b/app/assets/stylesheets/osem-payments.scss new file mode 100644 index 000000000..cfa451b40 --- /dev/null +++ b/app/assets/stylesheets/osem-payments.scss @@ -0,0 +1,3 @@ +.stripe-button-el { + float: right; +} diff --git a/app/assets/stylesheets/osem-rating.scss b/app/assets/stylesheets/osem-rating.scss new file mode 100644 index 000000000..b4a43c28f --- /dev/null +++ b/app/assets/stylesheets/osem-rating.scss @@ -0,0 +1,17 @@ +/* Styling for voting on proposals*/ +.rating { + background: image-url("star.png") 0 0; + background-size: 20px; + width: 20px; + height: 20px; + display: inline-block; + float: left; + + &.bright { + background-image: image-url("star-bright.png"); + } + + &.glow { + background-image: image-url("star-glow.png"); + } +} diff --git a/app/assets/stylesheets/osem-schedule.scss b/app/assets/stylesheets/osem-schedule.scss new file mode 100644 index 000000000..f722a41e9 --- /dev/null +++ b/app/assets/stylesheets/osem-schedule.scss @@ -0,0 +1,187 @@ +.room-name { + font-weight: bold; + padding:10px; + margin-top: 30px; + border: 1px solid #848484; + background-color: #E6E6E6; + + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + height: 40px; + line-height: 25px; + overflow: hidden; +} + +.schedule-room-slot { + padding: 2px 3px; + border: 1px solid #848484; + height: 58px; + line-height: 20px; + font-size: 13px; +} + +.schedule-event { + padding: 7px; + border: 2px solid #151515; + position:relative; + z-index:1; + cursor: move; + + a { + color: black; + } +} + +.schedule-room-slot.compact { + line-height: 12px; + font-size: 10px; + padding: 1px; +} + +// When an event is in a room. +.schedule-room-slot .schedule-event.compact { + margin-top: -14px; + width: 85%; + margin-left: 15%; +} + +.schedule-event.compact { + font-size: 75%; + padding: 0; + border-width: 1px; + + .schedule-event-text { + line-height: 12px; + overflow: hidden; + } +} + +.schedule-event-text { + display: -webkit-box; + text-overflow: ellipsis; + -webkit-box-orient: vertical; + line-height: 23px; + overflow: hidden; +} + +.schedule-event.compact { + .schedule-event-delete-button { + padding: 1px; + margin-right: 3px; + } +} + +.schedule-event-delete-button { + font-weight: bold; + cursor: pointer; + padding: 0 3px; + margin-right: 5px; + color: black; +} + + +.flexvideo { + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + padding-top: 25px; + height: 0; +} +.flexvideo iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.speakerinfo { + margin-top: 40px; +} + +.speakerbio { + margin-top: 10px; +} + + +.program-dropdown { + margin-left: 20%; + margin-right: 20%; + margin-top: 10px; + margin-bottom: 20px; +} + +.program-dropdown > button, +.program-dropdown > .dropdown-menu { + width: 100%; +} + +.elipsis{ + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; +} + +.break-words{ + /* These are technically the same, but use both */ + overflow-wrap: break-word; + word-wrap: break-word; + + /* Warning: Needed for oldIE support, but words are broken up letter-by-letter */ + -ms-word-break: break-all; + word-break: break-all; + + /* Non standard for webkit */ + word-break: break-word; + + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +.date-title{ + font-size: 23px; + font-weight: bold; + padding-bottom: 2px; + display:inline-block; +} + +.date-content { + border-bottom: 1px solid #6E6E6E; + margin: 30px 0 20px; +} + +.unscheduled-event{ + margin-top: 8px; +} + +.track { + padding: 0px 5px 0px 5px; + display: inline-block; + } + +.no-events-day { + color: #D8D8D8 !important; +} + +.non_schedulable{ + opacity: 0.5; +} + +.event-panel-title { + margin: auto 3px; + flex: 1; + line-height: 1.4; + + // Override some bootstrap + small { + line-height: 1.6; + } +} + +// Override Boostrap setting. +h3.event-panel-title small { + line-height: 1.4; +} diff --git a/app/assets/stylesheets/osem-splash.scss b/app/assets/stylesheets/osem-splash.scss new file mode 100644 index 000000000..15354cfc2 --- /dev/null +++ b/app/assets/stylesheets/osem-splash.scss @@ -0,0 +1,213 @@ +@import "breakpoints.scss"; +@import "osem-variables.scss"; + +#splash { + // Counter the general padding for #content + margin-bottom: -65px; + + section { + padding-top: 60px; + padding-bottom: 60px; + + .trapezoid { + top: 60px + $trapezoid-height; + } + } + + section:nth-child(even) { + background: none repeat scroll 0 0 #eee; + color: #333; + + .trapezoid { + border-top-color: #eee; + } + } + section:nth-child(odd) { + background: none repeat scroll 0 0 #ffffff; + color: #333; + .thumbnail { + background: none repeat scroll 0 0 #e0e0e0; + } + + .trapezoid { + border-top-color: #fff; + } + } + + .cta-button { + padding-top: 30px; + } + + #banner{ + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + margin-top: -10px; + padding-top: 100px; + padding-bottom: 100px; + .container { + #header-no-image { + background-color: rgba(224, 224, 224,.80); + padding-top: 20px; + padding-bottom: 20px; + } + + #header-image { + color: #FFF; + } + } + } + + #program { + h3 { + padding-left: 5px; + } + .track { + padding: 20px; + } + .track:hover{ + background-color: #F0FFFF; + } + } + + #callforpapers { + .timer-box{ + width: 130px; + margin: 35px 24px 20px 0; + border-radius: 50%; + border:4px solid #989797; + } + } + + #venue { + padding-top: 0px; + padding-bottom: 0px; + #venue-pic { + margin-top: 20px; + } + // The venue section has less padding. + .trapezoid { + top: $trapezoid-height; + } + } + + #lodging { + @include breakpoint(xs) { + .img-lodging{ + max-width: 280px; + } + } + @include breakpoint(sm) { + .img-lodging{ + max-width: 200px; + } + } + @include breakpoint(sm) { + .img-lodging{ + max-width: 260px; + } + } + @include breakpoint(lg) { + .img-lodging{ + max-width: 300px; + max-height: 200px; + } + } + } + + #tickets { + a { + &:hover, &:focus { + text-decoration: none; + } + } + } + + #sponsors { + .img-sponsor { + max-height: 100px; + margin: 2% auto; + } + .img-sponsor-1 { + max-height: 200px; + } + .img-sponsor-2 { + max-height: 150px; + } + .img-sponsor-3 { + max-height: 120px; + } + @include breakpoint(xs) { + .img-sponsor, .img-sponsor-1, .img-sponsor-3, .img-sponsor-3{ + max-height: unset; + max-width: 280px; + } + } + } + + .social-media.trapezoid { + border-top-color: #0C3559; // TODO: Use @conference color? + } + + #social-media{ + background: none repeat scroll 0 0 #0C3559; + padding: 50px 20px; + i{ + color: #FFF; + } + i:hover{ + color: #F2E205; + } + a{ + padding-left: 100px; + } + a:nth-child(-n+1) { + padding-left: 0px; + } + @include breakpoint(xs) { + a{ + padding-left: 10px; + } + } + } + + .scroll-top-wrapper { + position: fixed; + opacity: 0; + visibility: hidden; + overflow: hidden; + text-align: center; + z-index: 99999999; + background-color: #424242; + width: 50px; + height: 48px; + line-height: 48px; + right: 30px; + bottom: 30px; + padding-top: 2px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + -webkit-transition: all 0.5s ease-in-out; + -moz-transition: all 0.5s ease-in-out; + -ms-transition: all 0.5s ease-in-out; + -o-transition: all 0.5s ease-in-out; + transition: all 0.5s ease-in-out; + i.fa-solid { + line-height: inherit; + } + // &.show { + // visibility:visible; + // cursor:pointer; + // opacity: 1.0; + // } + a { + color: white; + } + } +} + +.publicorprivate { + padding-top: 30px; +} diff --git a/app/assets/stylesheets/osem-variables.scss b/app/assets/stylesheets/osem-variables.scss new file mode 100644 index 000000000..1ae23c039 --- /dev/null +++ b/app/assets/stylesheets/osem-variables.scss @@ -0,0 +1,14 @@ +// This file is included *early* in CSS order! +// These variables change bootstrap defaults. + +$navbar-default-bg: #0C3559; +$navbar-default-link-active-bg: rgb(8,8,8); +$navbar-default-border: 1px solid #d4d4d4; +$navbar-default-color: #FFF; +$navbar-default-link-color: #FFF; +$navbar-default-link-hover-color: #F2E205; +$navbar-default-link-active-hover-color: #FFF; + + +// The hiegh of the section tabs that are like Snap! blocks. +$trapezoid-height: 14px; diff --git a/app/assets/stylesheets/osem.scss b/app/assets/stylesheets/osem.scss new file mode 100644 index 000000000..4c7c28988 --- /dev/null +++ b/app/assets/stylesheets/osem.scss @@ -0,0 +1,145 @@ +@import "osem-variables"; +@import "bootstrap/mixins"; + +html { + position: relative; + min-height: 100%; +} + +body { + // Specifically remove Helevtica Neue from BS3 stack. + font-family: sans-serif; + /* Margin bottom by 2 times the footer height */ + margin-bottom: 85px; + padding-top: 60px; + font-size: 16px; +} + +#content { + padding-bottom: 60px; +} + +// Designed to be the last element in a section / nav +// Makes a little "puzzle piece" connector. +.trapezoid { + position: relative; + margin-left: 60px; + border-top: $trapezoid-height solid; + border-left: 9px solid transparent; + border-right: 9px solid transparent; + height: 0; + width: 64px; + margin-top: -1 * $trapezoid-height; + top: $trapezoid-height; + z-index: 1000; +} + +#footer { + position: absolute; + bottom: 0; + width: 100%; + background-color: #f5f5f5; + .container { + padding: 15px; + } +} + +.nav-tabs { + margin-bottom: 15px; +} + +.img-center { + margin: 0 auto; +} + +/* centered columns styles */ +.row-centered { + text-align:center; +} +.col-centered { + display:inline-block; + float:none; + /* reset the text-align */ + text-align:left; + /* inline-block space fix */ + margin-right:-4px; +} + +.col-top { + vertical-align:top; +} + +fieldset { + margin: 20px 0 20px 0; +} + +.bootstrap-switch { + height: 1.7em +} + +.comment-reply{ + padding-top: 40px; +} + +p.comment-body { + padding-top: 20px; +} + +.well.comment-section { + padding-bottom: 40px; +} + +#account-already { + font-size: 0.6em; +} + +/* comments views pane: use padding-bottom before next comment box */ +.panel.panel-default .panel-body .notifications { + padding-bottom: 20px; +} + +/* comments views pane: use padding-bottom after each comment */ +.panel.panel-default .panel-body .notifications p { + padding-bottom: 20px; +} + +#proposal-info div dt, #proposal-info div dd { + display: inline-block; +} + +.changeset{ + display: none; +} + +.box{ + height: 230px; +} + +/* sidebar hamburger btn */ +.side-nav-btn{ + margin-left: 10px; + float: left; + } + +.qr-image{ + margin-left: 120px; +} + +.g-recaptcha { + @include clearfix; + padding-bottom: 12px; + + div { + float: right; + } +} + +/* omniauth btn grp */ +#openid-btn-grp{ + display: flex; + justify-content: center; +} + +.word_break { + word-wrap: break-word; +} diff --git a/app/assets/stylesheets/strap-on.scss b/app/assets/stylesheets/strap-on.scss new file mode 100644 index 000000000..bd2d3b38b --- /dev/null +++ b/app/assets/stylesheets/strap-on.scss @@ -0,0 +1,60 @@ +// Place bootstrap variable customizations in the file below. +@import "osem-variables"; +@import 'bootstrap-datetimepicker'; +@import "bootstrap-sprockets"; + +// +// Bootstrap components +// +// Core variables and mixins +@import "bootstrap/variables"; +@import "bootstrap/mixins"; + +// Reset and dependencies +@import "bootstrap/normalize"; +@import "bootstrap/print"; +@import "bootstrap/glyphicons"; + +// Core CSS +@import "bootstrap/scaffolding"; +@import "bootstrap/type"; +@import "bootstrap/code"; +@import "bootstrap/grid"; +@import "bootstrap/tables"; +@import "bootstrap/forms"; +@import "bootstrap/buttons"; + +// Components +@import "bootstrap/component-animations"; +@import "bootstrap/dropdowns"; +@import "bootstrap/button-groups"; +@import "bootstrap/input-groups"; +@import "bootstrap/navs"; +@import "bootstrap/navbar"; +@import "bootstrap/breadcrumbs"; +@import "bootstrap/pagination"; +@import "bootstrap/pager"; +@import "bootstrap/labels"; +@import "bootstrap/badges"; +@import "bootstrap/jumbotron"; +@import "bootstrap/thumbnails"; +@import "bootstrap/alerts"; +@import "bootstrap/progress-bars"; +@import "bootstrap/media"; +@import "bootstrap/list-group"; +@import "bootstrap/panels"; +@import "bootstrap/responsive-embed"; +@import "bootstrap/wells"; +@import "bootstrap/close"; + +// Components w/ JavaScript +@import "bootstrap/modals"; +@import "bootstrap/tooltip"; +@import "bootstrap/popovers"; + +// Utility classes +@import "bootstrap/utilities"; +@import "bootstrap/responsive-utilities"; + +// semantic classes +@import "osem-datatables"; diff --git a/app/helpers/admin/cfps_helper.rb b/app/helpers/admin/cfps_helper.rb new file mode 100644 index 000000000..7b62fc20d --- /dev/null +++ b/app/helpers/admin/cfps_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Admin + module CfpsHelper + def cfp_form_url(cfp, conference) + if cfp.new_record? + admin_conference_program_cfps_path + else + admin_conference_program_cfp_path(conference, cfp) + end + end + end +end diff --git a/app/helpers/admin/volunteers_helper.rb b/app/helpers/admin/volunteers_helper.rb new file mode 100644 index 000000000..cf0ed7129 --- /dev/null +++ b/app/helpers/admin/volunteers_helper.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Admin + module VolunteersHelper + def can_manage_volunteers?(conference) + current_user.has_cached_role?(:organizer, + conference) || current_user.has_cached_role?(:volunteers_coordinator, conference) + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 000000000..de02aed90 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +DEFAULT_LOGO = Rails.configuration.conference[:default_logo_filename] + +# TODO-SNAPCON: Refactor this module. Move chunks to a dates_help, some events_helper +module ApplicationHelper + include Pagy::Frontend + # Returns a string build from the start and end date of the given conference. + # + # If the conference is only one day long + # * %B %d %Y (January 17 2014) + # If the conference starts and ends in the same month and year + # * %B %d - %d, %Y (January 17 - 21 2014) + # If the conference ends in another month but in the same year + # * %B %d - %B %d, %Y (January 31 - February 02 2014) + # All other cases + # * %B %d, %Y - %B %d, %Y (December 30, 2013 - January 02, 2014) + def date_string(start_date, end_date) + startstr = 'Unknown - ' + endstr = 'Unknown' + # When the conference is in the same month + if start_date.month == end_date.month && start_date.year == end_date.year + if start_date.day == end_date.day + startstr = start_date.strftime('%B %d') + endstr = end_date.strftime(' %Y') + else + startstr = start_date.strftime('%B %d - ') + endstr = end_date.strftime('%d, %Y') + end + elsif start_date.month != end_date.month && start_date.year == end_date.year + startstr = start_date.strftime('%B %d - ') + endstr = end_date.strftime('%B %d, %Y') + else + startstr = start_date.strftime('%B %d, %Y - ') + endstr = end_date.strftime('%B %d, %Y') + end + + startstr + endstr + end + + # Returns time with conference timezone + def time_with_timezone(time) + time.strftime('%F %R') + ' ' + @conference.timezone.to_s + end + + # Set resource_name for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposals#new) + def resource_name + :user + end + + def add_association_link(association_name, form_builder, div_class, html_options = {}) + link_to_add_association 'Add ' + association_name.to_s.singularize, form_builder, div_class, + html_options.merge(class: 'assoc btn btn-success') + end + + def remove_association_link(association_name, form_builder) + link_to_remove_association('Remove ' + association_name.to_s.singularize, form_builder, + class: 'assoc btn btn-danger') + tag.hr + end + + def dynamic_association(association_name, title, form_builder, options = {}) + render 'shared/dynamic_association', association_name: association_name, title: title, f: form_builder, +hint: options[:hint] + end + + def tracks(conference) + conference.confirmed_tracks.collect(&:name).to_sentence + end + + def difficulty_levels(conference) + conference.program.difficulty_levels.map(&:title).to_sentence + end + + def unread_notifications(user) + Comment.accessible_by(current_ability).find_since_last_login(user) + end + + # Receives a PaperTrail::Version object + # Outputs the list of attributes that were changed in the version (ignoring changes from one blank value to another) + # Eg: If version.changeset = '{"title"=>[nil, "Premium"], "description"=>[nil, "Premium = Super cool"], "conference_id"=>[nil, 3]}' + # Output will be 'title, description and conference' + def updated_attributes(version) + version.changeset + .reject { |_, values| values[0].blank? && values[1].blank? } + .keys.map { |key| key.gsub('_id', '').tr('_', ' ') }.join(', ') + .reverse.sub(',', ' dna ').reverse + end + + def normalize_array_length(hashmap, length) + hashmap.each_value do |value| + value.fill(value[-1], value.length...length) if value.length < length + end + end + + # TODO: Move to the event model. + def concurrent_events(event) + return nil unless event.scheduled? && event.program.selected_event_schedules + + event_schedule = event.program.selected_event_schedules.find { |es| es.event == event } + other_event_schedules = event.program.selected_event_schedules.reject do |other_event_schedule| + other_event_schedule == event_schedule + end + concurrent_events = [] + + event_time_range = (event_schedule.start_time.strftime '%Y-%m-%d %H:%M')...(event_schedule.end_time.strftime '%Y-%m-%d %H:%M') + other_event_schedules.each do |other_event_schedule| + next unless other_event_schedule.event.confirmed? + + other_event_time_range = (other_event_schedule.start_time.strftime '%Y-%m-%d %H:%M')...(other_event_schedule.end_time.strftime '%Y-%m-%d %H:%M') + concurrent_events << other_event_schedule.event if (event_time_range.to_a & other_event_time_range.to_a).present? + end + concurrent_events.sort_by { |schedule| schedule.room&.order } + end + + def speaker_links(event) + safe_join(event.speakers.map { |speaker| link_to speaker.name, admin_user_path(speaker) }, ',') + end + + def volunteer_links(event) + safe_join(event.volunteers.map do |volunteer| + link_to(volunteer.name, admin_user_path(volunteer)) + end, ', ') + end + + def event_types_sentence(conference) + conference.event_types.map { |et| et.title.pluralize }.to_sentence + end + + def sign_in_path + if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' + new_user_ichain_session_path + else + new_user_session_path + end + end + + def rescheduling_hint(affected_event_count) + if affected_event_count > 0 + "You have #{affected_event_count} scheduled #{'event'.pluralize(affected_event_count)}. Changing the conference hours will unschedule those scheduled outside the conference hours." + end + end + + ## + # ====Gets + # a conference object + # ==== Returns + # class hidden if conference is over + def hidden_if_conference_over(conference) + 'hidden' if Date.today > conference.end_date + end + + # TODO-SNAPCON: Replace this with a search for a conference logo. + # TODO: If conference is defined, the alt text should be conference name. + def nav_root_link_for(conference = nil) + path = conference&.id.present? ? conference_path(conference) : root_path + link_to( + image_tag(conference_logo_url(conference), alt: nav_link_text(conference)), + path, + class: 'navbar-brand', + title: nav_link_text(conference) + ) + end + + # TODO-SNAPCON: This should be the conference title. + def nav_link_text(conference = nil) + conference.try(:organization).try(:name) || ENV.fetch('OSEM_NAME', 'OSEM') + end + + # TODO: Consider Renaming this? + # TODO: Allow passing in an organization + def conference_logo_url(conference = nil) + return DEFAULT_LOGO unless conference + + if conference.picture.present? + conference.picture.thumb.url + elsif conference.organization&.picture.present? + conference.organization.picture.thumb.url + else + DEFAULT_LOGO + end + end + + # returns the url to be used for logo on basis of sponsorship level position + def get_logo(object) + if object.try(:sponsorship_level) + if object.sponsorship_level.position == 1 + object.picture.first.url + elsif object.sponsorship_level.position == 2 + object.picture.second.url + else + object.picture.others.url + end + else + object.picture.large.url + end + end + + # Embed links with a localized timezone URL + # Timestamps are stored at UTC but in the real timezone. + # We must convert then shift the time back to get the correct value. + # TODO: just take in an object? + def inyourtz(time, timezone, &block) + time = time.in_time_zone(timezone) + time -= time.utc_offset + link_to "https://inyourtime.zone/t?#{time.to_i}", target: '_blank', rel: 'noopener' do + block.call + end + end +end diff --git a/app/helpers/chart_helper.rb b/app/helpers/chart_helper.rb new file mode 100644 index 000000000..ac350dceb --- /dev/null +++ b/app/helpers/chart_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ChartHelper + def chart_values(distribution_hash) + distribution_hash.collect do |key, data| + [key, data['value']] + end.to_h + end + + def chart_colors(distribution_hash) + distribution_hash.collect do |_key, data| + data['color'] + end + end +end diff --git a/app/helpers/conference_helper.rb b/app/helpers/conference_helper.rb new file mode 100644 index 000000000..d393b6099 --- /dev/null +++ b/app/helpers/conference_helper.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module ConferenceHelper + # Return true if only call_for_papers or call_for_tracks or call_for_booths is open + def one_call_open(*calls) + calls.one? { |call| call.try(:open?) } + end + # Return true if exactly two of those calls are open: call_for_papers , call_for_tracks , call_for_booths + + def two_calls_open(*calls) + calls.count { |call| call.try(:open?) } == 2 + end + + # URL for sponsorship emails + def sponsorship_mailto(conference) + [ + 'mailto:', + conference.contact.sponsor_email, + '?subject=', + url_encode(conference.short_title), + '%20Sponsorship' + ].join + end + + def short_ticket_description(ticket) + return unless ticket.description + + markdown(ticket.description.split("\n").first&.strip) + end + + def conference_color(conference) + conference.color.presence || Rails.configuration.conference[:default_color] + end + + # adds events to icalendar for proposals in a conference + def icalendar_proposals(calendar, proposals, conference) + proposals.each do |proposal| + calendar.event do |e| + e.dtstart = proposal.time + e.dtend = proposal.time + (proposal.event_type.length * 60) + e.duration = "PT#{proposal.event_type.length}M" + e.created = proposal.created_at + e.last_modified = proposal.updated_at + e.summary = proposal.title + e.description = proposal.abstract + e.uid = proposal.guid + e.url = conference_program_proposal_url(conference.short_title, proposal.id) + v = conference.venue + if v + e.geo = v.latitude, v.longitude if v.latitude && v.longitude + location = '' + location += "#{proposal.room.name} - " if proposal.room.name + location += " - #{v.street}, " if v.street + location += "#{v.postalcode} #{v.city}, " if v.postalcode && v.city + location += "#{v.country_name}, " if v.country_name + e.location = location + end + e.categories = conference.title, "Difficulty: #{proposal.difficulty_level.title}", + "Track: #{proposal.track.name}" + end + end + calendar + end + + def get_happening_now_events_schedules(conference) + events_schedules = filter_events_schedules(conference, :happening_now?) + events_schedules ||= [] + events_schedules + end + + def get_happening_next_events_schedules(conference) + events_schedules = filter_events_schedules(conference, :happening_later?) + + return [] if events_schedules.empty? + + # events_schedules have been sorted by start_time in selected_event_schedules + happening_next_time = events_schedules[0].start_time + events_schedules.select { |s| s.start_time == happening_next_time } + end + + def load_happening_now + events_schedules_list = get_happening_now_events_schedules(@conference) + @is_happening_next = false + if events_schedules_list.empty? + events_schedules_list = get_happening_next_events_schedules(@conference) + @is_happening_next = true + end + @events_schedules_limit = Rails.configuration.conference[:events_per_page] + @events_schedules_length = events_schedules_list.length + @pagy, @events_schedules = pagy_array(events_schedules_list, + items: @events_schedules_limit, + link_extra: 'data-remote="true"') + end + + private + + # TODO: Move this to using the cached method on program/schedule + def filter_events_schedules(conference, filter) + conference.program.selected_event_schedules( + includes: [:event, :room, { event: + %i[event_type speakers speaker_event_users track program] }] + ).select(&filter) + end +end diff --git a/app/helpers/date_time_helper.rb b/app/helpers/date_time_helper.rb new file mode 100644 index 000000000..420b086a8 --- /dev/null +++ b/app/helpers/date_time_helper.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module DateTimeHelper + ## + # Includes functions related to date or time manipulations + ## + ## + # Gets an EventType object, and returns its length in timestamp format (HH:MM) + # ====Gets + # * +Integer+ -> 30 + # ====Returns + # * +String+ -> "00:30" + def length_timestamp(length) + [length / 60, length % 60].map { |t| t.to_s.rjust(2, '0') }.join(':') + end + + ## + # Gets a datetime object + # ====Returns + # * +String+ -> formated datetime object + def format_datetime(obj) + return unless obj + + obj.strftime('%Y-%m-%d %H:%M') + end + + def show_time(length) + return '0 h 0 min' if length.blank? + + h, min = length.divmod(60) + + if h == 0 + "#{min.round} min" + elsif min == 0 + "#{h} h" + else + "#{h} h #{min.round} min" + end + end +end diff --git a/app/helpers/event_types_helper.rb b/app/helpers/event_types_helper.rb new file mode 100644 index 000000000..f6e4abc11 --- /dev/null +++ b/app/helpers/event_types_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module EventTypesHelper + ## + # Includes functions related to event_types + ## + ## + # ====Returns + # * +String+ -> number of registrations / max allowed registrations + def event_type_select_options(event_types = {}) + event_types.map do |type| + [ + "#{type.title} - #{show_time(type.length)}", + type.id, + { data: { + min_words: type.minimum_abstract_length, + max_words: type.maximum_abstract_length, + instructions: type.submission_instructions + } } + ] + end + end +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb new file mode 100644 index 000000000..921edf50e --- /dev/null +++ b/app/helpers/events_helper.rb @@ -0,0 +1,324 @@ +# frozen_string_literal: true + +# TODO: Split this module into smaller modules +# rubocop:disable Metrics/ModuleLength +module EventsHelper + ## + # Includes functions related to events + ## + ## + # ====Returns + # * +String+ -> number of registrations / max allowed registrations + def registered_text(event) + return "Registered: #{event.registrations.count}/#{event.max_attendees}" if event.max_attendees + + "Registered: #{event.registrations.count}" + end + + # TODO-SNAPCON: Move to admin helper + def rating_stars(rating, max, options = {}) + Array.new(max) do |counter| + content_tag( + 'label', + '', + class: "rating#{' bright' if rating.to_f > counter}", + **options + ) + end.join.html_safe + end + + # TODO-SNAPCON: Move to admin helper + def rating_fraction(rating, max, options = {}) + content_tag('span', "#{rating}/#{max}", **options) + end + + def replacement_event_notice(event_schedule, styles: '') + if event_schedule.present? && event_schedule.replacement?(@withdrawn_event_schedules) + replaced_event = event_schedule.replaced_event_schedule.try(:event) + content_tag :span do + concat content_tag :span, 'Please note that this event replaces ' + concat link_to replaced_event.title, + conference_program_proposal_path(@conference.short_title, replaced_event.id), style: styles + end + end + end + + def canceled_replacement_event_label(event, event_schedule, *label_classes) + if event.state == 'canceled' || event.state == 'withdrawn' + content_tag :span, 'CANCELED', class: (%w[label label-danger] + label_classes) + elsif event_schedule.present? && event_schedule.replacement?(@withdrawn_event_schedules) + content_tag :span, 'REPLACEMENT', class: (%w[label label-info] + label_classes) + end + end + + def rating_tooltip(event, max_rating) + "#{event.average_rating}/#{max_rating}, #{pluralize(event.voters.length, 'vote')}" + end + + # TODO-SNAPCON: Move to admin helper + def event_type_dropdown(event, event_types, conference_id) + selection = event.event_type.try(:title) || 'Event Type' + options = event_types.collect do |event_type| + [ + event_type.title, + admin_conference_program_event_path( + conference_id, + event, + event: { event_type_id: event_type.id } + ) + ] + end + active_dropdown(selection, options) + end + + # TODO-SNAPCON: Move to admin helper + def track_dropdown(event, tracks, conference_id) + selection = event.track.try(:name) || 'Track' + options = tracks.collect do |track| + [ + track.name, + admin_conference_program_event_path( + conference_id, + event, + event: { track_id: track.id } + ) + ] + end + active_dropdown(selection, options) + end + + # TODO-SNAPCON: Move to admin helper + def difficulty_dropdown(event, difficulties, conference_id) + selection = event.difficulty_level.try(:title) || 'Difficulty' + options = difficulties.collect do |difficulty| + [ + difficulty.title, + admin_conference_program_event_path( + conference_id, + event, + event: { difficulty_level_id: difficulty.id } + ) + ] + end + active_dropdown(selection, options) + end + + # TODO-SNAPCON: Move to admin helper + def state_dropdown(event, conference_id, email_settings) + selection = event.state.humanize + options = [] + if event.transition_possible? :accept + options << [ + 'Accept', + accept_admin_conference_program_event_path(conference_id, event) + ] + if email_settings.send_on_accepted? + options << [ + 'Accept (without email)', + accept_admin_conference_program_event_path( + conference_id, + event, + send_mail: false + ) + ] + end + end + if event.transition_possible? :reject + options << [ + 'Reject', + reject_admin_conference_program_event_path(conference_id, event) + ] + if email_settings.send_on_rejected? + options << [ + 'Reject (without email)', + reject_admin_conference_program_event_path( + conference_id, + event, + send_mail: false + ) + ] + end + end + if event.transition_possible? :restart + options << [ + 'Start review', + restart_admin_conference_program_event_path(conference_id, event) + ] + end + if event.transition_possible? :confirm + options << [ + 'Confirm', + confirm_admin_conference_program_event_path(conference_id, event) + ] + end + if event.transition_possible? :cancel + options << [ + 'Cancel', + cancel_admin_conference_program_event_path(conference_id, event) + ] + end + active_dropdown(selection, options) + end + + # TODO-SNAPCON: Move to admin helper + def event_switch_checkbox(event, attribute, conference_id) + check_box_tag( + conference_id, + event.id, + event.send(attribute), + url: admin_conference_program_event_path( + conference_id, + event, + event: { attribute => nil } + ), + class: 'switch-checkbox' + ) + end + + def event_favourited?(event, current_user) + return false unless current_user + + event.favourite_users.exists?(current_user.id) + end + + # TODO-SNAPCON: These need to be refactored. + # It's not clear which should be an object vs when to use a tz string. + def display_timezone(user, conference) + return conference.timezone unless user + + user.timezone.presence || conference.timezone + end + + def timezone_offset(object) + Time.now.in_time_zone(object.timezone).utc_offset / 1.hour + end + + def timezone_text(object) + Time.now.in_time_zone(object.timezone).strftime('%Z') + end + + # timezone: Eastern Time (US & Canada) (UTC -5) + def timezone_mapping(timezone) + return unless timezone + + offset = Time.now.in_time_zone(timezone).utc_offset / 1.hour + text = Time.now.in_time_zone(timezone).strftime('%Z') + "#{text} (UTC #{offset})" + end + + def convert_timezone(date, old_timezone, new_timezone) + if date && old_timezone && new_timezone + date.strftime('%Y-%m-%dT%H:%M:%S').in_time_zone(old_timezone).in_time_zone(new_timezone) + end + end + + def join_event_link(event, event_schedule, current_user, small: false) + return unless current_user && event_schedule && event_schedule.room_url.present? + return if event.ended? + + conference = event.conference + is_now = event_schedule.happening_now? # 30 minute threshold. + is_registered = conference.user_registered?(current_user) + admin = current_user.roles.where(id: conference.roles).any? + # is_presenter = event.speakers.include?(current_user) || event.volunteers.include?(current_user) + + if admin || (is_now && is_registered) + link_to("Join Event Now #{'(Early)' unless is_now}", + join_conference_program_proposal_path(conference, event), + target: '_blank', class: "btn btn-primary #{'btn-xs' if small}", + 'aria-label': "Join #{event.title}", rel: 'noopener') + elsif is_registered + content_tag :span, class: 'btn btn-default btn-xs disabled' do + 'Click to Join During Event' + end + else + link_to('Register for the conference to join this event.', + conference_conference_registration_path(conference), + class: 'btn btn-default btn-xs', + 'aria-label': "Register for #{event.title}") + end + end + + def calendar_timestamp(timestamp, _timezone) + timestamp = timestamp.in_time_zone('GMT') + timestamp -= timestamp.utc_offset + timestamp.strftime('%Y%m%dT%H%M%S') + end + + def google_calendar_link(event_schedule) + event = event_schedule.event + conference = event.conference + calendar_base = 'https://www.google.com/calendar/render' + start_timestamp = calendar_timestamp(event_schedule.start_time, conference.timezone) + end_timestamp = calendar_timestamp(event_schedule.end_time, conference.timezone) + event_details = { + action: 'TEMPLATE', + text: "#{event.title} at #{conference.title}", + details: calendar_event_text(event, event_schedule, conference), + location: "#{event_schedule.room.name} #{event_schedule.room_url}", + dates: "#{start_timestamp}/#{end_timestamp}", + ctz: event_schedule.timezone + } + "#{calendar_base}?#{event_details.to_param}" + end + + def css_background_color(color) + "background-color: #{color}; color: #{contrast_color(color)};" + end + + def user_options_for_dropdown(event, column) + users = event.send(column).pluck(:id, :name, :username, :email) + options_for_select(users.map { |u| ["#{u[1]} (#{u[2]} #{u[3]})", u[0]] }, users.map(&:first)) + end + + def committee_only_actions(user, conference, roles: %i[organizer cfp], &block) + role_map = roles.map { |role| { name: role, resource: conference } } + return unless user&.has_any_role?(:admin, *role_map) + + content_tag(:div, class: 'panel panel-info') do + concat content_tag(:div, 'Conference Organizers', class: 'panel-heading') + concat content_tag(:div, capture(&block), class: 'panel-body') + end + end + + private + + def calendar_event_text(event, event_schedule, conference) + <<~TEXT + #{conference.title} - #{event.title} + #{event_schedule.start_time.strftime('%Y %B %e - %H:%M')} #{event_schedule.timezone} + + More Info: #{conference_program_proposal_url(conference, event)} + Join: #{event.url} + + #{truncate(event.abstract, length: 200)} + TEXT + end + + def active_dropdown(selection, options) + # Consistent rendering of dropdown lists that submit patched changes + # + # Selection is the string to show by default, which is clicked to expose the + # dropdown options. + # Options is a list of 2-item lists; for each entry: + # * [0] is the text of the option, + # * [1] is the link url for the options + content_tag('div', class: 'dropdown') do + content_tag( + 'a', + class: 'dropdown-toggle', + href: '#', + data: { toggle: 'dropdown' } + ) do + content_tag('span', selection) + + content_tag('span', '', class: 'caret') + end + + content_tag('ul', class: 'dropdown-menu') do + options.collect do |option| + content_tag('li', link_to(option[0], option[1], method: :patch)) + end.join.html_safe + end + end + end +end +# rubocop:enable Metrics/ModuleLength diff --git a/app/helpers/format_helper.rb b/app/helpers/format_helper.rb new file mode 100644 index 000000000..4f320682d --- /dev/null +++ b/app/helpers/format_helper.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require 'redcarpet/render_strip' + +module FormatHelper + ## + # Includes functions related to formatting (like adding classes, colors) + ## + def status_icon(object) + case object.state + when 'new', 'to_reject', 'to_accept' + 'fa-eye' + when 'unconfirmed', 'accepted' + 'fa-check text-muted' + when 'confirmed' + 'fa-check text-success' + when 'rejected', 'withdrawn', 'canceled' + 'fa-ban' + end + end + + def event_progress_color(progress) + progress = progress.to_i + if progress == 100 + 'progress-bar-success' + elsif progress >= 85 + 'progress-bar-info' + elsif progress >= 71 + 'progress-bar-warning' + else + 'progress-bar-danger' + end + end + + def variant_from_delta(delta, reverse: false) + if delta.to_i.positive? + reverse ? 'warning' : 'success' + elsif delta.to_i.negative? + reverse ? 'success' : 'warning' + else + 'info' + end + end + + def target_progress_color(progress) + progress = progress.to_i + if progress >= 90 + 'green' + elsif progress < 90 && progress >= 80 + 'orange' + else + 'red' + end + end + + def days_left_color(days_left) + days_left = days_left.to_i + if days_left > 30 + 'green' + elsif days_left < 30 && days_left > 10 + 'orange' + else + 'red' + end + end + + def bootstrap_class_for(flash_type) + case flash_type + when 'success' + 'alert-success' + when 'error' + 'alert-danger' + when 'alert' + 'alert-warning' + when 'notice' + 'alert-info' + else + 'alert-warning' + end + end + + def label_for(event_state) + result = '' + case event_state + when 'new' + result = 'label label-primary' + when 'withdrawn' + result = 'label label-danger' + when 'unconfirmed' + result = 'label label-success' + when 'confirmed' + result = 'label label-success' + when 'rejected' + result = 'label label-warning' + when 'canceled' + result = 'label label-danger' + end + result + end + + def icon_for_todo(bool) + if bool + 'fa-solid fa-check' + else + 'fa-solid fa-xmark' + end + end + + def class_for_todo(bool) + bool ? 'todolist-ok' : 'todolist-missing' + end + + def word_pluralize(count, singular, plural = nil) + if count == 1 || count =~ /^1(\.0+)?$/ + singular + else + plural || singular.pluralize + end + end + + # Returns black or white deppending on what of them contrast more with the + # given color. Useful to print text in a coloured background. + # hexcolor is a hex color of 7 characters, being the first one '#'. + # Reference: https://24ways.org/2010/calculating-color-contrast + def contrast_color(hexcolor) + r = hexcolor[1..2].to_i(16) + g = hexcolor[3..4].to_i(16) + b = hexcolor[5..6].to_i(16) + yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000 + yiq >= 128 ? 'black' : 'white' + end + + def td_height(rooms) + td_height = 500 / rooms.length + # we want all least 3 lines in events and td padding = 3px, speaker picture height >= 25px + # and line-height = 17px => (17 * 3) + 6 + 25 = 82 + td_height < 82 ? 82 : td_height + end + + def room_height(rooms) + room_lines(rooms) * 17 + end + + def room_lines(rooms) + # line-height = 17px, td padding = 3px + (td_height(rooms) - 6) / 17 + end + + def event_height(rooms) + event_lines(rooms) * 17 + end + + def event_lines(rooms) + # line-height = 17px, td padding = 3px, speaker picture height >= 25px + (td_height(rooms) - 31) / 17 + end + + def speaker_height(rooms) + # td padding = 3px + speaker_height = td_height(rooms) - 6 - event_height(rooms) + # The speaker picture is a circle and the width must be <= 37 to avoid making the cell widther + speaker_height >= 37 ? 37 : speaker_height + end + + def speaker_width(rooms) + # speaker picture padding: 4px 2px; and we want the picture to be a circle + speaker_height(rooms) - 4 + end + + def selected_scheduled?(schedule) + schedule == @selected_schedule ? 'Yes' : 'No' + end + + def markdown(text, escape_html = true) + return '' if text.nil? + + markdown_options = { + autolink: true, + space_after_headers: true, + # no_intra_emphasis: true, # SNAPCON + fenced_code_blocks: true, + disable_indented_code_blocks: true, + tables: true, # SNAPCON + strikethrough: true, # SNAPCON + footnotes: true, # SNAPCON + superscript: true # SNAPCON + } + render_options = { + escape_html: escape_html, + safe_links_only: true + } + markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(render_options), markdown_options) + sanitize(markdown.render(text)) + end + + def markdown_hint(text = '') + markdown( + "#{text} Please look at #{link_to '**Markdown Syntax**', 'https://daringfireball.net/projects/markdown/syntax', + target: '_blank', rel: 'noopener'} to format your text", false + ) + end + + # Return a plain text markdown stripped of formatting. + def plain_text(content) + Redcarpet::Markdown.new(Redcarpet::Render::StripDown).render(content) + end + + def quantity_left_of(resource) + return '-/-' if resource.quantity.blank? + + "#{resource.quantity - resource.used}/#{resource.quantity}" + end +end diff --git a/app/helpers/paths_helper.rb b/app/helpers/paths_helper.rb new file mode 100644 index 000000000..cfedc2bd1 --- /dev/null +++ b/app/helpers/paths_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module PathsHelper + ## + # Includes functions related to links or redirects + ## + + def active_nav_li(link) + if current_page?(link) + 'active' + else + '' + end + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 000000000..11c455c2b --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module UsersHelper + ## + # Includes functions related to users + ## + # Set devise_mapping for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposals#new) + def devise_mapping + @devise_mapping ||= Devise.mappings[:user] + end + + def omniauth_configured + return Devise.omniauth_providers if OmniAuth.config.test_mode + + providers = [] + Devise.omniauth_providers.each do |provider| + provider_key = "#{provider}_key" + provider_secret = "#{provider}_secret" + unless Rails.application.secrets.send(provider_key).blank? || Rails.application.secrets.send(provider_secret).blank? + providers << provider + end + providers << provider if ENV.fetch("OSEM_#{provider.upcase}_KEY", + nil).present? && ENV.fetch("OSEM_#{provider.upcase}_SECRET", nil).present? + end + + providers.uniq + end + + # Receives a hash, generated from User model, function get_roles + # Outputs the roles of a user, including the conferences for which the user has the roles + # Eg. organizer(oSC13, oSC14), cfp(oSC12, oSC13) + def show_roles(roles) + roles.map { |x| x[0].titleize + ' (' + x[1].join(', ') + ')' }.join ', ' + end +end diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb new file mode 100644 index 000000000..f3753c1a6 --- /dev/null +++ b/app/helpers/versions_helper.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +module VersionsHelper + ## + # Groups functions related to change description + ## + def link_if_alive(version, link_text, link_url, conference) + version.item && conference ? link_to(link_text, link_url) : "#{link_text} with ID #{version.item_id}" + end + + def link_to_organization(organization_id) + return 'deleted organization' unless organization_id + + org = Organization.find_by(id: organization_id) + return current_or_last_object_state('Organization', organization_id).try(:name) unless org + + org.name.to_s + end + + def link_to_conference(conference_id) + return 'deleted conference' if conference_id.nil? + + conference = Conference.find_by(id: conference_id) + if conference + link_to conference.short_title, + edit_admin_conference_path(conference.short_title) + else + short_title = current_or_last_object_state('Conference', conference_id).try(:short_title) || '' + " #{short_title} with ID #{conference_id}" + end + end + + def link_to_user(user_id) + return 'Someone (probably via the console)' unless user_id + + user = User.find_by(id: user_id) + if user + link_to user.name, admin_user_path(id: user_id) + else + name = current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset['name'].second if PaperTrail::Version.where(item_type: 'User', item_id: user_id).any? + "#{name || 'Unknown user'} with ID #{user_id}" + end + end + + # Receives a model_name and id + # Returns nil if model_name is invalid + # Returns object in its current state if its alive + # Otherwise Returns object state just before deletion + def current_or_last_object_state(model_name, id) + return nil unless id.present? && model_name.present? + + begin + object = model_name.constantize.find_by(id: id) + rescue NameError + return nil + end + + if object.nil? + object_last_version = PaperTrail::Version.where(item_type: model_name, item_id: id).last + object = object_last_version.reify if object_last_version + end + object + end + + def subscription_change_description(version) + user_id = current_or_last_object_state(version.item_type, version.item_id).user_id + user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second unless user_id.to_s == version.whodunnit + version.event == 'create' ? "subscribed #{user_name} to" : "unsubscribed #{user_name} from" + end + + def registration_change_description(version) + if version.item_type == 'Registration' + user_id = current_or_last_object_state(version.item_type, version.item_id).user_id + elsif version.item_type == 'EventsRegistration' + registration_id = current_or_last_object_state(version.item_type, version.item_id).registration_id + user_id = current_or_last_object_state('Registration', registration_id).user_id + end + user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second + + if user_id.to_s == version.whodunnit + case version.event + when 'create' then 'registered to' + when 'update' then "updated #{updated_attributes(version)} of the registration for" + when 'destroy' then 'unregistered from' + end + else + case version.event + when 'create' then "registered #{user_name} to" + when 'update' then "updated #{updated_attributes(version)} of #{user_name}'s registration for" + when 'destroy' then "unregistered #{user_name} from" + end + end + end + + def comment_change_description(version) + user = current_or_last_object_state(version.item_type, version.item_id).user + if version.event == 'create' + version.previous.nil? ? 'commented on' : "re-added #{user.name}'s comment on" + else + "deleted #{user.name}'s comment on" + end + end + + def vote_change_description(version) + user = current_or_last_object_state(version.item_type, version.item_id).user + if version.event == 'create' + version.previous.nil? ? 'voted on' : "re-added #{user.name}'s vote on" + elsif version.event == 'update' + "updated #{user.name}'s vote on" + else + "deleted #{user.name}'s vote on" + end + end + + def general_change_description(version) + if version.event == 'create' + 'created new' + elsif version.event == 'update' + "updated #{updated_attributes(version)} of" + else + 'deleted' + end + end + + def event_change_description(version) + if version.event == 'create' + 'submitted new' + + elsif version.changeset['state'] + case version.changeset['state'][1] + when 'unconfirmed' then 'accepted' + when 'withdrawn' then 'withdrew' + when 'canceled', 'rejected', 'confirmed' then version.changeset['state'][1] + when 'new' then 'resubmitted' + end + + else + "updated #{updated_attributes(version)} of" + end + end + + def event_schedule_change_description(version) + case version.event + when 'create' then 'scheduled' + when 'update' then 'rescheduled' + when 'destroy' then 'unscheduled' + end + end + + def user_change_description(version) + if version.event == 'create' + link_to_user(version.item_id) + ' signed up' + elsif version.event == 'update' + if version.changeset.keys.include?('reset_password_sent_at') + 'Someone requested password reset of' + elsif version.changeset.keys.include?('confirmed_at') && version.changeset['confirmed_at'][0].nil? + (version.whodunnit.nil? ? link_to_user(version.item_id) : link_to_user(version.whodunnit)) + ' confirmed account of' + elsif version.changeset.keys.include?('confirmed_at') && version.changeset['confirmed_at'][1].nil? + link_to_user(version.whodunnit) + ' unconfirmed account of' + else + link_to_user(version.whodunnit) + " updated #{updated_attributes(version)} of" + end + end + end + + def users_role_change_description(version) + version.event == 'create' ? 'added' : 'removed' + end +end diff --git a/app/services/full_calendar_formatter.rb b/app/services/full_calendar_formatter.rb new file mode 100644 index 000000000..7f9a21594 --- /dev/null +++ b/app/services/full_calendar_formatter.rb @@ -0,0 +1,49 @@ +class FullCalendarFormatter + def self.rooms_to_resources(rooms) + rooms.map { |room| room_to_resource(room) }.to_json + end + + def self.event_schedules_to_resources(event_schedules) + return '[]' if event_schedules.empty? + + conference = event_schedules.first.schedule.program.conference + event_schedules.map { |event_schedule| event_schedule_to_resource(conference, event_schedule) }.to_json + end + + class << self + include FormatHelper + + private + + def room_to_resource(room) + { + id: room.guid, + title: room.name, + order: room.order + } + end + + def event_schedule_to_resource(conference, event_schedule) + event = event_schedule.event + event_type_color = event.event_type.color + url = Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event.id) + background_event = event.event_type.title.match(/Break/i) + rooms = background_event ? conference.venue.rooms.pluck(:guid) : [event_schedule.room.guid] + + { + id: event.guid, + title: event.title, + start: event_schedule.start_time_in_conference_timezone, + end: event_schedule.end_time_in_conference_timezone, + resourceIds: rooms, + url: url, + borderColor: event_type_color, + backgroundColor: event_type_color, + textColor: contrast_color(event_type_color), + className: "fc-event-track-#{event.track&.short_name || 'none'}", + display: background_event ? 'background' : 'auto', + has_parent: event.parent_event.present? + } + end + end +end diff --git a/app/services/mailbluster_manager.rb b/app/services/mailbluster_manager.rb new file mode 100644 index 000000000..2bb06f4e9 --- /dev/null +++ b/app/services/mailbluster_manager.rb @@ -0,0 +1,40 @@ +class MailblusterManager + include HTTParty + base_uri 'https://api.mailbluster.com/api/leads/' + @auth_headers = { + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => ENV.fetch('MAILBLUSTER_API_KEY', nil) + } + } + + def self.query_api(method, path, body: {}) + options = @auth_headers.merge(body: body.to_json) + send(method, path, options).parsed_response + end + + def self.create_lead(user) + query_api(:post, '/', body: { + 'email' => user.email, + 'firstName' => user.name, + 'overrideExisting' => true, + 'subscribed' => true, + 'tags' => [ENV.fetch('OSEM_NAME', 'snapcon')] + }) + end + + def self.edit_lead(user, add_tags: [], remove_tags: [], old_email: nil) + email_hash = Digest::MD5.hexdigest(old_email.presence || user.email) + query_api(:put, "/#{email_hash}", body: { + 'email' => user.email, + 'firstName' => user.name, + 'addTags' => add_tags, + 'removeTags' => remove_tags + }) + end + + def self.delete_lead(email) + email_hash = Digest::MD5.hexdigest email + query_api(:delete, "/#{email_hash}") + end +end diff --git a/app/views/admin/booths/_all_booths.csv.haml b/app/views/admin/booths/_all_booths.csv.haml new file mode 100644 index 000000000..334918ac7 --- /dev/null +++ b/app/views/admin/booths/_all_booths.csv.haml @@ -0,0 +1,19 @@ +- headers = ["#{(t'booth').capitalize } ID", + 'Title', + 'Description', + 'Reasoning', + 'Submitter Name', + 'Submitter Relationship', + 'Website Url', + 'State'] += CSV.generate_line ["All #{(t'booth').pluralize}"] += CSV.generate_line headers +- @booths.each do |booth| + = CSV.generate_line([booth.id, + booth.title, + booth.description, + booth.reasoning, + booth.submitter.name, + booth.submitter_relationship, + booth.website_url, + booth.state]).html_safe diff --git a/app/views/admin/booths/_all_booths.pdf.prawn b/app/views/admin/booths/_all_booths.pdf.prawn new file mode 100644 index 000000000..df034b794 --- /dev/null +++ b/app/views/admin/booths/_all_booths.pdf.prawn @@ -0,0 +1,26 @@ +prawn_document(force_download: true, filename: "#{@file_name}.pdf", page_layout: :landscape) do |pdf| + booths_array = [] + header_array = ["#{(t'booth').capitalize } ID", + 'Title', + 'Description', + 'Reasoning', + 'Submitter Name', + 'Submitter Relationship', + 'Website Url', + 'State'] + booths_array << header_array + @booths.each do |booth| + row = [] + row << booth.id + row << booth.title + row << booth.description + row << booth.reasoning + row << booth.submitter.name + row << booth.submitter_relationship + row << booth.website_url + row << booth.state + booths_array << row + end + pdf.text "#{@conference.short_title} #{(t'booth').pluralize}", font_size: 25, align: :center + pdf.table booths_array, header: true, cell_style: {size: 8, border_width: 1},column_widths: [45,70,153,152,70,80,105,45] +end diff --git a/app/views/admin/booths/_confirmed_booths.pdf.prawn b/app/views/admin/booths/_confirmed_booths.pdf.prawn new file mode 100644 index 000000000..b3902ff7d --- /dev/null +++ b/app/views/admin/booths/_confirmed_booths.pdf.prawn @@ -0,0 +1,26 @@ +prawn_document(force_download: true, filename: "#{@file_name}.pdf", page_layout: :landscape) do |pdf| + booths_array = [] + header_array = ["#{(t'booth').capitalize } ID", + 'Title', + 'Description', + 'Reasoning', + 'Submitter Name', + 'Submitter Relationship', + 'Website Url', + 'State'] + booths_array << header_array + @booths.confirmed.each do |booth| + row = [] + row << booth.id + row << booth.title + row << booth.description + row << booth.reasoning + row << booth.submitter.name + row << booth.submitter_relationship + row << booth.website_url + row << booth.state + booths_array << row + end + pdf.text "#{@conference.short_title} booths", font_size: 25, align: :center + pdf.table booths_array, header: true, cell_style: {size: 8, border_width: 1},column_widths: [45,70,153,152,70,80,105,45] +end diff --git a/app/views/admin/booths/booths.pdf.prawn b/app/views/admin/booths/booths.pdf.prawn new file mode 100644 index 000000000..5c8f9bd26 --- /dev/null +++ b/app/views/admin/booths/booths.pdf.prawn @@ -0,0 +1,5 @@ +if @booth_export_option == 'confirmed' + render 'confirmed_booths' +elsif @booth_export_option == 'all' + render 'all_booths' +end diff --git a/app/views/admin/comments/_posted_comments.html.haml b/app/views/admin/comments/_posted_comments.html.haml new file mode 100644 index 000000000..d274f9292 --- /dev/null +++ b/app/views/admin/comments/_posted_comments.html.haml @@ -0,0 +1,12 @@ +- @posted_comments.each do |conference, events| + .panel.panel-default + .panel-heading + %h4.title.panel-title= conference.title + .panel-body + - events.each do |event, comments| + .notifications + %h4.title= link_to event.title, admin_conference_program_event_path(event.program.conference.short_title, event) + %hr + - comments.each do |comment| + %h5.strong Created at: #{comment.created_at} + %p= comment.body diff --git a/app/views/admin/comments/_unread_comments.html.haml b/app/views/admin/comments/_unread_comments.html.haml new file mode 100644 index 000000000..5facdc4ba --- /dev/null +++ b/app/views/admin/comments/_unread_comments.html.haml @@ -0,0 +1,12 @@ +- @unread_comments.each do |conference, events| + .panel.panel-default + .panel-heading + %h4.title.panel-title= conference.title + .panel-body + - events.each do |event, comments| + .notifications + %h4.title= link_to event.title, admin_conference_program_event_path(event.program.conference.short_title, event) + %hr + - comments.each do |comment| + %h5.strong Posted by: #{comment.user.name} | Created at: #{comment.created_at} + %p= comment.body diff --git a/app/views/admin/conferences/_form_fields.html.haml b/app/views/admin/conferences/_form_fields.html.haml new file mode 100644 index 000000000..b16692aa8 --- /dev/null +++ b/app/views/admin/conferences/_form_fields.html.haml @@ -0,0 +1,99 @@ +%h4 Basic Information +%hr +- if f.object.new_record? + .form-group + = f.label :organization, "Organization" + = f.select :organization_id, Organization.accessible_by(current_ability, :update).pluck(:name, :id) +.form-group + = f.label :title + %abbr{title: 'This field is required'} * + = f.text_field :title, required: true, class: 'form-control', placeholder: 'Title' + %span.help-block + The name of your conference as it shall appear throughout the site. Example: 'openSUSE Conference 2013' +.form-group + = f.label :short_title + %abbr{title: 'This field is required'} * + = f.text_field :short_title, required: true, pattern: '[a-zA-Z0-9_-]+', title: 'Only letters, numbers, underscores, and dashes.', prepend: conferences_url + '/', class: 'form-control', placeholder: 'Short Title' + %span.help-block + A short and unique handle for your conference, using only letters, numbers, underscores, and dashes. This will be used to identify your conference in URLs etc. Example: + %em + froscon2011 +- unless f.object.new_record? # We are showing more fields on the edit form + .form-group + = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' + %span.help-block + = markdown_hint('A description of the conference.') + .form-group + = f.color_field :color, size: 6, class: 'form-control' + %span.help-block + The color will be used for the dashboard, for instance. + .form-group + = f.label :picture, 'Conference Logo' + - if f.object.picture? + = image_tag f.object.picture.thumb.url + = f.file_field :picture + %span.help-block + This will be shown in the navigation bar and emails. + .form-group + = f.label :custom_css, "Custom CSS" + = f.text_area :custom_css, rows: 10, class: 'form-control', html: { style: 'font-family: monospace' } + %span.help-block + Add custon CSS to all pages within the conference. The class + %code .conference-#{@conference.short_title} + is included on the body element. + .form-group + = f.label :ticket_layout + = 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'}")}. +%p.text-right + - if f.object.new_record? + = f.submit nil, { class: 'btn btn-success' } + - else + = f.submit nil, { class: 'btn btn-success', data: { confirm: 'Are you sure you want to proceed?' } } diff --git a/app/views/admin/conferences/_recent_registrations.html.haml b/app/views/admin/conferences/_recent_registrations.html.haml new file mode 100644 index 000000000..9b750c835 --- /dev/null +++ b/app/views/admin/conferences/_recent_registrations.html.haml @@ -0,0 +1,19 @@ +- if recent_registrations && !recent_registrations.empty? + .table-responsive + %table.table + %thead + %tr + %th # + %th Public Name + %th Conference + %th Date + - recent_registrations.each_with_index do |registration, index| + %tbody + %tr + %td= index + 1 + %td= link_to registration.name, admin_user_path(registration.user.id) + %td= link_to registration.conference.title, admin_conference_path(registration.conference.short_title) + %td= registration.created_at.strftime('%m/%d/%Y') +- else + %h5.text-warning.text-center + No registrations! diff --git a/app/views/admin/conferences/_top_submitter.html.haml b/app/views/admin/conferences/_top_submitter.html.haml new file mode 100644 index 000000000..b4f714fd5 --- /dev/null +++ b/app/views/admin/conferences/_top_submitter.html.haml @@ -0,0 +1,18 @@ +.row + .col-md-12 + %h3 + Top Submitter + - if @top_submitter && !@top_submitter.empty? + - @top_submitter.each do |key, value| + .row.top-submitter + .col-md-2 + = image_tag(key.profile_picture(size: '25'), title: "Yo #{key.name}!", alt: '', 'class' => 'img-circle img-responsive text-center') + .col-md-10 + %h4 + = link_to key.name, admin_user_path(key) + %div + %small + = link_to pluralize(value, 'submission'), admin_user_path(key, tab: 'submissions-content') + - else + %h4.text-warning + No submissions! diff --git a/app/views/admin/contacts/edit.html.haml b/app/views/admin/contacts/edit.html.haml new file mode 100644 index 000000000..1a67342ca --- /dev/null +++ b/app/views/admin/contacts/edit.html.haml @@ -0,0 +1,58 @@ +.row + .col-md-12 + .page-header + %h1 Contact + %p.text-muted + How people can contact you +.row + .col-md-8 + = form_for(@contact, url: admin_conference_contact_path(@conference.short_title), html: {multipart: true}) do |f| + %h4 + Mail + %hr + .form-group + = f.label :email + = f.email_field :email, autofocus: true, class: 'form-control' + %span.help-block + Contact email address for your conference. Will be used as reply-to address in emails sent out by the system. + = f.label :sponsor_email + = f.email_field :sponsor_email, class: 'form-control' + %span.help-block + This will appear in the sponsor segment of the splash for the sponsors to contact to the organizers. + %h4 + Social Media + %hr + .form-group + = f.label :social_tag + = f.text_field :social_tag, class: 'form-control' + %span.help-block + The hashtag you'll use on Twitter and Google+. Don't include the '#' sign! + = f.label :blog, 'Blog URL' + = f.url_field :blog, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to your conference blog. + = f.label :facebook, 'Facebook URL' + = f.url_field :facebook, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to the Facebook page of your Conference' + = f.label :googleplus, 'Google+ URL' + = f.url_field :googleplus, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to the Google+ Page of your Conference' + = f.label :twitter, 'Twitter URL' + = f.url_field :twitter, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to the Twitter Page of your Conference' + = f.label :instagram, 'Insta URL' + = f.url_field :instagram, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to the Instagram Page of your Conference + = f.label :mastodon, 'Mastodon URL' + = f.url_field :mastodon, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to the Mastodon Page of your Conference + = f.label :youtube, 'Youtube URL' + = f.url_field :youtube, class: 'form-control' + %span.help-block + This will appear in the social media section as a link to the YouTube channel for your conference. + = f.submit nil, class: 'btn btn-primary' diff --git a/app/views/admin/difficulty_levels/edit.html.haml b/app/views/admin/difficulty_levels/edit.html.haml new file mode 100644 index 000000000..baec7d471 --- /dev/null +++ b/app/views/admin/difficulty_levels/edit.html.haml @@ -0,0 +1,9 @@ +.row + .col-md-12 + .page-header + %h1 + Edit Difficulty Level + = @difficulty_level.title +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml new file mode 100644 index 000000000..41a43825e --- /dev/null +++ b/app/views/admin/emails/index.html.haml @@ -0,0 +1,217 @@ +.row + .col-md-8 + = form_for(@settings, url: admin_conference_email_path(@conference.short_title, @conference.email_settings), html: {multipart: true}) do |f| + .row + .col-md-12 + %div{ role: 'tabpanel' } + / Nav tabs + %ul.nav.nav-tabs{ role: 'tablist' } + %li.active{ role: 'presentation' } + %a{ 'aria-controls' => 'onboarding', 'data-toggle' => 'tab', href: '#onboarding', role: 'tab' } Onboarding + %li{ role: 'presentation' } + %a{ 'aria-controls' => 'proposal', 'data-toggle' => 'tab', href: '#proposal', role: 'tab' } Proposal + %li{ role: 'presentation' } + %a{ 'aria-controls' => 'notifications', 'data-toggle' => 'tab', href: '#notifications', role: 'tab' } Update Notifications + %li{ role: 'presentation' } + %a{ 'aria-controls' => 'cfp', 'data-toggle' => 'tab', href: '#cfp', role: 'tab' } Call for Papers + %li{ role: 'presentation' } + %a{ 'aria-controls' => 'booths', 'data-toggle' => 'tab', href: '#booth', role: 'tab' } Booth + / Tab panes + .tab-content + #onboarding.tab-pane.active{ role: 'tabpanel' } + .checkbox + %label + = f.check_box :send_on_registration, data: {name: 'email_settings_registration_subject'}, class: 'send_on_radio' + Send an email when the user registers for the conference? + .form-group + = f.label :registration_subject, 'Subject' + = f.text_field :registration_subject, class: 'form-control' + .form-group + = f.label :registration_body, 'Body' + = f.text_area :registration_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_registration_subject', 'data-subject-text' => 'Thank you for registering', + 'data-body-input-id' => 'email_settings_registration_body', + 'data-body-text' => "Dear {name},\n\nThank you for Registering for the conference {conference}.\nPlease complete your registration by filling out your travel information.\n\nIf you are unable to attend please unregister online:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\nWe look forward to see you there.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'registration_help' } Show Help + = render partial: 'help', locals: { id: 'registration_help', show_event_variables: false } + #proposal.tab-pane{ role: 'tabpanel' } + .checkbox + %label + = f.check_box :send_on_submitted_proposal, data: { name: 'email_settings_proposal_submited_subject'}, class: 'send_on_radio' + Send an email when the proposal is submitted? + .form-group + = f.label :submitted_proposal_subject, 'Subject' + = f.text_field :submitted_proposal_subject, class: 'form-control' + .form-group + = f.label :submitted_proposal_body, 'Body' + = f.text_area :submitted_proposal_body, input_html: { rows: 10, cols: 20 }, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_submitted_proposal_subject', 'data-subject-text' => 'Your proposal has been submitted successfully', 'data-body-input-id' => 'email_settings_submitted_proposal_body', 'data-body-text' => "Dear {name}\n\nThank you for submitting your proposal {eventtitle}.\n\nOur team will review it and get back to you as soon as possible.\n\nFeel free to contact us with any questions or concerns.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'submitted_proposal_help' } Show Help + = render partial: 'help', locals: { id: 'submitted_proposal_help', show_event_variables: true } + .checkbox + %label + = f.check_box :send_on_accepted, data: { name: 'email_settings_accepted_subject' }, class: 'send_on_radio' + Send an email when the proposal is accepted? + .form-group + = f.label :accepted_subject, 'Subject' + = f.text_field :accepted_subject, class: 'form-control' + .form-group + = f.label :accepted_body, 'Body' + = f.text_area :accepted_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_accepted_subject', + 'data-subject-text' => 'Your submission has been accepted', + 'data-body-input-id' => 'email_settings_accepted_body', + 'data-body-text' => "Dear {name}\n\nWe are very pleased to inform you that your submission {eventtitle} has been accepted for the conference {conference}.\n\nThe public page of your submission can be found at:\n{proposalslink}\nIf you haven´t already registered for {conference}, please do as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help + = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } + .checkbox + %label + = f.check_box :send_on_rejected, data: { name: 'email_settings_rejected_subject'}, class: 'send_on_radio' + Send an email when the proposal is rejected? + .form-group + = f.label :rejected_subject, 'Subject' + = f.text_field :rejected_subject, class: 'form-control' + .form-group + = f.label :rejected_body, 'Body' + = f.text_area :rejected_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_rejected_subject', + 'data-subject-text' => 'Your submission has been rejected', + 'data-body-input-id' => 'email_settings_rejected_body', + 'data-body-text' => "Dear {name},\n\nThank you for your submission {eventtitle} for the conference {conference}.\nAfter careful consideration we are sorry to inform you that your submission has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'rejected_help' } Show Help + = render partial: 'help', locals: {id: 'rejected_help', show_event_variables: true} + .checkbox + %label + = f.check_box :send_on_confirmed_without_registration, data: {name: 'email_settings_confirmed_without_registration_subject'}, class: 'send_on_radio' + Send an email when a user has a confirmed proposal, but isn't yet registered? + .form-group + = f.label :confirmed_without_registration_subject, 'Subject' + = f.text_field :confirmed_without_registration_subject, class: 'form-control' + .form-group + = f.label :confirmed_without_registration_body, 'Body' + = f.text_area :confirmed_without_registration_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_confirmed_without_registration_subject', + 'data-subject-text' => 'Your proposal has been confirmed without registration', + 'data-body-input-id' => 'email_settings_confirmed_without_registration_body', + 'data-body-text' => "Dear {name},\n\nThank you for the confirmation of {eventtitle}. Unfortunately you are not registered for the conference {conference}. Please register as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'confirmed_help' } Show Help + = render partial: 'help', locals: {id: 'confirmed_help', show_event_variables: true} + #notifications.tab-pane{ role: 'tabpanel' } + .checkbox + %label + = f.check_box :send_on_conference_dates_updated, data: { name: 'email_settings_conference_dates_updated_subject'}, class: 'send_on_radio' + Send an email to all participants if the conference dates are changed? + .form-group + = f.label :conference_dates_updated_subject, 'Subject' + = f.text_field :conference_dates_updated_subject, class: 'form-control' + .form-group + = f.label :conference_dates_updated_body, 'Body' + = f.text_area :conference_dates_updated_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_conference_dates_updated_subject', + 'data-subject-text' => 'The dates of the conference have changed', + 'data-body-input-id' => 'email_settings_conference_dates_updated_body', + 'data-body-text' => "Dear {name},\n\nThe date of {conference} has changed.\n New Dates : {conference_start_date} - {conference_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_dates_help' } Show Help + = render partial: 'help', locals: {id: 'updated_dates_help', show_event_variables: false} + .checkbox + %label + = f.check_box :send_on_conference_registration_dates_updated, data: { name: 'email_settings_conference_registration_dates_updated_subject' }, class: 'send_on_radio' + Send an email to all participants if the conference registration dates changed? + .form-group + = f.label :conference_registration_dates_updated_subject, 'Subject' + = f.text_field :conference_registration_dates_updated_subject, class: 'form-control' + .form-group + = f.label :conference_registration_dates_updated_body, 'Body' + = f.text_area :conference_registration_dates_updated_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_conference_registration_dates_updated_subject', + 'data-subject-text' => 'The registration dates have changed', + 'data-body-input-id' => 'email_settings_conference_registration_dates_updated_body', + 'data-body-text' => "Dear {name},\n\nThe registration date of {conference} has changed.\n New Dates : {registration_start_date} - {registration_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_registrations_dates_help' } Show Help + = render partial: 'help', locals: {id: 'updated_registrations_dates_help', show_event_variables: false} + .checkbox + %label + = f.check_box :send_on_venue_updated, data: { name: 'email_settings_venue_updated_subject'}, class: 'send_on_radio' + Send an email on updating the venue? + .form-group + = f.label :venue_updated_subject, 'Subject' + = f.text_field :venue_updated_subject, class: 'form-control' + .form-group + = f.label :venue_updated_body, 'Body' + = f.text_area :venue_updated_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_venue_updated_subject', + 'data-subject-text' => 'The venue has changed', + 'data-body-input-id' => 'email_settings_venue_updated_body', + 'data-body-text' => "Dear {name},\n\nThe Conference venue of {conference} has changed. New location is: {venue}.\n Address: {venue_address}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_venue_help' } Show Help + = render partial: 'help', locals: {id: 'updated_venue_help', show_event_variables: false} + #cfp.tab-pane{ role: 'tabpanel' } + .checkbox + %label + = f.check_box :send_on_program_schedule_public + Send an email to all participants if the schedule is made public? + .form-group + = f.label :program_schedule_public_subject, 'Subject' + = f.text_field :program_schedule_public_subject, class: 'form-control' + .form-group + = f.label :program_schedule_public_body, 'Body' + = f.text_area :program_schedule_public_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_program_schedule_public_subject', + 'data-subject-text' => 'The schedule has been released', + 'data-body-input-id' => 'email_settings_program_schedule_public_body', + 'data-body-text' => "Dear {name},\n\nThe schedule for {conference} has been announced.\nLink to Schedule {schedule_link}\n\nBest wishes\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help + = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + .checkbox + %label + = f.check_box :send_on_cfp_dates_updated + Send an email to all participants if call for paper dates are updated? + .form-group + = f.label :cfp_dates_updated_subject, 'Subject' + = f.text_field :cfp_dates_updated_subject, class: 'form-control' + .form-group + = f.label :cfp_dates_updated_body, 'Body' + = f.text_area :cfp_dates_updated_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_cfp_dates_updated_subject', + 'data-subject-text' => 'The Call for Papers dates have changed', + 'data-body-input-id' => 'email_settings_cfp_dates_updated_body', + 'data-body-text' => "Dear {name},\n\nThe Conference Call for Papers Details of {conference} has changed.\nNew Dates : {cfp_start_date} - {cfp_end_date}.\n Link to Schedule {schedule_link} \n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help + = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + #booth.tab-pane{ role: 'tabpanel' } + .checkbox + %label + = f.check_box :send_on_booths_acceptance + Send an email when the booth is accepted? + .form-group + = f.label :booths_acceptance_subject, 'Subject' + = f.text_field :booths_acceptance_subject, class: 'form-control' + .form-group + = f.label :booths_acceptance_body, 'Body' + = f.text_area :booths_acceptance_body, rows:10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_acceptance_subject', + 'data-subject-text' => "Your #{t'booth'} has been accepted!", + 'data-body-input-id' => 'email_settings_booths_acceptance_body', + 'data-body-text' => "Dear {name},\n\nWe are pleased to inform you that your #{t'booth'} request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe are looking forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_acceptance_help' } Show help + = render partial: 'help', locals: {id: 'booth_acceptance_help', show_event_variables: false} + .checkbox + %label + = f.check_box :send_on_booths_rejection + Send an email when the booth is rejected? + .form-group + = f.label :booths_rejection_subject, 'Subject' + = f.text_field :booths_rejection_subject, class: 'form-control' + .form-group + = f.label :booths_rejection_body, 'Body' + = f.text_area :booths_rejection_body, rows:10, cols:20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_booths_rejection_subject', + 'data-subject-text' => "Your #{t'booth'} request has been rejected", + 'data-body-input-id' => 'email_settings_booths_rejection_body', + 'data-body-text' => "Dear {name},\n\nThank you for your #{t'booth'} request {booth_title} for the conference {conference}.\n\nUnfortunately, we are sorry to inform you that your request has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_rejection_help' } Show help + = render partial: 'help', locals: {id: 'booth_rejection_help', show_event_variables: false} + + .row + .col-md-12 + = f.submit nil, { class: 'btn btn-primary' } diff --git a/app/views/admin/event_types/edit.html.haml b/app/views/admin/event_types/edit.html.haml new file mode 100644 index 000000000..ebf17c09d --- /dev/null +++ b/app/views/admin/event_types/edit.html.haml @@ -0,0 +1,9 @@ +.row + .col-md-12 + .page-header + %h1 + Edit Event Type + = @event_type.title +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/event_types/index.html.haml b/app/views/admin/event_types/index.html.haml new file mode 100644 index 000000000..015a33975 --- /dev/null +++ b/app/views/admin/event_types/index.html.haml @@ -0,0 +1,44 @@ +.row + .col-md-12 + .page-header + %h1 Event Types + %p.text-muted + The character of events in your conference +.row + .col-md-12 + %table.table.table-hover#event_types + %thead + %th Title + %th Description + %th Instructions + %th Length + %th Abstract Length + %th Color + %th Actions + %tbody + - @conference.program.event_types.each do |event_type| + %tr + %td + = event_type.title + %td + = markdown(event_type.description) + %td + = markdown(event_type.submission_instructions) + %td + = event_type.length + Minutes + %td + = "#{event_type.minimum_abstract_length} - #{event_type.maximum_abstract_length}" + Words + %td + %span.label{ style: "background-color: #{event_type.color}; color: #{ contrast_color(event_type.color) };" } + = event_type.color + %td + .btn-group{ role: 'group' } + = link_to 'Edit', edit_admin_conference_program_event_type_path(@conference.short_title, event_type.id), + method: :get, class: 'btn btn-primary' + = link_to 'Delete', admin_conference_program_event_type_path(@conference.short_title, event_type.id), + method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete #{event_type.name}?" } +.row + .col-md-12.text-right + = link_to 'Add Event Type', new_admin_conference_program_event_type_path(@conference.short_title), class: 'btn btn-primary' diff --git a/app/views/admin/events/_all_events.pdf.prawn b/app/views/admin/events/_all_events.pdf.prawn new file mode 100644 index 000000000..3f1c0d786 --- /dev/null +++ b/app/views/admin/events/_all_events.pdf.prawn @@ -0,0 +1,36 @@ +prawn_document(force_download: true, filename: "#{@file_name}.pdf", page_layout: :landscape) do |pdf| + events_array = [] + header_array = ['Event ID', + 'Title', + 'Abstract', + 'Start time', + 'Submitter', + 'Speaker', + 'Speaker Email', + 'Event Type', + 'Track', + 'Difficulty Level', + 'Room', + 'State' + ] + events_array << header_array + @events.each do |event| + row = [] + row << event.id + row << event.title + row << event.abstract + row << (event.time.present? ? "#{event.time.strftime("%Y-%m-%d")} #{event.time.strftime("%I:%M%p")} " : '') + row << event.submitter.name + row << event.speaker_names + row << event.speaker_emails + row << event.event_type.title + row << (event.track.present? ? event.track.name : '') + row << (event.difficulty_level.present? ? event.difficulty_level.title : '') + row << (event.room.present? ? event.room.name : '') + row << event.state + events_array << row + end + + pdf.text "#{@conference.short_title} Events", font_size: 25, align: :center + pdf.table events_array, header: true, cell_style: {size: 8, border_width: 1},column_widths: [40,60,90,50,70,65,85,50,55,50,60,45] +end diff --git a/app/views/admin/events/_datatable_row_rating.haml b/app/views/admin/events/_datatable_row_rating.haml new file mode 100644 index 000000000..e2183c5e3 --- /dev/null +++ b/app/views/admin/events/_datatable_row_rating.haml @@ -0,0 +1,11 @@ +- if show_votes + %span{ data: { toggle: 'tooltip' }, title: rating_tooltip(event, max_rating) }< + = rating_stars(event.average_rating, max_rating, avgrate: true) + +- if event.voted?(current_user) + %span.label.label-success<> + You voted: + = rating_fraction(event.user_rating(current_user), max_rating) +- else + %span.label.label-danger<> + Not rated diff --git a/app/views/admin/events/_form.html.haml b/app/views/admin/events/_form.html.haml new file mode 100644 index 000000000..538d842a6 --- /dev/null +++ b/app/views/admin/events/_form.html.haml @@ -0,0 +1,42 @@ +.container + .row + .col-md-12 + .tabbable + %ul.nav.nav-tabs + %li.active + = link_to 'Proposal', '#proposal-content', 'data-toggle' => 'tab' + %li + = link_to 'Materials', '#commercials-content', 'data-toggle' => 'tab' + .tab-content + .tab-pane.active#proposal-content + = form_for(@event, url: @url) do |f| + = render 'proposals/form', f: f + = render 'shared/user_selectize' + .tab-pane#commercials-content + - if @event.persisted? + %p.text-muted + You can add materals for your proposal. These materials will be displayed on the + = link_to 'public proposal page.', conference_program_proposal_path(@conference.short_title, @event) + If you don't add any materials, the conference materials will be displayed. + - if can? :create, @event.commercials.new + .row + .col-md-6 + #resource-content + #resource-placeholder{ style: 'background-color:#d3d3d3; float: left; width: 400px; height: 250px; margin: 5px; border-width: 1px; border-style: solid; border-color: rgba(0,0,0,.2);' } + .row + .col-md-6 + = form_for(@event.commercials.new, url: conference_program_proposal_commercials_path(conference_id: @conference.short_title, proposal_id: @event)) do |f| + = render 'proposals/commercial_form_fields', f: f, commercial: @event.commercials.build + %hr + - @event.commercials.each_slice(3) do |slice| + .row + - slice.each do |commercial| + - if commercial.persisted? + .col-md-4 + .panel.panel-default + %div{ id: "resource-content-#{commercial.id}"} + = render partial: 'shared/media_item', locals: { commercial: commercial } + .caption.panel-footer + - if can? :update, commercial + = form_for(commercial, url: conference_program_proposal_commercial_path(conference_id: @conference.short_title, proposal_id: @event, id: commercial)) do |f| + = render 'proposals/commercial_form_fields', f: f, commercial: commercial diff --git a/app/views/admin/events/_user_fields.html.haml b/app/views/admin/events/_user_fields.html.haml new file mode 100644 index 000000000..e39ecf26a --- /dev/null +++ b/app/views/admin/events/_user_fields.html.haml @@ -0,0 +1,5 @@ +.nested-fields + = f.inputs do + = f.input :email + = f.input :name + = remove_association_link :user, f diff --git a/app/views/admin/events/events.csv.haml b/app/views/admin/events/events.csv.haml new file mode 100644 index 000000000..e440a49ea --- /dev/null +++ b/app/views/admin/events/events.csv.haml @@ -0,0 +1,6 @@ +- if @event_export_option == 'confirmed' + = render 'confirmed_events' +- elsif @event_export_option == 'all' + = render 'all_events' +- elsif @event_export_option == 'all_with_comments' + = render 'all_with_comments' diff --git a/app/views/admin/events/vote.js.erb b/app/views/admin/events/vote.js.erb new file mode 100644 index 000000000..46849859b --- /dev/null +++ b/app/views/admin/events/vote.js.erb @@ -0,0 +1,10 @@ +$('table#myrating').replaceWith( + "<%= escape_javascript(render 'voting', \ + event: @event, \ + show_votes: @program.show_voting?, \ + max_rating: @program.rating, \ + voting_period: @program.voting_period?, \ + votes: @votes, \ + conference_id: @conference.short_title \ + ) %>" +); diff --git a/app/views/admin/lodgings/new.html.haml b/app/views/admin/lodgings/new.html.haml new file mode 100644 index 000000000..23a97b039 --- /dev/null +++ b/app/views/admin/lodgings/new.html.haml @@ -0,0 +1,8 @@ +.row + .col-md-12 + .page-header + %h1 + Create Lodging +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/organizations/index.html.haml b/app/views/admin/organizations/index.html.haml new file mode 100644 index 000000000..d7ebe7c67 --- /dev/null +++ b/app/views/admin/organizations/index.html.haml @@ -0,0 +1,39 @@ +.row + .col-md-12 + .page-header + %h1 Organizations + - if can? :manage, :all + .btn-group.pull-right + = link_to 'Create Organization', new_admin_organization_path, class: 'btn btn-success pull-right' + %p.text-muted + Manage organizations in OSEM +.row + .col-md-12 + %table.datatable + %thead + %th Name + %th Upcoming Conferences + %th Past Conferences + %th Code of Conduct? + %th Actions + %tbody + - @organizations.each do |organization| + %tr{ id: "organization-#{organization.id}" } + %td + = organization.name + %td + = organization.conferences.upcoming.count + %td + = organization.conferences.past.count + %td.text-center + - unless organization.code_of_conduct.blank? + = icon 'fa-solid', 'check', title: 'yes' + %td + .btn-group + = link_to 'Admins', admins_admin_organization_path(organization), + method: :get, class: 'btn btn-success' + = link_to 'Edit', edit_admin_organization_path(organization), + method: :get, class: 'btn btn-primary' + = link_to 'Delete', admin_organization_path(organization), + method: :delete, class: 'btn btn-danger', data: { confirm: "Warning: This will delete #{organization.name} and all its data which includes data for all conferences within #{organization.name}. Do you really want to continue?" } + = link_to 'Add Conference', new_admin_conference_path, method: :get, class: 'btn btn-info' diff --git a/info.yml b/info.yml index 40cec3e6a..df1335b63 100644 --- a/info.yml +++ b/info.yml @@ -23,7 +23,7 @@ project: surname: 'Pau' githubUsername: 'Juapu' pivotalUsername: 'Jupau' - herokuEmail: 'jupau@berkeley.edu' + herokuEmail: 'jupau@berkeley.edu ' member4: name: 'Shiv' surname: 'Sethi' diff --git a/package-lock.json b/package-lock.json index d2cf3735d..d41ef7e66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,184 +5,29 @@ "packages": { "": { "dependencies": { - "@types/jquery": "^3.2.7", - "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } }, - "node_modules/@fullcalendar/core": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.4.tgz", - "integrity": "sha512-ZDD0Owv0LezAk14nsRNaOc9nbowItGmT0mnjOhEw+L6B8P5eads8yYaNA9itn70MWoOjiAG8xqD7Yk1iJGxqgQ==", - "dependencies": { - "preact": "^10.0.5" - } - }, - "node_modules/@fullcalendar/daygrid": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.4.tgz", - "integrity": "sha512-X0QWEiA/hT8GYiQzmXt9DlZTWaQbNtBHBXGtaMNcVXbGHDCzLoWTHrde/jABGfr/i2+d9sLUO4oTtwz2HVpNtQ==", - "peerDependencies": { - "@fullcalendar/core": "~6.1.4" - } - }, - "node_modules/@fullcalendar/interaction": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.4.tgz", - "integrity": "sha512-69G2B61bPiHy7VyTDiwU9l8yRHPUK9XxNxjIdm3N0nvWR6BaUIBDQe8dIWht+IZUf9qirFhnfcLWkRI0fOTWtw==", - "peerDependencies": { - "@fullcalendar/core": "~6.1.4" - } - }, - "node_modules/@fullcalendar/list": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.4.tgz", - "integrity": "sha512-RY2fE9J7ckzhKtvHOWhlI2FnVEBC4Z9ZlgRBE2nQekX2+THt+3KnZtOQxuYGnGi30NBKx794YsXeIc6/Lnl0Ww==", - "peerDependencies": { - "@fullcalendar/core": "~6.1.4" - } - }, - "node_modules/@fullcalendar/multimonth": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.4.tgz", - "integrity": "sha512-bljwyIER4APKTpKF/M8u0BkMDbYDKVkSszCSg1VIX9uVTPpAbzlStqiMAwT+zoOPvoQ8Hsn+yuCFFKKbfUwAFw==", - "dependencies": { - "@fullcalendar/daygrid": "~6.1.4" - }, - "peerDependencies": { - "@fullcalendar/core": "~6.1.4" - } - }, - "node_modules/@fullcalendar/timegrid": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.4.tgz", - "integrity": "sha512-B2/levLKW0CyDQru75JeuASpCZml5W8sINCwVTstxxhjKmNBG3F5qvSX12DDrTdga/ySYObNNP1pKDwavKk/JQ==", - "dependencies": { - "@fullcalendar/daygrid": "~6.1.4" - }, - "peerDependencies": { - "@fullcalendar/core": "~6.1.4" - } - }, - "node_modules/@types/jquery": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", - "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", - "dependencies": { - "@types/sizzle": "*" - } - }, - "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" - }, - "node_modules/fullcalendar": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.4.tgz", - "integrity": "sha512-XAgWTauifC8HKkbN8574wEWwWCgzcgMfjkxyvK1V5D0Q/HKldZlYuvOSrI0JKkzIXtufurx9IN+QljVQkAvg+A==", - "dependencies": { - "@fullcalendar/core": "~6.1.4", - "@fullcalendar/daygrid": "~6.1.4", - "@fullcalendar/interaction": "~6.1.4", - "@fullcalendar/list": "~6.1.4", - "@fullcalendar/multimonth": "~6.1.4", - "@fullcalendar/timegrid": "~6.1.4" - } - }, "node_modules/fullcalendar-scheduler": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" - }, - "node_modules/preact": { - "version": "10.13.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.0.tgz", - "integrity": "sha512-ERdIdUpR6doqdaSIh80hvzebHB7O6JxycOhyzAeLEchqOq/4yueslQbfnPwXaNhAYacFTyCclhwkEbOumT0tHw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } } }, "dependencies": { - "@fullcalendar/core": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.4.tgz", - "integrity": "sha512-ZDD0Owv0LezAk14nsRNaOc9nbowItGmT0mnjOhEw+L6B8P5eads8yYaNA9itn70MWoOjiAG8xqD7Yk1iJGxqgQ==", - "requires": { - "preact": "^10.0.5" - } - }, - "@fullcalendar/daygrid": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.4.tgz", - "integrity": "sha512-X0QWEiA/hT8GYiQzmXt9DlZTWaQbNtBHBXGtaMNcVXbGHDCzLoWTHrde/jABGfr/i2+d9sLUO4oTtwz2HVpNtQ==", - "requires": {} - }, - "@fullcalendar/interaction": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.4.tgz", - "integrity": "sha512-69G2B61bPiHy7VyTDiwU9l8yRHPUK9XxNxjIdm3N0nvWR6BaUIBDQe8dIWht+IZUf9qirFhnfcLWkRI0fOTWtw==", - "requires": {} - }, - "@fullcalendar/list": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.4.tgz", - "integrity": "sha512-RY2fE9J7ckzhKtvHOWhlI2FnVEBC4Z9ZlgRBE2nQekX2+THt+3KnZtOQxuYGnGi30NBKx794YsXeIc6/Lnl0Ww==", - "requires": {} - }, - "@fullcalendar/multimonth": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.4.tgz", - "integrity": "sha512-bljwyIER4APKTpKF/M8u0BkMDbYDKVkSszCSg1VIX9uVTPpAbzlStqiMAwT+zoOPvoQ8Hsn+yuCFFKKbfUwAFw==", - "requires": { - "@fullcalendar/daygrid": "~6.1.4" - } - }, - "@fullcalendar/timegrid": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.4.tgz", - "integrity": "sha512-B2/levLKW0CyDQru75JeuASpCZml5W8sINCwVTstxxhjKmNBG3F5qvSX12DDrTdga/ySYObNNP1pKDwavKk/JQ==", - "requires": { - "@fullcalendar/daygrid": "~6.1.4" - } - }, - "@types/jquery": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", - "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", - "requires": { - "@types/sizzle": "*" - } - }, - "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" - }, - "fullcalendar": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.4.tgz", - "integrity": "sha512-XAgWTauifC8HKkbN8574wEWwWCgzcgMfjkxyvK1V5D0Q/HKldZlYuvOSrI0JKkzIXtufurx9IN+QljVQkAvg+A==", - "requires": { - "@fullcalendar/core": "~6.1.4", - "@fullcalendar/daygrid": "~6.1.4", - "@fullcalendar/interaction": "~6.1.4", - "@fullcalendar/list": "~6.1.4", - "@fullcalendar/multimonth": "~6.1.4", - "@fullcalendar/timegrid": "~6.1.4" - } - }, "fullcalendar-scheduler": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" }, - "preact": { - "version": "10.13.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.0.tgz", - "integrity": "sha512-ERdIdUpR6doqdaSIh80hvzebHB7O6JxycOhyzAeLEchqOq/4yueslQbfnPwXaNhAYacFTyCclhwkEbOumT0tHw==" + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } } } } From 89cb436f8a911ada45f3b0b03644043acd56f38f Mon Sep 17 00:00:00 2001 From: Juapu <70485564+Juapu@users.noreply.github.com> Date: Wed, 8 Mar 2023 13:15:05 -0800 Subject: [PATCH 026/100] Update info.yml had an extra space --- info.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/info.yml b/info.yml index df1335b63..40cec3e6a 100644 --- a/info.yml +++ b/info.yml @@ -23,7 +23,7 @@ project: surname: 'Pau' githubUsername: 'Juapu' pivotalUsername: 'Jupau' - herokuEmail: 'jupau@berkeley.edu ' + herokuEmail: 'jupau@berkeley.edu' member4: name: 'Shiv' surname: 'Sethi' From 8d6a5f376c1e0ae74a343558a54eccd233635124 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 8 Mar 2023 14:38:21 -0800 Subject: [PATCH 027/100] revert json dependencies --- package-lock.json | 9 --------- package.json | 2 -- 2 files changed, 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index d41ef7e66..e5e7a779a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,15 +19,6 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/fullcalendar-scheduler/-/fullcalendar-scheduler-5.6.0.tgz", "integrity": "sha512-0jx/Q+6QqzvIGWU/52OE27UVjVAiM9E0o/qRwRcEhyuKlL54ypTF/yXP/wo0ctUtui5dDsilGNsSsba1xmARJA==" - }, - "node_modules/preact": { - "version": "10.12.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", - "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } } } } diff --git a/package.json b/package.json index e06a6cc61..d8f5ba1af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,5 @@ { "dependencies": { - "@types/jquery": "^3.2.7", - "fullcalendar": "^6.1.4", "fullcalendar-scheduler": "^5.6.0" } } From a28bc383ea7d141f1ad0dcbb55f474c36f578da1 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 9 Mar 2023 12:24:14 -0800 Subject: [PATCH 028/100] extra tab and dotenvexample file --- .github/workflows/spec.yml | 2 +- dotenv.example | 126 +++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 dotenv.example diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 121dd35ec..a1f672f72 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -17,7 +17,7 @@ jobs: env: PRONTO_PULL_REQUEST_ID: ${{ github.event.pull_request.number }} PRONTO_GITHUB_ACCESS_TOKEN: "${{ github.token }}" - CCTR: ./cc-test-reporter + CCTR: ./cc-test-reporter CCTR_ID: ${{ secrets.CC_TEST_REPORTER_ID }} steps: - uses: actions/checkout@v2 diff --git a/dotenv.example b/dotenv.example new file mode 100644 index 000000000..372800ec9 --- /dev/null +++ b/dotenv.example @@ -0,0 +1,126 @@ +# Set environment variables for OSEM in this file and copy it to .env or +# .env.$environment (like env.production or env.development) +# +# The following is a list of variables that OSEM uses to +# configure the system/application. Uncomment them to change +# the defaults. + +# Your secret key base. You can generate a secure one with +# bundle exec rake secret +# SECRET_KEY_BASE=12345 + +# The type of database to use (postgresql, mysql2, sqlite3) +# OSEM_DB_ADAPTER=mysql2 + +# The name of the host the database runs on +# OSEM_DB_HOST=database + +# The port the databse runs on +# OSEM_DB_PORT=3306 + +# The user to access the database with +# OSEM_DB_USER=root + +# The password to access the database with +# OSEM_DB_PASSWORD=mysecretpassword + +# The name of the database +# OSEM_DB_NAME=osem_production + +# The memached servers to use, default is a file based cache +# OSEM_MEMCACHED_SERVERS='cache-1.example.com,cache-2.example.com' +# OSEM_MEMCACHED_USERNAME='root' +# OSEM_MEMCACHED_PASSWORD='1234' + +# Set this if you want to deviate from our 'standard' ruby version +# OSEM_RUBY_VERSION=3.1.2 + +# What time is it? +# OSEM_TIME_ZONE="UTC" + +# The name of your OSEM installation +# OSEM_NAME=OSEM + +# The host this OSEM instance runs on. Used for +# generating urls in emails sent +# OSEM_HOSTNAME=osem.example.com + +# The address OSEM uses for sending mails +# OSEM_EMAIL_ADDRESS="no-reply@example.com" + +# The api key for transifex.com. +# See https://github.com/openSUSE/osem/wiki/Translation +# OSEM_TRANSIFEX_APIKEY=1234 + +# sentry.io DSN key +# OSEM_SENTRY_DSN=1234 + +# OMNIAUTH Developer Key/Secret for GOOGLE +# OSEM_GOOGLE_KEY=1234 +# OSEM_GOOGLE_SECRET=5678 + +# OMNIAUTH Developer Key/Secret for Facebook +# OSEM_FACEBOOK_KEY=1234 +# OSEM_FACEBOOK_SECRET=5678 + +# OMNIAUTH Developer Key/Secret for GitHub +# OSEM_GITHUB_KEY=1234 +# OSEM_GITHUB_SECRET=5678 + +# OMNIAUTH Developer KEY/Secret for SUSE/openSUSE +# OSEM_SUSE_KEY=1234 +# OSEM_SUSE_SECRET=5678 + +# STRIPE Publishable/Secret keys +# -> https://github.com/openSUSE/osem/wiki/Stripe +# STRIPE_PUBLISHABLE_KEY=1234 +# STRIPE_SECRET_KEY=5678 + +# MATOMO/PIWIK CONFIGURATION +# OSEM_PIWIK_ID=12345 +# OSEM_PIWIK_URL=localhost +# OSEM_PIWIK_ASYNC=false +# OSEM_PIWIK_DISABLED=true +# OSEM_PIWIK_HOSTNAME=localhost + +# The smtp configuration. See the rails guides for more +# http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration +# OSEM_SMTP_ADDRESS=smtp.gmail.com +# OSEM_SMTP_PORT=587 +# OSEM_SMTP_DOMAIN=example.com +# OSEM_SMTP_USERNAME=user1 +# OSEM_SMTP_PASSWORD=password123 +# OSEM_SMTP_AUTHENTICATION=plain +# OSEM_SMTP_ENABLE_STARTTLS_AUTO=true +# OSEM_SMTP_OPENSSL_VERIFY_MODE=peer + +# Enable the usage of the devise ichain plugin +# OSEM_ICHAIN_ENABLED=false +# OSEM_ICHAIN_BASE_URL=https://example.com + +# ReCAPTCHA keys +# RECAPTCHA_SITE_KEY=1234 +# RECAPTCHA_SECRET_KEY=5678 + +# The Conference#short_title to redirect the root URL to +# OSEM_ROOT_CONFERENCE=osc18 + +# log level in production environment +# OSEM_LOG_LEVEL=info + +# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. +# FORCE_SSL=true + +# Your skylight.io keys +# SKYLIGHT_AUTHENTICATION=1234 +# SKYLIGHT_PUBLIC_DASHBOARD_URL='https://oss.skylight.io/app/applications/xxxxxxxxxxxx' + +# How should browser tests be performed? +# For headless Chrome (default): +# OSEM_TEST_DRIVER=chrome_headless +# For Chrome: +# OSEM_TEST_DRIVER=chrome +# For headless Firefox: +# OSEM_TEST_DRIVER=firefox_headless +# For Firefox: +# OSEM_TEST_DRIVER=firefox From e5d3ef740413693a672f900bcab1fab73aa008d1 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 9 Mar 2023 12:27:54 -0800 Subject: [PATCH 029/100] moving env variables --- .github/workflows/spec.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index a1f672f72..0d55074bc 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -17,8 +17,6 @@ jobs: env: PRONTO_PULL_REQUEST_ID: ${{ github.event.pull_request.number }} PRONTO_GITHUB_ACCESS_TOKEN: "${{ github.token }}" - CCTR: ./cc-test-reporter - CCTR_ID: ${{ secrets.CC_TEST_REPORTER_ID }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 @@ -37,6 +35,8 @@ jobs: env: OSEM_DB_ADAPTER: sqlite3 RAILS_ENV: test + CCTR: ./cc-test-reporter + CCTR_ID: ${{ secrets.CC_TEST_REPORTER_ID }} strategy: matrix: suite: [models, features, controllers, ability, leftovers, cucumber] From eec43abefdc7372eac98046698ca467a09af8869 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 9 Mar 2023 12:39:49 -0800 Subject: [PATCH 030/100] cucumber --- features/support/env.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/features/support/env.rb b/features/support/env.rb index fcc73dc6f..7a4abde67 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -8,6 +8,12 @@ # frozen_string_literal: true + +require "simplecov" + +SimpleCov.start "rails" do + command_name "cucumber" +end # Capybara defaults to CSS3 selectors rather than XPath. # If you'd prefer to use XPath, just uncomment this line and adjust any # selectors in your step definitions to use the XPath syntax. From 7e2f2fa7807618fc0b74a3a57111aa555dab77f7 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 16:12:01 -0700 Subject: [PATCH 031/100] simplecov 0.21.2 --- Gemfile | 4 +++- Gemfile.lock | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index cbb76e8f9..7d89d523e 100644 --- a/Gemfile +++ b/Gemfile @@ -266,7 +266,9 @@ group :test do gem 'rspec-rails' gem 'webdrivers' # for measuring test coverage - gem 'simplecov-cobertura' + gem 'simplecov', "0.21.2" + #try 0.20, 0.21, 0.22 + # for describing models gem 'shoulda-matchers', require: false # for stubing/mocking models diff --git a/Gemfile.lock b/Gemfile.lock index 438fcef14..020233508 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -428,6 +428,8 @@ GEM next_rails (1.1.0) colorize (>= 0.8.1) nio4r (2.5.8) + nokogiri (1.14.2-arm64-darwin) + racc (~> 1.4) nokogiri (1.14.2-x86_64-darwin) racc (~> 1.4) nokogiri (1.14.2-x86_64-linux) @@ -680,13 +682,10 @@ GEM shoulda-matchers (5.1.0) activesupport (>= 5.2.0) simple_po_parser (1.1.6) - simplecov (0.22.0) + simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-cobertura (2.1.0) - rexml - simplecov (~> 0.19) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) sixarm_ruby_unaccent (1.2.0) @@ -762,6 +761,7 @@ GEM zeitwerk (2.6.7) PLATFORMS + arm64-darwin-22 x86_64-darwin-21 x86_64-darwin-22 x86_64-linux @@ -880,7 +880,7 @@ DEPENDENCIES sentry-rails sentry-ruby shoulda-matchers - simplecov-cobertura + simplecov (= 0.21.2) skylight (~> 5) sprockets-rails sqlite3 From edc6cd996e570b6f469a06d2f3932700c5592da7 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 16:16:47 -0700 Subject: [PATCH 032/100] added simplecov-cobertura --- Gemfile | 3 ++- Gemfile.lock | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 7d89d523e..0aba01a97 100644 --- a/Gemfile +++ b/Gemfile @@ -266,7 +266,8 @@ group :test do gem 'rspec-rails' gem 'webdrivers' # for measuring test coverage - gem 'simplecov', "0.21.2" + gem 'simplecov', '0.21.2' + gem 'simplecov-cobertura' #try 0.20, 0.21, 0.22 # for describing models diff --git a/Gemfile.lock b/Gemfile.lock index 020233508..bbad9424c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -686,6 +686,9 @@ GEM docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) + simplecov-cobertura (2.1.0) + rexml + simplecov (~> 0.19) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) sixarm_ruby_unaccent (1.2.0) @@ -881,6 +884,7 @@ DEPENDENCIES sentry-ruby shoulda-matchers simplecov (= 0.21.2) + simplecov-cobertura skylight (~> 5) sprockets-rails sqlite3 From 539b916e91da9fd01d8501dc46a74e3e27309292 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 16:26:18 -0700 Subject: [PATCH 033/100] added simplecov_json_formatter --- Gemfile | 1 + Gemfile.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 0aba01a97..efa64c2ac 100644 --- a/Gemfile +++ b/Gemfile @@ -268,6 +268,7 @@ group :test do # for measuring test coverage gem 'simplecov', '0.21.2' gem 'simplecov-cobertura' + gem 'simplecov_json_formatter' #try 0.20, 0.21, 0.22 # for describing models diff --git a/Gemfile.lock b/Gemfile.lock index bbad9424c..afbb3aa5c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -885,6 +885,7 @@ DEPENDENCIES shoulda-matchers simplecov (= 0.21.2) simplecov-cobertura + simplecov_json_formatter skylight (~> 5) sprockets-rails sqlite3 From 3b863c500a2efacce0d4c4c08e030bd5284546d4 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 16:48:42 -0700 Subject: [PATCH 034/100] adding json formatter to simplecov --- spec/spec_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0fa89ba08..605356799 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,8 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' require 'simplecov' +require "simplecov_json_formatter" +SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter if ENV['CI'] require 'simplecov-cobertura' From 030aaec2582acab3864da3778a1ac9bb5173362b Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 16:56:43 -0700 Subject: [PATCH 035/100] matrix.suite on spec --- .github/workflows/spec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 0d55074bc..c31ec7e2b 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -68,7 +68,7 @@ jobs: - name: spec/${{ matrix.suite }} run: | xvfb-run --auto-servernum bundle exec rake spec:${{ matrix.suite }} - $CCTR format-coverage --output coverage/codeclimate.$SUITE.json --input-type simplecov + $CCTR format-coverage --output coverage/codeclimate.${{ matrix.suite }}.json --input-type simplecov # - name: coverage upload ${{ matrix.suite }} # uses: codacy/codacy-coverage-reporter-action@v1 # if: github.ref == 'refs/heads/master' && always() From 1ed88ac158b896504c47ea21ac20401625fe2feb Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 17:05:47 -0700 Subject: [PATCH 036/100] change simplecov version + fix some linting errors --- Gemfile | 4 ++-- features/support/env.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index efa64c2ac..258a774c9 100644 --- a/Gemfile +++ b/Gemfile @@ -266,10 +266,10 @@ group :test do gem 'rspec-rails' gem 'webdrivers' # for measuring test coverage - gem 'simplecov', '0.21.2' + gem 'simplecov', '<0.18' gem 'simplecov-cobertura' gem 'simplecov_json_formatter' - #try 0.20, 0.21, 0.22 + # try 0.20, 0.21, 0.22 # for describing models gem 'shoulda-matchers', require: false diff --git a/features/support/env.rb b/features/support/env.rb index 7a4abde67..28573ad97 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -9,10 +9,10 @@ # frozen_string_literal: true -require "simplecov" +require 'simplecov' SimpleCov.start "rails" do - command_name "cucumber" + command_name 'cucumber' end # Capybara defaults to CSS3 selectors rather than XPath. # If you'd prefer to use XPath, just uncomment this line and adjust any From b7b5f2b90ed484bae3276aa23e042264481c8491 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 17:07:29 -0700 Subject: [PATCH 037/100] pushing gemfile.lock --- Gemfile.lock | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index afbb3aa5c..a49940c72 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -682,14 +682,13 @@ GEM shoulda-matchers (5.1.0) activesupport (>= 5.2.0) simple_po_parser (1.1.6) - simplecov (0.21.2) + simplecov (0.17.1) docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-cobertura (2.1.0) - rexml - simplecov (~> 0.19) - simplecov-html (0.12.3) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-cobertura (1.4.2) + simplecov (~> 0.8) + simplecov-html (0.10.2) simplecov_json_formatter (0.1.4) sixarm_ruby_unaccent (1.2.0) skylight (5.3.3) @@ -883,7 +882,7 @@ DEPENDENCIES sentry-rails sentry-ruby shoulda-matchers - simplecov (= 0.21.2) + simplecov (< 0.18) simplecov-cobertura simplecov_json_formatter skylight (~> 5) From b93395803006d7f2ac3e85b4d744cf0c0618a490 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 17:17:31 -0700 Subject: [PATCH 038/100] fixed more lint errors, removed json formatter for simplecov --- features/support/env.rb | 2 +- spec/spec_helper.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/features/support/env.rb b/features/support/env.rb index 28573ad97..98cd7a959 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -11,7 +11,7 @@ require 'simplecov' -SimpleCov.start "rails" do +SimpleCov.start 'rails' do command_name 'cucumber' end # Capybara defaults to CSS3 selectors rather than XPath. diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 605356799..0fa89ba08 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,8 +4,6 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' require 'simplecov' -require "simplecov_json_formatter" -SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter if ENV['CI'] require 'simplecov-cobertura' From a5082d3b6a39f5ddbe8c1e14492d2f12f42beae5 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 17:23:33 -0700 Subject: [PATCH 039/100] fixed more linting errors --- Gemfile | 3 --- features/support/env.rb | 1 - 2 files changed, 4 deletions(-) diff --git a/Gemfile b/Gemfile index 258a774c9..342806e32 100644 --- a/Gemfile +++ b/Gemfile @@ -268,9 +268,6 @@ group :test do # for measuring test coverage gem 'simplecov', '<0.18' gem 'simplecov-cobertura' - gem 'simplecov_json_formatter' - # try 0.20, 0.21, 0.22 - # for describing models gem 'shoulda-matchers', require: false # for stubing/mocking models diff --git a/features/support/env.rb b/features/support/env.rb index 98cd7a959..82916b709 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -8,7 +8,6 @@ # frozen_string_literal: true - require 'simplecov' SimpleCov.start 'rails' do From 3d1db39595a1f36e03d0b96b350251c3f70537d2 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 17:25:13 -0700 Subject: [PATCH 040/100] added gemfile.lock --- Gemfile.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a49940c72..0e8f6dbf3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -689,7 +689,6 @@ GEM simplecov-cobertura (1.4.2) simplecov (~> 0.8) simplecov-html (0.10.2) - simplecov_json_formatter (0.1.4) sixarm_ruby_unaccent (1.2.0) skylight (5.3.3) activesupport (>= 5.2.0) @@ -884,7 +883,6 @@ DEPENDENCIES shoulda-matchers simplecov (< 0.18) simplecov-cobertura - simplecov_json_formatter skylight (~> 5) sprockets-rails sqlite3 From 033f309ae40f5da292b38ee53025d9f9e2364e87 Mon Sep 17 00:00:00 2001 From: Monis Mohiuddin Date: Wed, 15 Mar 2023 17:41:54 -0700 Subject: [PATCH 041/100] updated rubocop --- .codeclimate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 4e984a42a..581ab3566 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,4 +1,4 @@ plugins: rubocop: enabled: true - channel: rubocop-0-75 + channel: rubocop-1-39-0 From 949f980ce0bbe9b710e8629aeca1644c48750223 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 23 Mar 2023 14:37:00 -0700 Subject: [PATCH 042/100] migrations for email subject and email body --- app/models/ticket.rb | 2 ++ db/migrate/20230323200402_add_email_subject_to_tickets.rb | 5 +++++ db/migrate/20230323200709_add_email_body_to_tickets.rb | 5 +++++ db/schema.rb | 4 +++- spec/factories/tickets.rb | 2 ++ spec/models/ticket_spec.rb | 2 ++ 6 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20230323200402_add_email_subject_to_tickets.rb create mode 100644 db/migrate/20230323200709_add_email_body_to_tickets.rb diff --git a/app/models/ticket.rb b/app/models/ticket.rb index df2903887..8fd167a38 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -6,6 +6,8 @@ # # id :bigint not null, primary key # description :text +# email_body :text +# email_subject :string # price_cents :integer default(0), not null # price_currency :string default("USD"), not null # registration_ticket :boolean default(FALSE) diff --git a/db/migrate/20230323200402_add_email_subject_to_tickets.rb b/db/migrate/20230323200402_add_email_subject_to_tickets.rb new file mode 100644 index 000000000..6c666cdd3 --- /dev/null +++ b/db/migrate/20230323200402_add_email_subject_to_tickets.rb @@ -0,0 +1,5 @@ +class AddEmailSubjectToTickets < ActiveRecord::Migration[7.0] + def change + add_column :tickets, :email_subject, :string + end +end diff --git a/db/migrate/20230323200709_add_email_body_to_tickets.rb b/db/migrate/20230323200709_add_email_body_to_tickets.rb new file mode 100644 index 000000000..fb0897028 --- /dev/null +++ b/db/migrate/20230323200709_add_email_body_to_tickets.rb @@ -0,0 +1,5 @@ +class AddEmailBodyToTickets < ActiveRecord::Migration[7.0] + def change + add_column :tickets, :email_body, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index dab7b14ec..12c951f60 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: 2022_04_26_082022) do +ActiveRecord::Schema[7.0].define(version: 2023_03_23_200709) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -550,6 +550,8 @@ t.datetime "created_at" t.datetime "updated_at" t.boolean "visible", default: true + t.string "email_subject" + t.text "email_body" end create_table "tracks", force: :cascade do |t| diff --git a/spec/factories/tickets.rb b/spec/factories/tickets.rb index 9adda4f98..e4ec0c824 100644 --- a/spec/factories/tickets.rb +++ b/spec/factories/tickets.rb @@ -6,6 +6,8 @@ # # id :bigint not null, primary key # description :text +# email_body :text +# email_subject :string # price_cents :integer default(0), not null # price_currency :string default("USD"), not null # registration_ticket :boolean default(FALSE) diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb index c971a6ee9..f61fc6c4b 100644 --- a/spec/models/ticket_spec.rb +++ b/spec/models/ticket_spec.rb @@ -6,6 +6,8 @@ # # id :bigint not null, primary key # description :text +# email_body :text +# email_subject :string # price_cents :integer default(0), not null # price_currency :string default("USD"), not null # registration_ticket :boolean default(FALSE) From f9178baa7840e99f9887a94494676c6b8dd611b8 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 4 Apr 2023 16:07:11 -0700 Subject: [PATCH 043/100] this updates the plaintext and html that gets sent to users to confirm purchase of ticket --- app/controllers/admin/tickets_controller.rb | 2 +- app/views/admin/tickets/_form.html.haml | 6 ++++++ app/views/mailbot/ticket_confirmation_template.html.erb | 3 ++- app/views/mailbot/ticket_confirmation_template.text.erb | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/tickets_controller.rb b/app/controllers/admin/tickets_controller.rb index dd396269d..74299c89c 100644 --- a/app/controllers/admin/tickets_controller.rb +++ b/app/controllers/admin/tickets_controller.rb @@ -89,7 +89,7 @@ def destroy def ticket_params params.require(:ticket).permit( :conference, :conference_id, - :title, :url, :description, + :title, :url, :description, :email_subject, :email_body, :price_cents, :price_currency, :price, :registration_ticket, :visible ) diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index b0a4a7ca7..b9482e82f 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -6,6 +6,12 @@ .form-group = f.label :description = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' + .form-group + = f.label :email_subject + = f.text_field :email_subject, class: 'form-control' + .form-group + = f.label :email_body + = f.text_area :email_body, rows: 10, cols: 20, class: 'form-control' .form-group = f.label :price = f.number_field :price, class: 'form-control' diff --git a/app/views/mailbot/ticket_confirmation_template.html.erb b/app/views/mailbot/ticket_confirmation_template.html.erb index 97a97c601..ad19d62ba 100644 --- a/app/views/mailbot/ticket_confirmation_template.html.erb +++ b/app/views/mailbot/ticket_confirmation_template.html.erb @@ -4,7 +4,8 @@ Dear <%= @user.name %>, Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. - + <%= @ticket_purchase.ticket.email_subject %> + <%= @ticket_purchase.ticket.email_body %> Please, find the ticket(s) pdf attached. Best wishes, diff --git a/app/views/mailbot/ticket_confirmation_template.text.erb b/app/views/mailbot/ticket_confirmation_template.text.erb index 08f5175af..757496a0b 100644 --- a/app/views/mailbot/ticket_confirmation_template.text.erb +++ b/app/views/mailbot/ticket_confirmation_template.text.erb @@ -1,7 +1,8 @@ Dear <%= @user.name %>, Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. - +<%= @ticket_purchase.ticket.email_subject %> +<%= @ticket_purchase.ticket.email_body %> Please, find the ticket(s) pdf attached. Best wishes, From 6ef68621a1c7b603d98b48ba463ec15f6f640ba0 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 5 Apr 2023 14:35:01 -0700 Subject: [PATCH 044/100] put subject in subject line, removed from body --- app/mailers/mailbot.rb | 4 +++- app/views/mailbot/ticket_confirmation_template.html.erb | 1 - app/views/mailbot/ticket_confirmation_template.text.erb | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 7b13ffce1..b2267f887 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -35,8 +35,10 @@ def ticket_confirmation_mail(ticket_purchase) template_name = 'ticket_confirmation_template' template_name = 'young_thinkers_ticket_confirmation_template' if @ticket_purchase.ticket_id == YTLF_TICKET_ID - mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", + mail(subject: "#{@ticket_purchase.ticket.email_subject} | Ticket Confirmation and PDF!", template_name: template_name) + # mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", + # template_name: template_name) end def acceptance_mail(event) diff --git a/app/views/mailbot/ticket_confirmation_template.html.erb b/app/views/mailbot/ticket_confirmation_template.html.erb index ad19d62ba..455c0f613 100644 --- a/app/views/mailbot/ticket_confirmation_template.html.erb +++ b/app/views/mailbot/ticket_confirmation_template.html.erb @@ -4,7 +4,6 @@ Dear <%= @user.name %>, Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. - <%= @ticket_purchase.ticket.email_subject %> <%= @ticket_purchase.ticket.email_body %> Please, find the ticket(s) pdf attached. diff --git a/app/views/mailbot/ticket_confirmation_template.text.erb b/app/views/mailbot/ticket_confirmation_template.text.erb index 757496a0b..7acc04625 100644 --- a/app/views/mailbot/ticket_confirmation_template.text.erb +++ b/app/views/mailbot/ticket_confirmation_template.text.erb @@ -1,7 +1,6 @@ Dear <%= @user.name %>, Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. -<%= @ticket_purchase.ticket.email_subject %> <%= @ticket_purchase.ticket.email_body %> Please, find the ticket(s) pdf attached. From c5328f9e3ae5260a6ba14e4d18088e5ad1e8b9c9 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 5 Apr 2023 15:12:21 -0700 Subject: [PATCH 045/100] default values for email subject and body when registering --- app/views/admin/tickets/_form.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index b9482e82f..4ead978cf 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -8,10 +8,10 @@ = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' .form-group = f.label :email_subject - = f.text_field :email_subject, class: 'form-control' + = f.text_field :email_subject, value: "#{@conference.title} | Ticket Confirmation and PDF!", class: 'form-control' .form-group - = f.label :email_body - = f.text_area :email_body, rows: 10, cols: 20, class: 'form-control' + = f.label :email_body + = f.text_area :email_body, value: "Default email body".html_safe, rows: 10, cols: 20, class: 'form-control' .form-group = f.label :price = f.number_field :price, class: 'form-control' From 25317bf6a76bd514d10d830321ac8e934b8e04a6 Mon Sep 17 00:00:00 2001 From: killamonis Date: Thu, 6 Apr 2023 15:01:59 -0700 Subject: [PATCH 046/100] fix lint error --- app/mailers/mailbot.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index b2267f887..9b925ac0d 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -35,8 +35,7 @@ def ticket_confirmation_mail(ticket_purchase) template_name = 'ticket_confirmation_template' template_name = 'young_thinkers_ticket_confirmation_template' if @ticket_purchase.ticket_id == YTLF_TICKET_ID - mail(subject: "#{@ticket_purchase.ticket.email_subject} | Ticket Confirmation and PDF!", - template_name: template_name) + mail(subject: "#{@ticket_purchase.ticket.email_subject} | Ticket Confirmation and PDF!", template_name: template_name) # mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", # template_name: template_name) end From 15d571f2a67b2d14aa2806169ec27fdc7a47867f Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 11 Apr 2023 14:26:34 -0700 Subject: [PATCH 047/100] adding front-end templates --- app/mailers/mailbot.rb | 4 ++ app/models/ticket_purchase.rb | 2 + app/views/admin/tickets/_form.html.haml | 12 ++++- app/views/admin/tickets/_help.html.haml | 63 +++++++++++++++++++++++++ app/views/payments/_payment.html.haml | 2 +- 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 app/views/admin/tickets/_help.html.haml diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 9b925ac0d..5fe9643b7 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -32,9 +32,13 @@ def ticket_confirmation_mail(ticket_purchase) attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}.pdf"] = pdf.render end + template_name = 'ticket_confirmation_template' template_name = 'young_thinkers_ticket_confirmation_template' if @ticket_purchase.ticket_id == YTLF_TICKET_ID + #add template for custom ticket email body + #use that if email body is not empty + mail(subject: "#{@ticket_purchase.ticket.email_subject} | Ticket Confirmation and PDF!", template_name: template_name) # mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", # template_name: template_name) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 32ac9b670..252150fdb 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -15,6 +15,8 @@ # ticket_id :integer # user_id :integer # + +#add a currency field class TicketPurchase < ApplicationRecord belongs_to :ticket belongs_to :user diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 4ead978cf..4bac655eb 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -8,10 +8,18 @@ = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' .form-group = f.label :email_subject - = f.text_field :email_subject, value: "#{@conference.title} | Ticket Confirmation and PDF!", class: 'form-control' + = f.text_field :email_subject, class: 'form-control' .form-group = f.label :email_body - = f.text_area :email_body, value: "Default email body".html_safe, rows: 10, cols: 20, class: 'form-control' + = f.text_area :email_body, rows: 10, cols: 20, class: 'form-control' + %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'ticket_email_subject', + 'data-subject-text' => '{conference} | Ticket Confirmation and PDF!', + 'data-body-input-id' => 'ticket_email_body', + 'data-body-text' => "Dear {user},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help + = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } + -# template here + -# booth not necessary .form-group = f.label :price = f.number_field :price, class: 'form-control' diff --git a/app/views/admin/tickets/_help.html.haml b/app/views/admin/tickets/_help.html.haml new file mode 100644 index 000000000..31ed0673d --- /dev/null +++ b/app/views/admin/tickets/_help.html.haml @@ -0,0 +1,63 @@ +.template-help{ id: id } + Valid attributes: + %table.table + %tr + %td {email} + %td The user's email address + %tr + %td {name} + %td The user's full name + %tr + %td {conference} + %td The full conference title + - if show_event_variables + %tr + %td {proposalslink} + %td A link to the user's proposal page + %tr + %td {eventtitle} + %td The title of an accepted or rejected proposal + %tr + %td {committee_review} + %td The raw text in the committee review for the proposal + %tr + %td {committee_review_html} + %td The committee review markdown rendered as HTML. + %tr + %td {conference_start_date} + %td The start date of the conference + %tr + %td {conference_end_date} + %td The end date of the conference + - if @conference.registration_dates_given? + %tr + %td {registration_start_date} + %td The start date of the registration period + %tr + %td {registration_end_date} + %td The end date of the registration period + %tr + %td {registrationlink} + %td A link to the registration page + - if @conference.venue + %tr + %td {venue} + %td The name of the venue + - if @conference.venue + %tr + %td {venue_address} + %td The address of the venue + - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? + %tr + %td {cfp_start_date} + %td The call for papers start date + %tr + %td {cfp_end_date} + %td The call for papers end date + - if @conference.program.schedule_public + %td {schedule_link} + %td The link to complete schedule of the conference + - if @conference.splashpage && @conference.splashpage.public? + %tr + %td {conference_splash_link} + %td The link to conference splash page \ No newline at end of file diff --git a/app/views/payments/_payment.html.haml b/app/views/payments/_payment.html.haml index 838f042ee..1b8678d8f 100644 --- a/app/views/payments/_payment.html.haml +++ b/app/views/payments/_payment.html.haml @@ -27,5 +27,5 @@ currency: @total_amount_to_pay.currency, name: ENV.fetch('OSEM_NAME', 'OSEM'), description: "#{@conference.title} tickets", - key: Rails.application.secrets.stripe_publishable_key, locale: "auto"}} + key: ENV['STRIPE_PUBLISHABLE_KEY'] || Rails.application.secrets.stripe_publishable_key, locale: "auto"}} = link_to 'Edit Purchase', conference_tickets_path(@conference.short_title), class: 'btn btn-default' From f00523db48556a650f88c582a99deedf3eb1ebb3 Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 11 Apr 2023 15:00:52 -0700 Subject: [PATCH 048/100] added mailbot functionality --- app/mailers/mailbot.rb | 29 ++++++++++++++++++------- app/views/admin/tickets/_form.html.haml | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 5fe9643b7..987c32ce2 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -32,16 +32,29 @@ def ticket_confirmation_mail(ticket_purchase) attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}.pdf"] = pdf.render end - - template_name = 'ticket_confirmation_template' - template_name = 'young_thinkers_ticket_confirmation_template' if @ticket_purchase.ticket_id == YTLF_TICKET_ID + if @ticket_purchase.ticket_id == YTLF_TICKET_ID + template_name = 'young_thinkers_ticket_confirmation_template' + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: template_name) + end - #add template for custom ticket email body - #use that if email body is not empty + default_template_name = 'ticket_confirmation_template' + custom_template_name = 'custom_confirmation_template' - mail(subject: "#{@ticket_purchase.ticket.email_subject} | Ticket Confirmation and PDF!", template_name: template_name) - # mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", - # template_name: template_name) + default_email_subject = "#{@conference.title} | Ticket Confirmation and PDF!" + + #if email subject is empty, use custom template + if @ticket_purchase.ticket.email_subject.empty? and !@ticket_purchase.ticket.email_body.empty? + mail(subject: default_email_subject, template_name: custom_template_name) + #if email body is empty, use default template with subject + elsif !@ticket_purchase.ticket.email_subject.empty? and @ticket_purchase.ticket.email_body.empty? + mail(subject: @ticket_purchase.ticket.email_subject, template_name: default_template_name) + #if both exist, use custom + elsif @ticket_purchase.ticket.email_subject.empty? and @ticket_purchase.ticket.email_body.empty? + mail(subject: custom_email_subject template_name: custom_template_name) + #if both empty, use default + else + mail(subject: default_email_subject, template_name: default_template_name) + end end def acceptance_mail(event) diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 4bac655eb..616854393 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -15,7 +15,7 @@ %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'ticket_email_subject', 'data-subject-text' => '{conference} | Ticket Confirmation and PDF!', 'data-body-input-id' => 'ticket_email_body', - 'data-body-text' => "Dear {user},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default + 'data-body-text' => "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } -# template here From 8e43b565e25dcf89afd7e4ff757d489c35a563fc Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 11 Apr 2023 15:05:27 -0700 Subject: [PATCH 049/100] added to help partial --- app/views/admin/tickets/_form.html.haml | 2 +- app/views/admin/tickets/_help.html.haml | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 616854393..53d3c4cb0 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -15,7 +15,7 @@ %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'ticket_email_subject', 'data-subject-text' => '{conference} | Ticket Confirmation and PDF!', 'data-body-input-id' => 'ticket_email_body', - 'data-body-text' => "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default + 'data-body-text' => "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default Email %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } -# template here diff --git a/app/views/admin/tickets/_help.html.haml b/app/views/admin/tickets/_help.html.haml index 31ed0673d..29faf309d 100644 --- a/app/views/admin/tickets/_help.html.haml +++ b/app/views/admin/tickets/_help.html.haml @@ -60,4 +60,13 @@ - if @conference.splashpage && @conference.splashpage.public? %tr %td {conference_splash_link} - %td The link to conference splash page \ No newline at end of file + %td The link to conference splash page + %tr + %td {ticket_quantity} + %td The quantity of tickets purchased + %tr + %td {ticket_title} + %td The ticket title + %tr + %td {ticket_purchase_id} + %td The id of the ticket purchase transaction From a8c50921ac22fd6ea309b381a900d77d75140d00 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 11 Apr 2023 15:16:28 -0700 Subject: [PATCH 050/100] generate mail method --- app/models/ticket_purchase.rb | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 252150fdb..32c7048dc 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -16,7 +16,7 @@ # user_id :integer # -#add a currency field +#add a currency field class TicketPurchase < ApplicationRecord belongs_to :ticket belongs_to :user @@ -119,6 +119,33 @@ def set_week save! end +def get_values(event = nil, booth = nil) + h = { + 'name' => user.name, + 'conference' => conference.title, + 'ticket_quantity' => quantity, + 'ticket_title' => ticket.title, + 'ticket_purchase_id' => ticket.id + } + +end + +def generate_confirmation_mail(event_template) + parse_template(event_template, get_values) +end + +def parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end + end + text +end + + def count_purchased_registration_tickets(conference, purchases) # TODO: WHAT CAUSED THIS??? return 0 unless purchases From 12957fb418c0c49a453fcc6e72c479aeddf5c04d Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 11 Apr 2023 16:14:46 -0700 Subject: [PATCH 051/100] added confirmation template, fixed some mailbot logic --- app/mailers/mailbot.rb | 15 +++++++++---- app/models/ticket_purchase.rb | 21 ++++++++++--------- app/views/admin/tickets/_help.html.haml | 13 ------------ ...stom_ticket_confirmation_template.html.erb | 7 +++++++ ...stom_ticket_confirmation_template.text.erb | 1 + 5 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 app/views/mailbot/custom_ticket_confirmation_template.html.erb create mode 100644 app/views/mailbot/custom_ticket_confirmation_template.text.erb diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 987c32ce2..93bb9e220 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -38,19 +38,26 @@ def ticket_confirmation_mail(ticket_purchase) end default_template_name = 'ticket_confirmation_template' - custom_template_name = 'custom_confirmation_template' + custom_template_name = 'custom_ticket_confirmation_template' default_email_subject = "#{@conference.title} | Ticket Confirmation and PDF!" - + #if email subject is empty, use custom template if @ticket_purchase.ticket.email_subject.empty? and !@ticket_purchase.ticket.email_body.empty? + puts "subject is empty" + @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) + @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) mail(subject: default_email_subject, template_name: custom_template_name) #if email body is empty, use default template with subject elsif !@ticket_purchase.ticket.email_subject.empty? and @ticket_purchase.ticket.email_body.empty? + puts "body is empty" mail(subject: @ticket_purchase.ticket.email_subject, template_name: default_template_name) #if both exist, use custom - elsif @ticket_purchase.ticket.email_subject.empty? and @ticket_purchase.ticket.email_body.empty? - mail(subject: custom_email_subject template_name: custom_template_name) + elsif !@ticket_purchase.ticket.email_subject.empty? and !@ticket_purchase.ticket.email_body.empty? + puts "neither is empty" + @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) + @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) + mail(subject: @ticket_purchase.ticket.email_subject, template_name: custom_template_name) #if both empty, use default else mail(subject: default_email_subject, template_name: default_template_name) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 32c7048dc..ef98d1a33 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -112,22 +112,15 @@ def registration_ticket_already_purchased end end -private - -def set_week - self.week = created_at.strftime('%W') - save! -end - def get_values(event = nil, booth = nil) h = { 'name' => user.name, 'conference' => conference.title, - 'ticket_quantity' => quantity, + 'ticket_quantity' => quantity.to_s, 'ticket_title' => ticket.title, - 'ticket_purchase_id' => ticket.id + 'ticket_purchase_id' => ticket.id.to_s } - + return h end def generate_confirmation_mail(event_template) @@ -145,6 +138,14 @@ def parse_template(text, values) text end +public :generate_confirmation_mail + +private + +def set_week + self.week = created_at.strftime('%W') + save! +end def count_purchased_registration_tickets(conference, purchases) # TODO: WHAT CAUSED THIS??? diff --git a/app/views/admin/tickets/_help.html.haml b/app/views/admin/tickets/_help.html.haml index 29faf309d..dacadf99d 100644 --- a/app/views/admin/tickets/_help.html.haml +++ b/app/views/admin/tickets/_help.html.haml @@ -10,19 +10,6 @@ %tr %td {conference} %td The full conference title - - if show_event_variables - %tr - %td {proposalslink} - %td A link to the user's proposal page - %tr - %td {eventtitle} - %td The title of an accepted or rejected proposal - %tr - %td {committee_review} - %td The raw text in the committee review for the proposal - %tr - %td {committee_review_html} - %td The committee review markdown rendered as HTML. %tr %td {conference_start_date} %td The start date of the conference diff --git a/app/views/mailbot/custom_ticket_confirmation_template.html.erb b/app/views/mailbot/custom_ticket_confirmation_template.html.erb new file mode 100644 index 000000000..dbc1e3a91 --- /dev/null +++ b/app/views/mailbot/custom_ticket_confirmation_template.html.erb @@ -0,0 +1,7 @@ +<%= render partial: "layouts/mailbot_header" %> +
+ + <%= @ticket_purchase.ticket.email_body %> + +
+<%= render partial: "layouts/mailbot_footer" %> \ No newline at end of file diff --git a/app/views/mailbot/custom_ticket_confirmation_template.text.erb b/app/views/mailbot/custom_ticket_confirmation_template.text.erb new file mode 100644 index 000000000..426630251 --- /dev/null +++ b/app/views/mailbot/custom_ticket_confirmation_template.text.erb @@ -0,0 +1 @@ +<%= @ticket_purchase.ticket.email_body %> \ No newline at end of file From 5a63112fe296f9885a8378269add7070e416f107 Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 11 Apr 2023 16:29:02 -0700 Subject: [PATCH 052/100] functionality for more variables w/ gsub --- app/models/ticket_purchase.rb | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index ef98d1a33..03bcab218 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -118,9 +118,39 @@ def get_values(event = nil, booth = nil) 'conference' => conference.title, 'ticket_quantity' => quantity.to_s, 'ticket_title' => ticket.title, - 'ticket_purchase_id' => ticket.id.to_s + 'ticket_purchase_id' => ticket.id.to_s, + 'conference_start_date' => conference.start_date, + 'conference_end_date' => conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) } - return h + if conference.program.cfp + h['cfp_start_date'] = conference.program.cfp.start_date + h['cfp_end_date'] = conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' + end + if conference.venue + h['venue'] = conference.venue.name + h['venue_address'] = conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if conference.registration_period + h['registration_start_date'] = conference.registration_period.start_date + h['registration_end_date'] = conference.registration_period.end_date + end + h end def generate_confirmation_mail(event_template) From f8ae95ef94ab01ad07d0076ed4fd9f1fa6f34c76 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 11 Apr 2023 17:04:15 -0700 Subject: [PATCH 053/100] ticket sub and body test --- app/models/ticket_purchase.rb | 38 +++++++++++++++++++++++++++++++++-- spec/features/tickets_spec.rb | 4 ++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 32c7048dc..e03ef1d9e 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -119,15 +119,49 @@ def set_week save! end -def get_values(event = nil, booth = nil) +def get_values(booth = nil) h = { 'name' => user.name, 'conference' => conference.title, 'ticket_quantity' => quantity, 'ticket_title' => ticket.title, - 'ticket_purchase_id' => ticket.id + 'ticket_purchase_id' => ticket.id, + 'conference_start_date' => conference.start_date, + 'conference_end_date' => conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) } + if conference.program.cfp + h['cfp_start_date'] = conference.program.cfp.start_date + h['cfp_end_date'] = conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' + end + + if conference.venue + h['venue'] = conference.venue.name + h['venue_address'] = conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + + if conference.registration_period + h['registration_start_date'] = conference.registration_period.start_date + h['registration_end_date'] = conference.registration_period.end_date + end + + h end def generate_confirmation_mail(event_template) diff --git a/spec/features/tickets_spec.rb b/spec/features/tickets_spec.rb index 5179698f6..7b298ad8b 100644 --- a/spec/features/tickets_spec.rb +++ b/spec/features/tickets_spec.rb @@ -22,6 +22,8 @@ fill_in 'ticket_title', with: 'Business Ticket' fill_in 'ticket_description', with: 'The business ticket' fill_in 'ticket_price', with: '100' + fill_in 'ticket_email_subject', with: 'Confirmation' + fill_in 'ticket_email_body', with: 'Hi there! This email confirms that you made a business ticket purchase!' click_button 'Create Ticket' page.find('#flash') @@ -49,6 +51,8 @@ fill_in 'ticket_title', with: 'Hidden Ticket' fill_in 'ticket_description', with: 'The hidden ticket' fill_in 'ticket_price', with: '100' + fill_in 'ticket_email_subject', with: 'Confirmation' + fill_in 'ticket_email_body', with: 'Hi there! This email confirms that you made a hidden ticket purchase!' uncheck 'ticket_visible' click_button 'Create Ticket' From 13c4fe9ec416b8f5a2de92661fcefa685fdc0c3d Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 11 Apr 2023 17:05:06 -0700 Subject: [PATCH 054/100] fixed some lint errors --- app/mailers/mailbot.rb | 21 +++++++++------------ app/models/ticket_purchase.rb | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 93bb9e220..14de8b8a7 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -32,9 +32,9 @@ def ticket_confirmation_mail(ticket_purchase) attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}.pdf"] = pdf.render end - if @ticket_purchase.ticket_id == YTLF_TICKET_ID + if @ticket_purchase.ticket_id == YTLF_TICKET_ID template_name = 'young_thinkers_ticket_confirmation_template' - mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: template_name) + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: template_name) end default_template_name = 'ticket_confirmation_template' @@ -42,23 +42,20 @@ def ticket_confirmation_mail(ticket_purchase) default_email_subject = "#{@conference.title} | Ticket Confirmation and PDF!" - #if email subject is empty, use custom template - if @ticket_purchase.ticket.email_subject.empty? and !@ticket_purchase.ticket.email_body.empty? - puts "subject is empty" + # if email subject is empty, use custom template + if @ticket_purchase.ticket.email_subject.empty? && !@ticket_purchase.ticket.email_body.empty? @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) mail(subject: default_email_subject, template_name: custom_template_name) - #if email body is empty, use default template with subject - elsif !@ticket_purchase.ticket.email_subject.empty? and @ticket_purchase.ticket.email_body.empty? - puts "body is empty" + # if email body is empty, use default template with subject + elsif !@ticket_purchase.ticket.email_subject.empty? && @ticket_purchase.ticket.email_body.empty? mail(subject: @ticket_purchase.ticket.email_subject, template_name: default_template_name) - #if both exist, use custom - elsif !@ticket_purchase.ticket.email_subject.empty? and !@ticket_purchase.ticket.email_body.empty? - puts "neither is empty" + # if both exist, use custom + elsif !@ticket_purchase.ticket.email_subject.empty? && !@ticket_purchase.ticket.email_body.empty? @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) mail(subject: @ticket_purchase.ticket.email_subject, template_name: custom_template_name) - #if both empty, use default + # if both empty, use default else mail(subject: default_email_subject, template_name: default_template_name) end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 03bcab218..29fd8c4fe 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -16,7 +16,7 @@ # user_id :integer # -#add a currency field +# add a currency field class TicketPurchase < ApplicationRecord belongs_to :ticket belongs_to :user @@ -112,7 +112,7 @@ def registration_ticket_already_purchased end end -def get_values(event = nil, booth = nil) +def get_values h = { 'name' => user.name, 'conference' => conference.title, From 03ef2078856ea815436a9c7542e0803a78e3b9ef Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 11 Apr 2023 17:05:55 -0700 Subject: [PATCH 055/100] saving --- spec/features/tickets_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/features/tickets_spec.rb b/spec/features/tickets_spec.rb index 7b298ad8b..43601921c 100644 --- a/spec/features/tickets_spec.rb +++ b/spec/features/tickets_spec.rb @@ -28,6 +28,8 @@ click_button 'Create Ticket' page.find('#flash') expect(flash).to eq('Ticket successfully created.') + expect(ticket.email_subject).to eq('Confirmation') + expect(ticket.email_body).to eq('Hi there! This email confirms that you made a business ticket purchase!') expect(Ticket.count).to eq(2) end From 6bf3bc0ae62d22b2b33a57d757ce67896af37734 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 12 Apr 2023 13:48:49 -0700 Subject: [PATCH 056/100] fixed spec test --- spec/features/tickets_spec.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/spec/features/tickets_spec.rb b/spec/features/tickets_spec.rb index 43601921c..ec3847c3c 100644 --- a/spec/features/tickets_spec.rb +++ b/spec/features/tickets_spec.rb @@ -22,14 +22,10 @@ fill_in 'ticket_title', with: 'Business Ticket' fill_in 'ticket_description', with: 'The business ticket' fill_in 'ticket_price', with: '100' - fill_in 'ticket_email_subject', with: 'Confirmation' - fill_in 'ticket_email_body', with: 'Hi there! This email confirms that you made a business ticket purchase!' click_button 'Create Ticket' page.find('#flash') expect(flash).to eq('Ticket successfully created.') - expect(ticket.email_subject).to eq('Confirmation') - expect(ticket.email_body).to eq('Hi there! This email confirms that you made a business ticket purchase!') expect(Ticket.count).to eq(2) end @@ -53,8 +49,6 @@ fill_in 'ticket_title', with: 'Hidden Ticket' fill_in 'ticket_description', with: 'The hidden ticket' fill_in 'ticket_price', with: '100' - fill_in 'ticket_email_subject', with: 'Confirmation' - fill_in 'ticket_email_body', with: 'Hi there! This email confirms that you made a hidden ticket purchase!' uncheck 'ticket_visible' click_button 'Create Ticket' @@ -73,6 +67,8 @@ fill_in 'ticket_title', with: 'Event Ticket' fill_in 'ticket_price', with: '50' + fill_in 'ticket_email_subject', with: 'Confirmation' + fill_in 'ticket_email_body', with: 'Hi there! This email confirms that you made a business ticket purchase!' click_button 'Update Ticket' @@ -81,6 +77,8 @@ page.find('#flash') expect(flash).to eq('Ticket successfully updated.') expect(ticket.price).to eq(Money.new(50 * 100, 'USD')) + expect(ticket.email_subject).to eq('Confirmation') + expect(ticket.email_body).to eq('Hi there! This email confirms that you made a business ticket purchase!') expect(ticket.title).to eq('Event Ticket') expect(Ticket.count).to eq(2) end From 7169ce11e93dc3a3f2fb0299d7eb29e04a833ed0 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 12 Apr 2023 13:55:35 -0700 Subject: [PATCH 057/100] whitespace fix --- app/models/ticket_purchase.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index d94e1dcc2..29fd8c4fe 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -132,7 +132,6 @@ def get_values conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') ) } - if conference.program.cfp h['cfp_start_date'] = conference.program.cfp.start_date h['cfp_end_date'] = conference.program.cfp.end_date @@ -140,7 +139,6 @@ def get_values h['cfp_start_date'] = 'Unknown' h['cfp_end_date'] = 'Unknown' end - if conference.venue h['venue'] = conference.venue.name h['venue_address'] = conference.venue.address @@ -148,7 +146,6 @@ def get_values h['venue'] = 'Unknown' h['venue_address'] = 'Unknown' end - if conference.registration_period h['registration_start_date'] = conference.registration_period.start_date h['registration_end_date'] = conference.registration_period.end_date From 210008a2438fdbc90b8c21d71411409317ca7f07 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 12 Apr 2023 14:16:55 -0700 Subject: [PATCH 058/100] some rubcop fixes --- app/mailers/mailbot.rb | 15 +++++---------- app/models/ticket_purchase.rb | 4 ++-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 14de8b8a7..32dfb7c2a 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -37,27 +37,22 @@ def ticket_confirmation_mail(ticket_purchase) mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: template_name) end - default_template_name = 'ticket_confirmation_template' - custom_template_name = 'custom_ticket_confirmation_template' - - default_email_subject = "#{@conference.title} | Ticket Confirmation and PDF!" - # if email subject is empty, use custom template if @ticket_purchase.ticket.email_subject.empty? && !@ticket_purchase.ticket.email_body.empty? @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) - @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) - mail(subject: default_email_subject, template_name: custom_template_name) + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: 'custom_ticket_confirmation_template') # if email body is empty, use default template with subject elsif !@ticket_purchase.ticket.email_subject.empty? && @ticket_purchase.ticket.email_body.empty? - mail(subject: @ticket_purchase.ticket.email_subject, template_name: default_template_name) + @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) + mail(subject: @ticket_purchase.ticket.email_subject, template_name: 'ticket_confirmation_template') # if both exist, use custom elsif !@ticket_purchase.ticket.email_subject.empty? && !@ticket_purchase.ticket.email_body.empty? @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) - mail(subject: @ticket_purchase.ticket.email_subject, template_name: custom_template_name) + mail(subject: @ticket_purchase.ticket.email_subject, template_name: 'custom_ticket_confirmation_template') # if both empty, use default else - mail(subject: default_email_subject, template_name: default_template_name) + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: 'ticket_confirmation_template') end end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 29fd8c4fe..f74a4a120 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -112,7 +112,7 @@ def registration_ticket_already_purchased end end -def get_values +def retrieve_values h = { 'name' => user.name, 'conference' => conference.title, @@ -154,7 +154,7 @@ def get_values end def generate_confirmation_mail(event_template) - parse_template(event_template, get_values) + parse_template(event_template, retrieve_values) end def parse_template(text, values) From be5601cbae62542e4c5c3ffd3ad076dd0394a342 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 12 Apr 2023 14:25:51 -0700 Subject: [PATCH 059/100] more rubcop fixes --- app/models/ticket_purchase.rb | 102 +++++++++++++++++----------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index f74a4a120..e27853ac2 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -110,66 +110,64 @@ def registration_ticket_already_purchased errors.add(:quantity, 'cannot be greater than one for registration tickets.') end end -end -def retrieve_values - h = { - 'name' => user.name, - 'conference' => conference.title, - 'ticket_quantity' => quantity.to_s, - 'ticket_title' => ticket.title, - 'ticket_purchase_id' => ticket.id.to_s, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - } - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date + def get_values + h = { + 'name' => user.name, + 'conference' => conference.title, + 'ticket_quantity' => quantity.to_s, + 'ticket_title' => ticket.title, + 'ticket_purchase_id' => ticket.id.to_s, + 'conference_start_date' => conference.start_date, + 'conference_end_date' => conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + } + if conference.program.cfp + h['cfp_start_date'] = conference.program.cfp.start_date + h['cfp_end_date'] = conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' + end + if conference.venue + h['venue'] = conference.venue.name + h['venue_address'] = conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if conference.registration_period + h['registration_start_date'] = conference.registration_period.start_date + h['registration_end_date'] = conference.registration_period.end_date + end + h end - h -end -def generate_confirmation_mail(event_template) - parse_template(event_template, retrieve_values) -end + def generate_confirmation_mail(event_template) + parse_template(event_template, get_values) + end -def parse_template(text, values) - values.each do |key, value| - if value.is_a?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? - else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + def parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end end + text end - text end -public :generate_confirmation_mail - private def set_week From bb43f2f7d27420afa845843da88ddf711413a26d Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 19 Apr 2023 11:57:50 -0700 Subject: [PATCH 060/100] Update PULL_REQUEST_TEMPLATE.md Make this CS169-specific to align with class templates --- .github/PULL_REQUEST_TEMPLATE.md | 36 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 501ce4df0..b82f589c4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,31 @@ -**Checklist** +### [Pivotal Tracker Link][tracker] -- [ ] I have read the [Contribution & Best practices Guide](https://github.com/openSUSE/osem/blob/master/CONTRIBUTING.md). -- [ ] My branch is up-to-date with the upstream `master` branch. -- [ ] The tests pass locally with my changes. -- [ ] I have added tests that prove my fix is effective or that my feature works(if appropriate). -- [ ] I have added necessary documentation (if appropriate). + +[tracker]: https://www.pivotaltracker.com/story/show/your-story-id -**Short description of what this resolves/which [issues](https://github.com/openSUSE/osem/issues) does this fix?:** +## What this PR does: + - +This pull request fixes|implements (pick one...) ______. -- +### Include screenshots, videos, etc. -**Changes proposed in this pull request:** +#### Who authored this PR? + - -- +### How should this PR be tested? + +* Is there a deploy we can view? +* What do the specs/features test? +* Are there edge cases to watch out for? + +#### Are there any complications to deploying this? + + + +### Checklist: + +- [ ] Has this been deployed to a staging environment or reviewed by a customer? +- [ ] Tag someone for code review (either a coach / team member) +- [ ] I have renamed the branch to match PivotTracker's suggested one (necessary for BlueJay) From eec6280b598e5645a7b2e315a07f49bca54c552e Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 19 Apr 2023 14:37:52 -0700 Subject: [PATCH 061/100] added service object email_template_parser --- app/models/email_settings.rb | 65 +++------------------------ app/models/ticket_purchase.rb | 55 ++--------------------- app/services/email_template_parser.rb | 59 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 111 deletions(-) create mode 100644 app/services/email_template_parser.rb diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index f4f50f083..cf7ec49b0 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -50,72 +50,19 @@ class EmailSettings < ApplicationRecord has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } - def get_values(conference, user, event = nil, booth = nil) - h = { - 'email' => user.email, - 'name' => user.name, - 'conference' => conference.title, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - } - - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date - end - - if event - h['eventtitle'] = event.title - h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - h['committee_review'] = event.committee_review - h['committee_review_html'] = ApplicationController.helpers.markdown(event.committee_review) - end - - h['booth_title'] = booth.title if booth - h - end - def generate_event_mail(event, event_template) - values = get_values(event.program.conference, event.submitter, event) - parse_template(event_template, values) + values = EmailTemplateParser.retrieve_values(event.program.conference, event.submitter, event) + EmailTemplateParser.parse_template(event_template, values) end def generate_email_on_conf_updates(conference, user, conf_update_template) - values = get_values(conference, user) - parse_template(conf_update_template, values) + values = EmailTemplateParser.retrieve_values(conference, user) + EmailTemplateParser.parse_template(conf_update_template, values) end def generate_booth_mail(booth, booth_template) - values = get_values(booth.conference, booth.submitter, nil, booth) - parse_template(booth_template, values) + values = EmailTemplateParser.retrieve_values(booth.conference, booth.submitter, nil, booth) + EmailTemplateParser.parse_template(booth_template, values) end private diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index e27853ac2..e2648e79b 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -111,61 +111,12 @@ def registration_ticket_already_purchased end end - def get_values - h = { - 'name' => user.name, - 'conference' => conference.title, - 'ticket_quantity' => quantity.to_s, - 'ticket_title' => ticket.title, - 'ticket_purchase_id' => ticket.id.to_s, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - } - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date - end - h - end - def generate_confirmation_mail(event_template) - parse_template(event_template, get_values) + parser = EmailTemplateParser.new() + values = parser.retrieve_values(conference, user, nil, nil, self.quantity, self.ticket) + parser.parse_template(event_template, values) end - def parse_template(text, values) - values.each do |key, value| - if value.is_a?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? - else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? - end - end - text - end end private diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb new file mode 100644 index 000000000..910fdf070 --- /dev/null +++ b/app/services/email_template_parser.rb @@ -0,0 +1,59 @@ +class EmailTemplateParser + def retrieve_values(conference, user, event = nil, booth = nil, quantity = nil, ticket = nil) + h = { + 'name' => user.name, + 'conference' => conference.title, + 'conference_start_date' => conference.start_date, + 'conference_end_date' => conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + } + puts(quantity) + puts(ticket) + if conference.program.cfp + h['cfp_start_date'] = conference.program.cfp.start_date + h['cfp_end_date'] = conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' + end + if conference.venue + h['venue'] = conference.venue.name + h['venue_address'] = conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if conference.registration_period + h['registration_start_date'] = conference.registration_period.start_date + h['registration_end_date'] = conference.registration_period.end_date + end + if quantity + h['ticket_quantity'] = quantity.to_s + end + if ticket + h['ticket_title'] = ticket.title + h['ticket_purchase_id'] = ticket.id.to_s + end + h + end + + def parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end + end + text + end +end \ No newline at end of file From ea49eef260a94a7b7f0807802d54c6786a56ad2d Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 19 Apr 2023 14:48:59 -0700 Subject: [PATCH 062/100] fixed some rubocop errors --- app/models/email_settings.rb | 28 +++---- app/models/ticket_purchase.rb | 5 +- app/services/email_template_parser.rb | 111 +++++++++++++------------- 3 files changed, 65 insertions(+), 79 deletions(-) diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index cf7ec49b0..bd38c3d8c 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -51,30 +51,20 @@ class EmailSettings < ApplicationRecord has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } def generate_event_mail(event, event_template) - values = EmailTemplateParser.retrieve_values(event.program.conference, event.submitter, event) - EmailTemplateParser.parse_template(event_template, values) + parser = EmailTemplateParser.new + values = parser.retrieve_values(event.program.conference, event.submitter, event) + parser.parse_template(event_template, values) end def generate_email_on_conf_updates(conference, user, conf_update_template) - values = EmailTemplateParser.retrieve_values(conference, user) - EmailTemplateParser.parse_template(conf_update_template, values) + parser = EmailTemplateParser.new + values = parser.retrieve_values(conference, user) + parser.parse_template(conf_update_template, values) end def generate_booth_mail(booth, booth_template) - values = EmailTemplateParser.retrieve_values(booth.conference, booth.submitter, nil, booth) - EmailTemplateParser.parse_template(booth_template, values) - end - - private - - def parse_template(text, values) - values.each do |key, value| - if value.is_a?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? - else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? - end - end - text + parser = EmailTemplateParser.new + values = parser.retrieve_values(booth.conference, booth.submitter, nil, booth) + parser.parse_template(booth_template, values) end end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index e2648e79b..f1e2ee908 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -112,11 +112,10 @@ def registration_ticket_already_purchased end def generate_confirmation_mail(event_template) - parser = EmailTemplateParser.new() - values = parser.retrieve_values(conference, user, nil, nil, self.quantity, self.ticket) + parser = EmailTemplateParser.new + values = parser.retrieve_values(conference, user, nil, nil, quantity, ticket) parser.parse_template(event_template, values) end - end private diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index 910fdf070..21580a44a 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -1,59 +1,56 @@ class EmailTemplateParser - def retrieve_values(conference, user, event = nil, booth = nil, quantity = nil, ticket = nil) - h = { - 'name' => user.name, - 'conference' => conference.title, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - } - puts(quantity) - puts(ticket) - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date - end - if quantity - h['ticket_quantity'] = quantity.to_s - end - if ticket - h['ticket_title'] = ticket.title - h['ticket_purchase_id'] = ticket.id.to_s - end - h - end - - def parse_template(text, values) - values.each do |key, value| - if value.is_a?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? - else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? - end - end - text + def retrieve_values(conference, user, event = nil, booth = nil, quantity = nil, ticket = nil) + h = { + 'name' => user.name, + 'conference' => conference.title, + 'conference_start_date' => conference.start_date, + 'conference_end_date' => conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + } + if conference.program.cfp + h['cfp_start_date'] = conference.program.cfp.start_date + h['cfp_end_date'] = conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' end -end \ No newline at end of file + if conference.venue + h['venue'] = conference.venue.name + h['venue_address'] = conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if conference.registration_period + h['registration_start_date'] = conference.registration_period.start_date + h['registration_end_date'] = conference.registration_period.end_date + end + h['ticket_quantity'] = quantity.to_s if quantity + end + if ticket + h['ticket_title'] = ticket.title + h['ticket_purchase_id'] = ticket.id.to_s + end + h['booth_title'] = booth.title if booth + end + + def parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end + end + text + end +end From 57943687ea03c4c65b27d6bc7f5aa796b4a5b135 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 19 Apr 2023 15:08:36 -0700 Subject: [PATCH 063/100] fixed end statement error --- app/services/email_template_parser.rb | 101 +++++++++++++------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index 21580a44a..68d51dbdb 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -1,56 +1,59 @@ class EmailTemplateParser def retrieve_values(conference, user, event = nil, booth = nil, quantity = nil, ticket = nil) h = { - 'name' => user.name, - 'conference' => conference.title, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - } - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date - end - h['ticket_quantity'] = quantity.to_s if quantity - end - if ticket - h['ticket_title'] = ticket.title - h['ticket_purchase_id'] = ticket.id.to_s - end - h['booth_title'] = booth.title if booth - end - - def parse_template(text, values) - values.each do |key, value| - if value.is_a?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + 'name' => user.name, + 'conference' => conference.title, + 'conference_start_date' => conference.start_date, + 'conference_end_date' => conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + } + if conference.program.cfp + h['cfp_start_date'] = conference.program.cfp.start_date + h['cfp_end_date'] = conference.program.cfp.end_date else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' end + if conference.venue + h['venue'] = conference.venue.name + h['venue_address'] = conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if conference.registration_period + h['registration_start_date'] = conference.registration_period.start_date + h['registration_end_date'] = conference.registration_period.end_date + end + if booth + h['booth_title'] = booth.title + end + if quantity + h['ticket_quantity'] = quantity.to_s + end + if ticket + h['ticket_title'] = ticket.title + h['ticket_purchase_id'] = ticket.id.to_s + end + end + + def parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end + end + text end - text - end end From 79f403b94df6e9ce8d460389b80fc9c412021fa8 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 19 Apr 2023 16:15:16 -0700 Subject: [PATCH 064/100] fixed some spec/codeclimate/lint errors, added new help partial --- .vscode/settings.json | 3 + app/models/email_settings.rb | 12 +-- app/models/ticket_purchase.rb | 4 +- app/services/email_template_parser.rb | 120 ++++++++++++++------------ app/views/shared/_help.html.haml | 72 ++++++++++++++++ 5 files changed, 150 insertions(+), 61 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 app/views/shared/_help.html.haml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..81356792e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.indentSize": "tabSize" +} \ No newline at end of file diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index bd38c3d8c..a3b369f5f 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -51,20 +51,20 @@ class EmailSettings < ApplicationRecord has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } def generate_event_mail(event, event_template) - parser = EmailTemplateParser.new - values = parser.retrieve_values(event.program.conference, event.submitter, event) + parser = EmailTemplateParser.new(event.program.conference, event.submitter) + values = parser.retrieve_values(event) parser.parse_template(event_template, values) end def generate_email_on_conf_updates(conference, user, conf_update_template) - parser = EmailTemplateParser.new - values = parser.retrieve_values(conference, user) + parser = EmailTemplateParser.new(conference, user) + values = parser.retrieve_values() parser.parse_template(conf_update_template, values) end def generate_booth_mail(booth, booth_template) - parser = EmailTemplateParser.new - values = parser.retrieve_values(booth.conference, booth.submitter, nil, booth) + parser = EmailTemplateParser.new(booth.conference, booth.submitter) + values = parser.retrieve_values(nil, booth) parser.parse_template(booth_template, values) end end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index f1e2ee908..34d5b87f4 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -112,8 +112,8 @@ def registration_ticket_already_purchased end def generate_confirmation_mail(event_template) - parser = EmailTemplateParser.new - values = parser.retrieve_values(conference, user, nil, nil, quantity, ticket) + parser = EmailTemplateParser.new(conference, user) + values = parser.retrieve_values(nil, nil, quantity, ticket) parser.parse_template(event_template, values) end end diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index 68d51dbdb..e08e5df0b 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -1,59 +1,73 @@ class EmailTemplateParser - def retrieve_values(conference, user, event = nil, booth = nil, quantity = nil, ticket = nil) - h = { - 'name' => user.name, - 'conference' => conference.title, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ), + def initialize(conference, user) + @conference = conference + @user = user + end - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') - ) - } - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date - end - if booth - h['booth_title'] = booth.title - end - if quantity - h['ticket_quantity'] = quantity.to_s - end - if ticket - h['ticket_title'] = ticket.title - h['ticket_purchase_id'] = ticket.id.to_s - end + def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) + h = { + 'name' => @user.name, + 'conference' => @conference.title, + 'conference_start_date' => @conference.start_date, + 'conference_end_date' => @conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + } + if @conference.program.cfp + h['cfp_start_date'] = @conference.program.cfp.start_date + h['cfp_end_date'] = @conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' + end + if @conference.venue + h['venue'] = @conference.venue.name + h['venue_address'] = @conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if @conference.registration_period + h['registration_start_date'] = @conference.registration_period.start_date + h['registration_end_date'] = @conference.registration_period.end_date + end + if event + h['eventtitle'] = event.title + h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( + @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + h['committee_review'] = event.committee_review + h['committee_review_html'] = ApplicationController.helpers.markdown(event.committee_review) + end + if booth + h['booth_title'] = booth.title end + if quantity + h['ticket_quantity'] = quantity.to_s + end + if ticket + h['ticket_title'] = ticket.title + h['ticket_purchase_id'] = ticket.id.to_s + end + h + end - def parse_template(text, values) - values.each do |key, value| - if value.is_a?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? - else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? - end - end - text + def parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end end + text + end end diff --git a/app/views/shared/_help.html.haml b/app/views/shared/_help.html.haml new file mode 100644 index 000000000..ccdef01f2 --- /dev/null +++ b/app/views/shared/_help.html.haml @@ -0,0 +1,72 @@ +.template-help{ id: id } + Valid attributes: + %table.table + %tr + %td {email} + %td The user's email address + %tr + %td {name} + %td The user's full name + %tr + %td {conference} + %td The full conference title + - if defined?(show_event_variables) + %tr + %td {proposalslink} + %td A link to the user's proposal page + %tr + %td {eventtitle} + %td The title of an accepted or rejected proposal + %tr + %td {committee_review} + %td The raw text in the committee review for the proposal + %tr + %td {committee_review_html} + %td The committee review markdown rendered as HTML. + %tr + %td {conference_start_date} + %td The start date of the conference + %tr + %td {conference_end_date} + %td The end date of the conference + - if defined?(@conference.registration_dates_given?) + %tr + %td {registration_start_date} + %td The start date of the registration period + %tr + %td {registration_end_date} + %td The end date of the registration period + %tr + %td {registrationlink} + %td A link to the registration page + - if defined?(@conference.venue) + %tr + %td {venue} + %td The name of the venue + %tr + %td {venue_address} + %td The address of the venue + - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? + %tr + %td {cfp_start_date} + %td The call for papers start date + %tr + %td {cfp_end_date} + %td The call for papers end date + - if @conference.program.schedule_public + %td {schedule_link} + %td The link to complete schedule of the conference + - if defined?(@conference.splashpage) && @conference.splashpage.public? + %tr + %td {conference_splash_link} + %td The link to conference splash page + - if defined?(ticket_quantity) + %tr + %td {ticket_quantity} + %td The quantity of tickets purchased + %tr + %td {ticket_title} + %td The ticket title + %tr + %td {ticket_purchase_id} + %td The id of the ticket purchase transaction From 71f9ef58fa985a4bf4d942de6a70c6ef752f472f Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 25 Apr 2023 14:55:30 -0700 Subject: [PATCH 065/100] made changes to pass spec:model --- app/models/email_settings.rb | 21 ++++++++++++--------- app/services/email_template_parser.rb | 8 +++++--- app/views/shared/_help.html.haml | 8 ++++---- spec/models/email_settings_spec.rb | 1 + 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index a3b369f5f..45814b8bf 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -49,22 +49,25 @@ class EmailSettings < ApplicationRecord belongs_to :conference has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } + + def get_values(conference, user, event = nil, booth = nil) + parser = EmailTemplateParser.new(conference, user) + values = parser.retrieve_values(event, booth) + values + end def generate_event_mail(event, event_template) - parser = EmailTemplateParser.new(event.program.conference, event.submitter) - values = parser.retrieve_values(event) - parser.parse_template(event_template, values) + values = get_values(event.program.conference, event.submitter, event) + EmailTemplateParser.parse_template(event_template, values) end def generate_email_on_conf_updates(conference, user, conf_update_template) - parser = EmailTemplateParser.new(conference, user) - values = parser.retrieve_values() - parser.parse_template(conf_update_template, values) + values = get_values(conference, user) + EmailTemplateParser.parse_template(conf_update_template, values) end def generate_booth_mail(booth, booth_template) - parser = EmailTemplateParser.new(booth.conference, booth.submitter) - values = parser.retrieve_values(nil, booth) - parser.parse_template(booth_template, values) + values = get_values(conference: booth.conference, user: booth.submitter, booth: booth) + EmailTemplateParser.parse_template(booth_template, values) end end diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index e08e5df0b..3c1ac3005 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -2,10 +2,11 @@ class EmailTemplateParser def initialize(conference, user) @conference = conference @user = user - end - + end + def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) h = { + 'email' => @user.email, 'name' => @user.name, 'conference' => @conference.title, 'conference_start_date' => @conference.start_date, @@ -40,6 +41,7 @@ def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) h['registration_end_date'] = @conference.registration_period.end_date end if event + puts("there is an event") h['eventtitle'] = event.title h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') @@ -60,7 +62,7 @@ def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) h end - def parse_template(text, values) + def self.parse_template(text, values) values.each do |key, value| if value.is_a?(Date) text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? diff --git a/app/views/shared/_help.html.haml b/app/views/shared/_help.html.haml index ccdef01f2..c8c19b94e 100644 --- a/app/views/shared/_help.html.haml +++ b/app/views/shared/_help.html.haml @@ -29,7 +29,7 @@ %tr %td {conference_end_date} %td The end date of the conference - - if defined?(@conference.registration_dates_given?) + - if defined?(conference.registration_dates_given?) %tr %td {registration_start_date} %td The start date of the registration period @@ -39,21 +39,21 @@ %tr %td {registrationlink} %td A link to the registration page - - if defined?(@conference.venue) + - if defined?(conference.venue) %tr %td {venue} %td The name of the venue %tr %td {venue_address} %td The address of the venue - - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? + - unless conference.program.cfp.blank? || conference.program.cfp.start_date.blank? || conference.program.cfp.end_date.blank? %tr %td {cfp_start_date} %td The call for papers start date %tr %td {cfp_end_date} %td The call for papers end date - - if @conference.program.schedule_public + - if conference.program.schedule_public %td {schedule_link} %td The link to complete schedule of the conference - if defined?(@conference.splashpage) && @conference.splashpage.public? diff --git a/spec/models/email_settings_spec.rb b/spec/models/email_settings_spec.rb index d62f6398d..ced013cc9 100644 --- a/spec/models/email_settings_spec.rb +++ b/spec/models/email_settings_spec.rb @@ -53,6 +53,7 @@ end let(:user) { create(:user, username: 'johnd', email: 'john@doe.com', name: 'John Doe') } let(:event) { create(:event, program: conference.program, title: 'Talk about talks', submitter: user) } + let(:emailtemplateparser) { create(:emailtemplateparser, conference: conference, user: user) } let(:expected_hash) do { 'email' => 'john@doe.com', From 638f279a5edb524f2b64f5d4921bb7be0b436f65 Mon Sep 17 00:00:00 2001 From: killamonis Date: Tue, 25 Apr 2023 14:58:12 -0700 Subject: [PATCH 066/100] fixed some rubocop errors --- app/models/email_settings.rb | 5 ++--- app/services/email_template_parser.rb | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index 45814b8bf..bd2debddb 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -49,11 +49,10 @@ class EmailSettings < ApplicationRecord belongs_to :conference has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } - + def get_values(conference, user, event = nil, booth = nil) parser = EmailTemplateParser.new(conference, user) - values = parser.retrieve_values(event, booth) - values + parser.retrieve_values(event, booth) end def generate_event_mail(event, event_template) diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index 3c1ac3005..d6123f5f2 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -41,7 +41,6 @@ def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) h['registration_end_date'] = @conference.registration_period.end_date end if event - puts("there is an event") h['eventtitle'] = event.title h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') From dcea5bf915c89358cff1c886bc7ed5162d4ff6a5 Mon Sep 17 00:00:00 2001 From: killamonis Date: Wed, 26 Apr 2023 14:05:27 -0700 Subject: [PATCH 067/100] refactored help partial for ticket + emails, removed old partials --- app/views/admin/emails/_help.html.haml | 67 ------------------------- app/views/admin/emails/index.html.haml | 24 ++++----- app/views/admin/tickets/_form.html.haml | 2 +- app/views/admin/tickets/_help.html.haml | 59 ---------------------- app/views/shared/_help.html.haml | 18 ++++--- 5 files changed, 24 insertions(+), 146 deletions(-) delete mode 100644 app/views/admin/emails/_help.html.haml delete mode 100644 app/views/admin/tickets/_help.html.haml diff --git a/app/views/admin/emails/_help.html.haml b/app/views/admin/emails/_help.html.haml deleted file mode 100644 index 1222c1ac4..000000000 --- a/app/views/admin/emails/_help.html.haml +++ /dev/null @@ -1,67 +0,0 @@ -.template-help{ id: id } - Valid attributes: - %table.table - %tr - %td {email} - %td The user's email address - %tr - %td {name} - %td The user's full name - %tr - %td {conference} - %td The full conference title - - if show_event_variables - %tr - %td {proposalslink} - %td A link to the user's proposal page - %tr - %td {eventtitle} - %td The title of an accepted or rejected proposal - %tr - %td {committee_review} - %td The raw text in the committee review for the proposal - %tr - %td {committee_review_html} - %td The committee review markdown rendered as HTML. - %tr - %td {conference_start_date} - %td The start date of the conference - %tr - %td {conference_end_date} - %td The end date of the conference - - if @conference.registration_dates_given? - %tr - %td {registration_start_date} - %td The start date of the registration period - %tr - %td {registration_end_date} - %td The end date of the registration period - %tr - %td {registrationlink} - %td A link to the registration page - - if @conference.venue - %tr - %td {venue} - %td The name of the venue - - if @conference.venue - %tr - %td {venue_address} - %td The address of the venue - - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? - %tr - %td {cfp_start_date} - %td The call for papers start date - %tr - %td {cfp_end_date} - %td The call for papers end date - - if @conference.program.schedule_public - %td {schedule_link} - %td The link to complete schedule of the conference - - if @conference.splashpage && @conference.splashpage.public? - %tr - %td {conference_splash_link} - %td The link to conference splash page - - if @conference.booths - %tr - %td {booth_title} - %td Booth's title diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml index 41a43825e..93dec608f 100644 --- a/app/views/admin/emails/index.html.haml +++ b/app/views/admin/emails/index.html.haml @@ -33,7 +33,7 @@ 'data-body-input-id' => 'email_settings_registration_body', 'data-body-text' => "Dear {name},\n\nThank you for Registering for the conference {conference}.\nPlease complete your registration by filling out your travel information.\n\nIf you are unable to attend please unregister online:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\nWe look forward to see you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'registration_help' } Show Help - = render partial: 'help', locals: { id: 'registration_help', show_event_variables: false } + = render partial: 'shared/help', locals: { id: 'registration_help', show_event_variables: false, show_ticket_variables: false, show_ticket_variables: false } #proposal.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -47,7 +47,7 @@ = f.text_area :submitted_proposal_body, input_html: { rows: 10, cols: 20 }, class: 'form-control' %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_submitted_proposal_subject', 'data-subject-text' => 'Your proposal has been submitted successfully', 'data-body-input-id' => 'email_settings_submitted_proposal_body', 'data-body-text' => "Dear {name}\n\nThank you for submitting your proposal {eventtitle}.\n\nOur team will review it and get back to you as soon as possible.\n\nFeel free to contact us with any questions or concerns.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'submitted_proposal_help' } Show Help - = render partial: 'help', locals: { id: 'submitted_proposal_help', show_event_variables: true } + = render partial: 'shared/help', locals: { id: 'submitted_proposal_help', show_event_variables: true, show_ticket_variables: false } .checkbox %label = f.check_box :send_on_accepted, data: { name: 'email_settings_accepted_subject' }, class: 'send_on_radio' @@ -63,7 +63,7 @@ 'data-body-input-id' => 'email_settings_accepted_body', 'data-body-text' => "Dear {name}\n\nWe are very pleased to inform you that your submission {eventtitle} has been accepted for the conference {conference}.\n\nThe public page of your submission can be found at:\n{proposalslink}\nIf you haven´t already registered for {conference}, please do as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help - = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } + = render partial: 'shared/help', locals: { id: 'accepted_help', show_event_variables: true, show_ticket_variables: false } .checkbox %label = f.check_box :send_on_rejected, data: { name: 'email_settings_rejected_subject'}, class: 'send_on_radio' @@ -79,7 +79,7 @@ 'data-body-input-id' => 'email_settings_rejected_body', 'data-body-text' => "Dear {name},\n\nThank you for your submission {eventtitle} for the conference {conference}.\nAfter careful consideration we are sorry to inform you that your submission has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'rejected_help' } Show Help - = render partial: 'help', locals: {id: 'rejected_help', show_event_variables: true} + = render partial: 'shared/help', locals: {id: 'rejected_help', show_event_variables: true, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_confirmed_without_registration, data: {name: 'email_settings_confirmed_without_registration_subject'}, class: 'send_on_radio' @@ -95,7 +95,7 @@ 'data-body-input-id' => 'email_settings_confirmed_without_registration_body', 'data-body-text' => "Dear {name},\n\nThank you for the confirmation of {eventtitle}. Unfortunately you are not registered for the conference {conference}. Please register as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'confirmed_help' } Show Help - = render partial: 'help', locals: {id: 'confirmed_help', show_event_variables: true} + = render partial: 'shared/help', locals: {id: 'confirmed_help', show_event_variables: true, show_ticket_variables: false} #notifications.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -112,7 +112,7 @@ 'data-body-input-id' => 'email_settings_conference_dates_updated_body', 'data-body-text' => "Dear {name},\n\nThe date of {conference} has changed.\n New Dates : {conference_start_date} - {conference_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_dates_help' } Show Help - = render partial: 'help', locals: {id: 'updated_dates_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_dates_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_conference_registration_dates_updated, data: { name: 'email_settings_conference_registration_dates_updated_subject' }, class: 'send_on_radio' @@ -128,7 +128,7 @@ 'data-body-input-id' => 'email_settings_conference_registration_dates_updated_body', 'data-body-text' => "Dear {name},\n\nThe registration date of {conference} has changed.\n New Dates : {registration_start_date} - {registration_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_registrations_dates_help' } Show Help - = render partial: 'help', locals: {id: 'updated_registrations_dates_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_registrations_dates_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_venue_updated, data: { name: 'email_settings_venue_updated_subject'}, class: 'send_on_radio' @@ -144,7 +144,7 @@ 'data-body-input-id' => 'email_settings_venue_updated_body', 'data-body-text' => "Dear {name},\n\nThe Conference venue of {conference} has changed. New location is: {venue}.\n Address: {venue_address}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_venue_help' } Show Help - = render partial: 'help', locals: {id: 'updated_venue_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_venue_help', show_event_variables: false, show_ticket_variables: false} #cfp.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -161,7 +161,7 @@ 'data-body-input-id' => 'email_settings_program_schedule_public_body', 'data-body-text' => "Dear {name},\n\nThe schedule for {conference} has been announced.\nLink to Schedule {schedule_link}\n\nBest wishes\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help - = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_cfp_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_cfp_dates_updated @@ -177,7 +177,7 @@ 'data-body-input-id' => 'email_settings_cfp_dates_updated_body', 'data-body-text' => "Dear {name},\n\nThe Conference Call for Papers Details of {conference} has changed.\nNew Dates : {cfp_start_date} - {cfp_end_date}.\n Link to Schedule {schedule_link} \n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help - = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_cfp_help', show_event_variables: false, show_ticket_variables: false} #booth.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -194,7 +194,7 @@ 'data-body-input-id' => 'email_settings_booths_acceptance_body', 'data-body-text' => "Dear {name},\n\nWe are pleased to inform you that your #{t'booth'} request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe are looking forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_acceptance_help' } Show help - = render partial: 'help', locals: {id: 'booth_acceptance_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'booth_acceptance_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_booths_rejection @@ -210,7 +210,7 @@ 'data-body-input-id' => 'email_settings_booths_rejection_body', 'data-body-text' => "Dear {name},\n\nThank you for your #{t'booth'} request {booth_title} for the conference {conference}.\n\nUnfortunately, we are sorry to inform you that your request has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_rejection_help' } Show help - = render partial: 'help', locals: {id: 'booth_rejection_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'booth_rejection_help', show_event_variables: false, show_ticket_variables: false, show_ticket_variables: false} .row .col-md-12 diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 53d3c4cb0..2198f7b82 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -17,7 +17,7 @@ 'data-body-input-id' => 'ticket_email_body', 'data-body-text' => "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default Email %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help - = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } + = render partial: 'shared/help', locals: { id: 'accepted_help', show_event_variables: true, show_ticket_variables: true} -# template here -# booth not necessary .form-group diff --git a/app/views/admin/tickets/_help.html.haml b/app/views/admin/tickets/_help.html.haml deleted file mode 100644 index dacadf99d..000000000 --- a/app/views/admin/tickets/_help.html.haml +++ /dev/null @@ -1,59 +0,0 @@ -.template-help{ id: id } - Valid attributes: - %table.table - %tr - %td {email} - %td The user's email address - %tr - %td {name} - %td The user's full name - %tr - %td {conference} - %td The full conference title - %tr - %td {conference_start_date} - %td The start date of the conference - %tr - %td {conference_end_date} - %td The end date of the conference - - if @conference.registration_dates_given? - %tr - %td {registration_start_date} - %td The start date of the registration period - %tr - %td {registration_end_date} - %td The end date of the registration period - %tr - %td {registrationlink} - %td A link to the registration page - - if @conference.venue - %tr - %td {venue} - %td The name of the venue - - if @conference.venue - %tr - %td {venue_address} - %td The address of the venue - - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? - %tr - %td {cfp_start_date} - %td The call for papers start date - %tr - %td {cfp_end_date} - %td The call for papers end date - - if @conference.program.schedule_public - %td {schedule_link} - %td The link to complete schedule of the conference - - if @conference.splashpage && @conference.splashpage.public? - %tr - %td {conference_splash_link} - %td The link to conference splash page - %tr - %td {ticket_quantity} - %td The quantity of tickets purchased - %tr - %td {ticket_title} - %td The ticket title - %tr - %td {ticket_purchase_id} - %td The id of the ticket purchase transaction diff --git a/app/views/shared/_help.html.haml b/app/views/shared/_help.html.haml index c8c19b94e..55d8819ab 100644 --- a/app/views/shared/_help.html.haml +++ b/app/views/shared/_help.html.haml @@ -10,7 +10,7 @@ %tr %td {conference} %td The full conference title - - if defined?(show_event_variables) + - if (show_event_variables.present?) %tr %td {proposalslink} %td A link to the user's proposal page @@ -29,7 +29,7 @@ %tr %td {conference_end_date} %td The end date of the conference - - if defined?(conference.registration_dates_given?) + - if @conference.registration_dates_given? %tr %td {registration_start_date} %td The start date of the registration period @@ -39,28 +39,28 @@ %tr %td {registrationlink} %td A link to the registration page - - if defined?(conference.venue) + - if (@conference.venue.present?) %tr %td {venue} %td The name of the venue %tr %td {venue_address} %td The address of the venue - - unless conference.program.cfp.blank? || conference.program.cfp.start_date.blank? || conference.program.cfp.end_date.blank? + - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? %tr %td {cfp_start_date} %td The call for papers start date %tr %td {cfp_end_date} %td The call for papers end date - - if conference.program.schedule_public + - if @conference.program.schedule_public %td {schedule_link} %td The link to complete schedule of the conference - - if defined?(@conference.splashpage) && @conference.splashpage.public? + - if (@conference.splashpage.present?) && @conference.splashpage.public? %tr %td {conference_splash_link} %td The link to conference splash page - - if defined?(ticket_quantity) + - if show_ticket_variables %tr %td {ticket_quantity} %td The quantity of tickets purchased @@ -70,3 +70,7 @@ %tr %td {ticket_purchase_id} %td The id of the ticket purchase transaction + - if @conference.booths + %tr + %td {booth_title} + %td Booth's title From e2a0dffac81a8fca7cd30404cd49c2521a06ef66 Mon Sep 17 00:00:00 2001 From: killamonis Date: Thu, 27 Apr 2023 16:52:24 -0700 Subject: [PATCH 068/100] lint errors should be gone --- app/services/email_template_parser.rb | 2 ++ app/views/admin/tickets/_form.html.haml | 2 -- app/views/shared/_help.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index d6123f5f2..fa2f5390f 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -4,6 +4,7 @@ def initialize(conference, user) @user = user end + # rubocop:disable Metrics/AbcSize, Metrics/ParameterLists def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) h = { 'email' => @user.email, @@ -71,4 +72,5 @@ def self.parse_template(text, values) end text end + # rubocop:enable Metrics/AbcSize, Metrics/ParameterLists end diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 2198f7b82..74cea4f97 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -18,8 +18,6 @@ 'data-body-text' => "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default Email %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help = render partial: 'shared/help', locals: { id: 'accepted_help', show_event_variables: true, show_ticket_variables: true} - -# template here - -# booth not necessary .form-group = f.label :price = f.number_field :price, class: 'form-control' diff --git a/app/views/shared/_help.html.haml b/app/views/shared/_help.html.haml index 55d8819ab..a41276da0 100644 --- a/app/views/shared/_help.html.haml +++ b/app/views/shared/_help.html.haml @@ -63,7 +63,7 @@ - if show_ticket_variables %tr %td {ticket_quantity} - %td The quantity of tickets purchased + %td The quantity of tickets purchased %tr %td {ticket_title} %td The ticket title From 8d091d016f9cacb6f5b20158a24afee2ef0daf47 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 19 Apr 2023 23:39:02 -0700 Subject: [PATCH 069/100] fixed some merge conflicts --- .../admin/currency_conversions_controller.rb | 58 ++++++++++++++++++ app/models/currency_conversion.rb | 14 +++++ .../admin/currency_conversions/edit.html.haml | 8 +++ .../currency_conversions/index.html.haml | 34 +++++++++++ .../admin/currency_conversions/new.html.haml | 8 +++ app/views/admin/tickets/_help.html.haml | 59 +++++++++++++++++++ app/views/layouts/_admin_sidebar.html.haml | 4 ++ ...30418211400_create_currency_conversions.rb | 12 ++++ spec/factories/currency_conversions.rb | 19 ++++++ spec/features/currency_conversions_spec.rb | 54 +++++++++++++++++ 10 files changed, 270 insertions(+) create mode 100644 app/controllers/admin/currency_conversions_controller.rb create mode 100644 app/models/currency_conversion.rb create mode 100644 app/views/admin/currency_conversions/edit.html.haml create mode 100644 app/views/admin/currency_conversions/index.html.haml create mode 100644 app/views/admin/currency_conversions/new.html.haml create mode 100644 app/views/admin/tickets/_help.html.haml create mode 100644 db/migrate/20230418211400_create_currency_conversions.rb create mode 100644 spec/factories/currency_conversions.rb create mode 100644 spec/features/currency_conversions_spec.rb diff --git a/app/controllers/admin/currency_conversions_controller.rb b/app/controllers/admin/currency_conversions_controller.rb new file mode 100644 index 000000000..e74738223 --- /dev/null +++ b/app/controllers/admin/currency_conversions_controller.rb @@ -0,0 +1,58 @@ +module Admin + class CurrencyConversionsController < Admin::BaseController + load_and_authorize_resource :conference, find_by: :short_title + load_and_authorize_resource :currency_conversion, through: :conference + + # GET /currency_conversions + def index; end + + # GET /currency_conversions/1 + def show; end + + # GET /currency_conversions/new + def new + @currency_conversion = @conference.currency_conversions.new(conference_id: @conference.short_title) + end + + # GET /currency_conversions/1/edit + def edit; end + + # POST /currency_conversions + def create + @currency_conversion = @conference.currency_conversions.new(currency_conversion_params) + + if @currency_conversion.save + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' + else + flash.now[:error] = 'Creating currency conversion failed.' + render :new + end + end + + # PATCH/PUT /currency_conversions/1 + def update + if @currency_conversion.update(currency_conversion_params) + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' + else + flash.now[:error] = 'Updating currency conversion failed.' + render :edit + end + end + + # DELETE /currency_conversions/1 + def destroy + if @currency_conversion.destroy + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' + else + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Deleting currency conversion failed.' + end + end + + private + + # Only allow a list of trusted parameters through. + def currency_conversion_params + params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) + end + end +end diff --git a/app/models/currency_conversion.rb b/app/models/currency_conversion.rb new file mode 100644 index 000000000..0ade9fe2e --- /dev/null +++ b/app/models/currency_conversion.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: currency_conversions +# +# rate :decimal +# from_currency :string +# to_currency :string +# conference_id :integer +# +class CurrencyConversion < ApplicationRecord + belongs_to :conference +end diff --git a/app/views/admin/currency_conversions/edit.html.haml b/app/views/admin/currency_conversions/edit.html.haml new file mode 100644 index 000000000..69b77e91b --- /dev/null +++ b/app/views/admin/currency_conversions/edit.html.haml @@ -0,0 +1,8 @@ +.row + .col-md-12 + .page-header + %h1 + Edit Currency Conversion +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/currency_conversions/index.html.haml b/app/views/admin/currency_conversions/index.html.haml new file mode 100644 index 000000000..d85152938 --- /dev/null +++ b/app/views/admin/currency_conversions/index.html.haml @@ -0,0 +1,34 @@ +.row + .col-md-12 + .page-header + %h1 Currency Conversions + %p.text-muted + Enter the currency conversions for this conference +.row + .col-md-12 + %table.table.table-hover#currency_conversions + %thead + %th From Curr + %th To Curr + %th Rate + %th Actions + %tbody + - @conference.currency_conversions.each do |currency_conversion| + %tr + %td + = currency_conversion.from_currency + %td + = currency_conversion.to_currency + %td + = currency_conversion.rate + %td + .btn-group{ role: 'group' } + = link_to 'Edit', edit_admin_conference_currency_conversion_path(@conference.short_title, currency_conversion.id), + method: :get, class: 'btn btn-primary' + = link_to 'Delete', admin_conference_currency_conversion_path(@conference.short_title, currency_conversion.id), + method: :delete, class: 'btn btn-danger', + data: { confirm: "Do you really want to delete this currency conversion?" } + +.row + .col-md-12.text-right + = link_to 'Add Currency Conversion', new_admin_conference_currency_conversion_path(@conference.short_title), class: 'btn btn-primary' diff --git a/app/views/admin/currency_conversions/new.html.haml b/app/views/admin/currency_conversions/new.html.haml new file mode 100644 index 000000000..bcc3543f2 --- /dev/null +++ b/app/views/admin/currency_conversions/new.html.haml @@ -0,0 +1,8 @@ +.row + .col-md-12 + .page-header + %h1 + Create Currency Conversion +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/tickets/_help.html.haml b/app/views/admin/tickets/_help.html.haml new file mode 100644 index 000000000..0ad816dd9 --- /dev/null +++ b/app/views/admin/tickets/_help.html.haml @@ -0,0 +1,59 @@ +.template-help{ id: id } + Valid attributes: + %table.table + %tr + %td {email} + %td The user's email address + %tr + %td {name} + %td The user's full name + %tr + %td {conference} + %td The full conference title + %tr + %td {conference_start_date} + %td The start date of the conference + %tr + %td {conference_end_date} + %td The end date of the conference + - if @conference.registration_dates_given? + %tr + %td {registration_start_date} + %td The start date of the registration period + %tr + %td {registration_end_date} + %td The end date of the registration period + %tr + %td {registrationlink} + %td A link to the registration page + - if @conference.venue + %tr + %td {venue} + %td The name of the venue + - if @conference.venue + %tr + %td {venue_address} + %td The address of the venue + - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? + %tr + %td {cfp_start_date} + %td The call for papers start date + %tr + %td {cfp_end_date} + %td The call for papers end date + - if @conference.program.schedule_public + %td {schedule_link} + %td The link to complete schedule of the conference + - if @conference.splashpage && @conference.splashpage.public? + %tr + %td {conference_splash_link} + %td The link to conference splash page + %tr + %td {ticket_quantity} + %td The quantity of tickets purchased + %tr + %td {ticket_title} + %td The ticket title + %tr + %td {ticket_purchase_id} + %td The id of the ticket purchase transaction diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 395b48f96..202482e34 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -116,6 +116,10 @@ - if can? :update, @conference.tickets.build %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) + + %li + = link_to 'Currency', admin_currency_conversions_path + - if can? :manage, @conference.booths.build %li = link_to admin_conference_booths_path(@conference.short_title) do diff --git a/db/migrate/20230418211400_create_currency_conversions.rb b/db/migrate/20230418211400_create_currency_conversions.rb new file mode 100644 index 000000000..9c5100085 --- /dev/null +++ b/db/migrate/20230418211400_create_currency_conversions.rb @@ -0,0 +1,12 @@ +class CreateCurrencyConversions < ActiveRecord::Migration[7.0] + def change + create_table :currency_conversions do |t| + t.decimal :rate + t.string :from_currency + t.string :to_currency + t.references :conference, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/spec/factories/currency_conversions.rb b/spec/factories/currency_conversions.rb new file mode 100644 index 000000000..a853050ab --- /dev/null +++ b/spec/factories/currency_conversions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: currency_conversions +# +# rate :decimal +# from_currency :string +# to_currency :string +# conference_id :integer +# +FactoryBot.define do + factory :currency_conversion do + from_currency { 'USD' } + to_currency { 'EUR' } + rate { 0.89 } + conference + end +end diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb new file mode 100644 index 000000000..eb53c7804 --- /dev/null +++ b/spec/features/currency_conversions_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe CurrencyConversion do + let!(:conference) { create(:conference, title: 'ExampleCon') } + let!(:admin) { create(:admin) } + + context 'as an admin' do + before do + sign_in admin + end + + after do + sign_out + end + + it 'add a currency conversion', feature: true do + visit admin_conference_currency_conversions_path(conference.short_title) + click_link 'Add Currency Conversion' + + fill_in 'currency_conversion_from_currency', with: 'USD' + fill_in 'currency_conversion_to_currency', with: 'EUR' + fill_in 'currency_conversion_rate', with: '0.89' + + click_button 'Create Currency conversion' + page.find('#flash') + expect(flash).to eq('Currency conversion was successfully created.') + within('table#currency_conversions') do + expect(page.has_content?('USD')).to be true + expect(page.has_content?('EUR')).to be true + expect(page.assert_selector('tbody tr', count: 1)).to be true + end + end + + it 'Deletes Currency Conversion', feature: true, js: true do + conference.currency_conversions << create(:currency_conversion) + visit admin_conference_currency_conversions_path(conference.short_title) + # Remove currency conversion + page.accept_alert do + within('table tbody tr:nth-of-type(1) td:nth-of-type(4)') do + click_link 'Delete' + end + end + page.find('#flash') + + # Validations + expect(flash).to eq('Currency conversion was successfully deleted.') + within('table#currency_conversions') do + expect(page.assert_selector('tbody tr', count: 0)).to be true + end + end + end +end From 85ac5431975450afa9bac5f9ec8ccc06e932691c Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 18 Apr 2023 14:51:32 -0700 Subject: [PATCH 070/100] create migration updated model relationship --- app/models/conference.rb | 1 + app/models/ticket.rb | 1 + db/schema.rb | 13 ++++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/conference.rb b/app/models/conference.rb index 7dff0f5ee..4abb207a9 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -93,6 +93,7 @@ class Conference < ApplicationRecord through: :program, source: :events has_many :event_types, through: :program + has_many :currency_conversions has_many :surveys, as: :surveyable, dependent: :destroy do def for_registration diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 8fd167a38..1a03bb8b4 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -21,6 +21,7 @@ class Ticket < ApplicationRecord belongs_to :conference has_many :ticket_purchases, dependent: :destroy has_many :buyers, -> { distinct }, through: :ticket_purchases, source: :user + has_many :currency_conversions, through: :conference has_paper_trail meta: { conference_id: :conference_id }, ignore: %i[updated_at] diff --git a/db/schema.rb b/db/schema.rb index 12c951f60..b80fedefa 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: 2023_03_23_200709) do +ActiveRecord::Schema[7.0].define(version: 2023_04_18_211400) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -133,6 +133,16 @@ t.string "blog" end + create_table "currency_conversions", force: :cascade do |t| + t.decimal "rate" + t.string "from_currency" + t.string "to_currency" + t.bigint "conference_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["conference_id"], name: "index_currency_conversions_on_conference_id" + end + create_table "delayed_jobs", force: :cascade do |t| t.integer "priority", default: 0, null: false t.integer "attempts", default: 0, null: false @@ -684,5 +694,6 @@ t.datetime "updated_at" end + add_foreign_key "currency_conversions", "conferences" add_foreign_key "events", "events", column: "parent_id" end From 318b1bb280a9fbdcbcb1b22d367b6888a037a46d Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Tue, 18 Apr 2023 15:21:28 -0700 Subject: [PATCH 071/100] conferences --- app/models/ticket.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 1a03bb8b4..fd6efeace 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -21,7 +21,7 @@ class Ticket < ApplicationRecord belongs_to :conference has_many :ticket_purchases, dependent: :destroy has_many :buyers, -> { distinct }, through: :ticket_purchases, source: :user - has_many :currency_conversions, through: :conference + has_many :currency_conversions, through: :conferences has_paper_trail meta: { conference_id: :conference_id }, ignore: %i[updated_at] From 858e9aff8770c042ad39645039468344ee8ded8d Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 19 Apr 2023 23:30:40 -0700 Subject: [PATCH 072/100] new curr conv model --- app/views/layouts/_admin_sidebar.html.haml | 4 ++++ config/routes.rb | 1 + 2 files changed, 5 insertions(+) diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 202482e34..81bb8e9ac 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -117,8 +117,12 @@ %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) +<<<<<<< HEAD %li = link_to 'Currency', admin_currency_conversions_path +======= + +>>>>>>> d21715a84 (new curr conv model) - if can? :manage, @conference.booths.build %li diff --git a/config/routes.rb b/config/routes.rb index d32cdf5a6..9ed30e8a9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -126,6 +126,7 @@ end resources :sponsors, except: [:show] resources :lodgings, except: [:show] + resources :currency_conversions, except: [:show] resources :emails, only: %i[show update index] resources :physical_tickets, only: [:index] resources :roles, except: %i[new create] do From 11c9d7335e2229fe95ca7cb32d2b11b84f2a886f Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 20 Apr 2023 14:01:11 -0700 Subject: [PATCH 073/100] MVC for currency_conversions --- .../currency_conversions_controller.rb | 58 +++++++++++++++++++ .../currency_conversions/_form.html.haml | 10 ++++ app/views/currency_conversions/edit.html.haml | 7 +++ .../currency_conversions/index.html.haml | 19 ++++++ app/views/currency_conversions/new.html.haml | 5 ++ app/views/currency_conversions/show.html.haml | 6 ++ app/views/layouts/_admin_sidebar.html.haml | 5 -- config/routes.rb | 2 +- .../currency_conversions_controller_test.rb | 48 +++++++++++++++ 9 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 app/controllers/currency_conversions_controller.rb create mode 100644 app/views/currency_conversions/_form.html.haml create mode 100644 app/views/currency_conversions/edit.html.haml create mode 100644 app/views/currency_conversions/index.html.haml create mode 100644 app/views/currency_conversions/new.html.haml create mode 100644 app/views/currency_conversions/show.html.haml create mode 100644 test/controllers/currency_conversions_controller_test.rb diff --git a/app/controllers/currency_conversions_controller.rb b/app/controllers/currency_conversions_controller.rb new file mode 100644 index 000000000..83a17b156 --- /dev/null +++ b/app/controllers/currency_conversions_controller.rb @@ -0,0 +1,58 @@ +class CurrencyConversionsController < ApplicationController + before_action :set_currency_conversion, only: %i[ show edit update destroy ] + + # GET /currency_conversions + def index + @currency_conversions = CurrencyConversion.all + end + + # GET /currency_conversions/1 + def show + end + + # GET /currency_conversions/new + def new + @currency_conversion = CurrencyConversion.new + end + + # GET /currency_conversions/1/edit + def edit + end + + # POST /currency_conversions + def create + @currency_conversion = CurrencyConversion.new(currency_conversion_params) + + if @currency_conversion.save + redirect_to @currency_conversion, notice: "Currency conversion was successfully created." + else + render :new, status: :unprocessable_entity + end + end + + # PATCH/PUT /currency_conversions/1 + def update + if @currency_conversion.update(currency_conversion_params) + redirect_to @currency_conversion, notice: "Currency conversion was successfully updated." + else + render :edit, status: :unprocessable_entity + end + end + + # DELETE /currency_conversions/1 + def destroy + @currency_conversion.destroy + redirect_to currency_conversions_url, notice: "Currency conversion was successfully destroyed." + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_currency_conversion + @currency_conversion = CurrencyConversion.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def currency_conversion_params + params.fetch(:currency_conversion, {}) + end +end diff --git a/app/views/currency_conversions/_form.html.haml b/app/views/currency_conversions/_form.html.haml new file mode 100644 index 000000000..550a95599 --- /dev/null +++ b/app/views/currency_conversions/_form.html.haml @@ -0,0 +1,10 @@ += form_for @currency_conversion do |f| + - if @currency_conversion.errors.any? + #error_explanation + %h2= "#{pluralize(@currency_conversion.errors.count, "error")} prohibited this currency_conversion from being saved:" + %ul + - @currency_conversion.errors.full_messages.each do |message| + %li= message + + .actions + = f.submit 'Save' diff --git a/app/views/currency_conversions/edit.html.haml b/app/views/currency_conversions/edit.html.haml new file mode 100644 index 000000000..6977e203f --- /dev/null +++ b/app/views/currency_conversions/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing currency_conversion + += render 'form' + += link_to 'Show', @currency_conversion +\| += link_to 'Back', currency_conversions_path diff --git a/app/views/currency_conversions/index.html.haml b/app/views/currency_conversions/index.html.haml new file mode 100644 index 000000000..5a62970f6 --- /dev/null +++ b/app/views/currency_conversions/index.html.haml @@ -0,0 +1,19 @@ +%h1 Listing currency_conversions + +%table + %thead + %tr + %th + %th + %th + + %tbody + - @currency_conversions.each do |currency_conversion| + %tr + %td= link_to 'Show', currency_conversion + %td= link_to 'Edit', edit_currency_conversion_path(currency_conversion) + %td= link_to 'Destroy', currency_conversion, method: :delete, data: { confirm: 'Are you sure?' } + +%br + += link_to 'New Currency conversion', new_currency_conversion_path diff --git a/app/views/currency_conversions/new.html.haml b/app/views/currency_conversions/new.html.haml new file mode 100644 index 000000000..fdd6b3273 --- /dev/null +++ b/app/views/currency_conversions/new.html.haml @@ -0,0 +1,5 @@ +%h1 New currency_conversion + += render 'form' + += link_to 'Back', currency_conversions_path diff --git a/app/views/currency_conversions/show.html.haml b/app/views/currency_conversions/show.html.haml new file mode 100644 index 000000000..ebbcbd44c --- /dev/null +++ b/app/views/currency_conversions/show.html.haml @@ -0,0 +1,6 @@ +%p#notice= notice + + += link_to 'Edit', edit_currency_conversion_path(@currency_conversion) +\| += link_to 'Back', currency_conversions_path diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 81bb8e9ac..6306573b8 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -117,12 +117,7 @@ %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) -<<<<<<< HEAD - %li - = link_to 'Currency', admin_currency_conversions_path -======= ->>>>>>> d21715a84 (new curr conv model) - if can? :manage, @conference.booths.build %li diff --git a/config/routes.rb b/config/routes.rb index 9ed30e8a9..93df91a7a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Osem::Application.routes.draw do + resources :currency_conversions mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development? constraints DomainConstraint do @@ -126,7 +127,6 @@ end resources :sponsors, except: [:show] resources :lodgings, except: [:show] - resources :currency_conversions, except: [:show] resources :emails, only: %i[show update index] resources :physical_tickets, only: [:index] resources :roles, except: %i[new create] do diff --git a/test/controllers/currency_conversions_controller_test.rb b/test/controllers/currency_conversions_controller_test.rb new file mode 100644 index 000000000..d952bfe19 --- /dev/null +++ b/test/controllers/currency_conversions_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class CurrencyConversionsControllerTest < ActionDispatch::IntegrationTest + setup do + @currency_conversion = currency_conversions(:one) + end + + test "should get index" do + get currency_conversions_url + assert_response :success + end + + test "should get new" do + get new_currency_conversion_url + assert_response :success + end + + test "should create currency_conversion" do + assert_difference("CurrencyConversion.count") do + post currency_conversions_url, params: { currency_conversion: { } } + end + + assert_redirected_to currency_conversion_url(CurrencyConversion.last) + end + + test "should show currency_conversion" do + get currency_conversion_url(@currency_conversion) + assert_response :success + end + + test "should get edit" do + get edit_currency_conversion_url(@currency_conversion) + assert_response :success + end + + test "should update currency_conversion" do + patch currency_conversion_url(@currency_conversion), params: { currency_conversion: { } } + assert_redirected_to currency_conversion_url(@currency_conversion) + end + + test "should destroy currency_conversion" do + assert_difference("CurrencyConversion.count", -1) do + delete currency_conversion_url(@currency_conversion) + end + + assert_redirected_to currency_conversions_url + end +end From 5fe819f135d421531e6e0d9ff88d2860c5859911 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Fri, 21 Apr 2023 21:10:24 -0700 Subject: [PATCH 074/100] routes are working except update --- .../admin/currency_conversions_controller.rb | 35 +++++++++++ .../currency_conversions_controller.rb | 58 ------------------- app/models/currency_conversion.rb | 1 + .../currency_conversions/_form.html.haml | 12 ++++ .../currency_conversions/_form.html.haml | 10 ---- app/views/currency_conversions/edit.html.haml | 7 --- .../currency_conversions/index.html.haml | 19 ------ app/views/currency_conversions/new.html.haml | 5 -- app/views/currency_conversions/show.html.haml | 6 -- app/views/layouts/_admin_sidebar.html.haml | 3 +- config/routes.rb | 3 +- 11 files changed, 52 insertions(+), 107 deletions(-) delete mode 100644 app/controllers/currency_conversions_controller.rb create mode 100644 app/views/admin/currency_conversions/_form.html.haml delete mode 100644 app/views/currency_conversions/_form.html.haml delete mode 100644 app/views/currency_conversions/edit.html.haml delete mode 100644 app/views/currency_conversions/index.html.haml delete mode 100644 app/views/currency_conversions/new.html.haml delete mode 100644 app/views/currency_conversions/show.html.haml diff --git a/app/controllers/admin/currency_conversions_controller.rb b/app/controllers/admin/currency_conversions_controller.rb index e74738223..8ccf2caf2 100644 --- a/app/controllers/admin/currency_conversions_controller.rb +++ b/app/controllers/admin/currency_conversions_controller.rb @@ -7,7 +7,12 @@ class CurrencyConversionsController < Admin::BaseController def index; end # GET /currency_conversions/1 +<<<<<<< HEAD def show; end +======= + def show + end +>>>>>>> 34525cbc4 (routes are working except update) # GET /currency_conversions/new def new @@ -15,16 +20,27 @@ def new end # GET /currency_conversions/1/edit +<<<<<<< HEAD def edit; end +======= + def edit + end +>>>>>>> 34525cbc4 (routes are working except update) # POST /currency_conversions def create @currency_conversion = @conference.currency_conversions.new(currency_conversion_params) if @currency_conversion.save +<<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' else flash.now[:error] = 'Creating currency conversion failed.' +======= + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Currency conversion was successfully created." + else + flash.now[:error] = "Creating currency conversion failed." +>>>>>>> 34525cbc4 (routes are working except update) render :new end end @@ -32,9 +48,15 @@ def create # PATCH/PUT /currency_conversions/1 def update if @currency_conversion.update(currency_conversion_params) +<<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' else flash.now[:error] = 'Updating currency conversion failed.' +======= + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Currency conversion was successfully updated." + else + flash.now[:error] = "Updating currency conversion failed." +>>>>>>> 34525cbc4 (routes are working except update) render :edit end end @@ -42,17 +64,30 @@ def update # DELETE /currency_conversions/1 def destroy if @currency_conversion.destroy +<<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' else redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Deleting currency conversion failed.' +======= + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Currency conversion was successfully deleted." + else + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Deleting currency conversion failed." +>>>>>>> 34525cbc4 (routes are working except update) end end private +<<<<<<< HEAD # Only allow a list of trusted parameters through. def currency_conversion_params params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) end +======= + # Only allow a list of trusted parameters through. + def currency_conversion_params + params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) + end +>>>>>>> 34525cbc4 (routes are working except update) end end diff --git a/app/controllers/currency_conversions_controller.rb b/app/controllers/currency_conversions_controller.rb deleted file mode 100644 index 83a17b156..000000000 --- a/app/controllers/currency_conversions_controller.rb +++ /dev/null @@ -1,58 +0,0 @@ -class CurrencyConversionsController < ApplicationController - before_action :set_currency_conversion, only: %i[ show edit update destroy ] - - # GET /currency_conversions - def index - @currency_conversions = CurrencyConversion.all - end - - # GET /currency_conversions/1 - def show - end - - # GET /currency_conversions/new - def new - @currency_conversion = CurrencyConversion.new - end - - # GET /currency_conversions/1/edit - def edit - end - - # POST /currency_conversions - def create - @currency_conversion = CurrencyConversion.new(currency_conversion_params) - - if @currency_conversion.save - redirect_to @currency_conversion, notice: "Currency conversion was successfully created." - else - render :new, status: :unprocessable_entity - end - end - - # PATCH/PUT /currency_conversions/1 - def update - if @currency_conversion.update(currency_conversion_params) - redirect_to @currency_conversion, notice: "Currency conversion was successfully updated." - else - render :edit, status: :unprocessable_entity - end - end - - # DELETE /currency_conversions/1 - def destroy - @currency_conversion.destroy - redirect_to currency_conversions_url, notice: "Currency conversion was successfully destroyed." - end - - private - # Use callbacks to share common setup or constraints between actions. - def set_currency_conversion - @currency_conversion = CurrencyConversion.find(params[:id]) - end - - # Only allow a list of trusted parameters through. - def currency_conversion_params - params.fetch(:currency_conversion, {}) - end -end diff --git a/app/models/currency_conversion.rb b/app/models/currency_conversion.rb index 0ade9fe2e..d4ac6eba3 100644 --- a/app/models/currency_conversion.rb +++ b/app/models/currency_conversion.rb @@ -11,4 +11,5 @@ # class CurrencyConversion < ApplicationRecord belongs_to :conference + validates :rate, numericality: { greater_than: 0 } end diff --git a/app/views/admin/currency_conversions/_form.html.haml b/app/views/admin/currency_conversions/_form.html.haml new file mode 100644 index 000000000..6f052dcd2 --- /dev/null +++ b/app/views/admin/currency_conversions/_form.html.haml @@ -0,0 +1,12 @@ += form_for(@currency_conversion, url: (@currency_conversion.new_record? ? admin_conference_currency_conversions_path : admin_conference_currency_conversion_path(@conference.short_title, @currency_conversion))) do |f| + .form-group + = f.label :from_currency + = f.text_field :from_currency, autofocus: true , required: true, class: 'form-control' + .form-group + = f.label :to_currency + = f.text_field :to_currency, autofocus: true , required: true, class: 'form-control' + .form-group + = f.label :rate + = f.text_field :rate, autofocus: true, required: true, class: 'form-control', type: 'number', step: '0.01' + %p.text-right + = f.submit nil, class: 'btn btn-primary' diff --git a/app/views/currency_conversions/_form.html.haml b/app/views/currency_conversions/_form.html.haml deleted file mode 100644 index 550a95599..000000000 --- a/app/views/currency_conversions/_form.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -= form_for @currency_conversion do |f| - - if @currency_conversion.errors.any? - #error_explanation - %h2= "#{pluralize(@currency_conversion.errors.count, "error")} prohibited this currency_conversion from being saved:" - %ul - - @currency_conversion.errors.full_messages.each do |message| - %li= message - - .actions - = f.submit 'Save' diff --git a/app/views/currency_conversions/edit.html.haml b/app/views/currency_conversions/edit.html.haml deleted file mode 100644 index 6977e203f..000000000 --- a/app/views/currency_conversions/edit.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%h1 Editing currency_conversion - -= render 'form' - -= link_to 'Show', @currency_conversion -\| -= link_to 'Back', currency_conversions_path diff --git a/app/views/currency_conversions/index.html.haml b/app/views/currency_conversions/index.html.haml deleted file mode 100644 index 5a62970f6..000000000 --- a/app/views/currency_conversions/index.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%h1 Listing currency_conversions - -%table - %thead - %tr - %th - %th - %th - - %tbody - - @currency_conversions.each do |currency_conversion| - %tr - %td= link_to 'Show', currency_conversion - %td= link_to 'Edit', edit_currency_conversion_path(currency_conversion) - %td= link_to 'Destroy', currency_conversion, method: :delete, data: { confirm: 'Are you sure?' } - -%br - -= link_to 'New Currency conversion', new_currency_conversion_path diff --git a/app/views/currency_conversions/new.html.haml b/app/views/currency_conversions/new.html.haml deleted file mode 100644 index fdd6b3273..000000000 --- a/app/views/currency_conversions/new.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%h1 New currency_conversion - -= render 'form' - -= link_to 'Back', currency_conversions_path diff --git a/app/views/currency_conversions/show.html.haml b/app/views/currency_conversions/show.html.haml deleted file mode 100644 index ebbcbd44c..000000000 --- a/app/views/currency_conversions/show.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -%p#notice= notice - - -= link_to 'Edit', edit_currency_conversion_path(@currency_conversion) -\| -= link_to 'Back', currency_conversions_path diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index 6306573b8..cf6a0ac17 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -117,7 +117,8 @@ %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) - + %li + = link_to 'Currency', admin_conference_currency_conversions_path(@conference.short_title) - if can? :manage, @conference.booths.build %li diff --git a/config/routes.rb b/config/routes.rb index 93df91a7a..e59ed77f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,5 @@ Osem::Application.routes.draw do - resources :currency_conversions + mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development? constraints DomainConstraint do @@ -127,6 +127,7 @@ end resources :sponsors, except: [:show] resources :lodgings, except: [:show] + resources :currency_conversions, except: [:show] resources :emails, only: %i[show update index] resources :physical_tickets, only: [:index] resources :roles, except: %i[new create] do From 98f93b09a7bb586a1d9c8aedaba1ea4d1fc74bc3 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Fri, 21 Apr 2023 21:15:29 -0700 Subject: [PATCH 075/100] moved currency to be under donations --- app/views/layouts/_admin_sidebar.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index cf6a0ac17..eb88c3df0 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -116,9 +116,10 @@ - if can? :update, @conference.tickets.build %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) + %li + = link_to 'Currency', admin_conference_currency_conversions_path(@conference.short_title) + - %li - = link_to 'Currency', admin_conference_currency_conversions_path(@conference.short_title) - if can? :manage, @conference.booths.build %li From 3308a529e646983fd322fd8535aab523055bdf84 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 26 Apr 2023 20:22:32 -0700 Subject: [PATCH 076/100] rspec tests for currency conversions --- .../admin/currency_conversions_controller.rb | 32 ++++++++++++ config/routes.rb | 1 - spec/features/currency_spec.rb | 52 +++++++++++++++++++ .../currency_conversions_controller_test.rb | 48 ----------------- 4 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 spec/features/currency_spec.rb delete mode 100644 test/controllers/currency_conversions_controller_test.rb diff --git a/app/controllers/admin/currency_conversions_controller.rb b/app/controllers/admin/currency_conversions_controller.rb index 8ccf2caf2..cfabe11c7 100644 --- a/app/controllers/admin/currency_conversions_controller.rb +++ b/app/controllers/admin/currency_conversions_controller.rb @@ -7,12 +7,16 @@ class CurrencyConversionsController < Admin::BaseController def index; end # GET /currency_conversions/1 +<<<<<<< HEAD <<<<<<< HEAD def show; end ======= def show end >>>>>>> 34525cbc4 (routes are working except update) +======= + def show; end +>>>>>>> ac12fba08 (rspec tests for currency conversions) # GET /currency_conversions/new def new @@ -20,18 +24,23 @@ def new end # GET /currency_conversions/1/edit +<<<<<<< HEAD <<<<<<< HEAD def edit; end ======= def edit end >>>>>>> 34525cbc4 (routes are working except update) +======= + def edit; end +>>>>>>> ac12fba08 (rspec tests for currency conversions) # POST /currency_conversions def create @currency_conversion = @conference.currency_conversions.new(currency_conversion_params) if @currency_conversion.save +<<<<<<< HEAD <<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' else @@ -41,6 +50,11 @@ def create else flash.now[:error] = "Creating currency conversion failed." >>>>>>> 34525cbc4 (routes are working except update) +======= + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' + else + flash.now[:error] = 'Creating currency conversion failed.' +>>>>>>> ac12fba08 (rspec tests for currency conversions) render :new end end @@ -48,6 +62,7 @@ def create # PATCH/PUT /currency_conversions/1 def update if @currency_conversion.update(currency_conversion_params) +<<<<<<< HEAD <<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' else @@ -57,6 +72,11 @@ def update else flash.now[:error] = "Updating currency conversion failed." >>>>>>> 34525cbc4 (routes are working except update) +======= + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' + else + flash.now[:error] = 'Updating currency conversion failed.' +>>>>>>> ac12fba08 (rspec tests for currency conversions) render :edit end end @@ -64,6 +84,7 @@ def update # DELETE /currency_conversions/1 def destroy if @currency_conversion.destroy +<<<<<<< HEAD <<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' else @@ -73,21 +94,32 @@ def destroy else redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Deleting currency conversion failed." >>>>>>> 34525cbc4 (routes are working except update) +======= + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' + else + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Deleting currency conversion failed.' +>>>>>>> ac12fba08 (rspec tests for currency conversions) end end private <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> ac12fba08 (rspec tests for currency conversions) # Only allow a list of trusted parameters through. def currency_conversion_params params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) end +<<<<<<< HEAD ======= # Only allow a list of trusted parameters through. def currency_conversion_params params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) end >>>>>>> 34525cbc4 (routes are working except update) +======= +>>>>>>> ac12fba08 (rspec tests for currency conversions) end end diff --git a/config/routes.rb b/config/routes.rb index e59ed77f9..9ed30e8a9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,4 @@ Osem::Application.routes.draw do - mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development? constraints DomainConstraint do diff --git a/spec/features/currency_spec.rb b/spec/features/currency_spec.rb new file mode 100644 index 000000000..9e0ea1ded --- /dev/null +++ b/spec/features/currency_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe CurrencyConversion do + let!(:conference) { create(:conference, title: 'ExampleCon') } + let!(:organizer) { create(:organizer, resource: conference) } + + context 'as a organizer' do + before do + sign_in organizer + end + + after do + sign_out + end + + it 'add a currency conversion', feature: true do + visit admin_conference_currency_conversions_path(conference.short_title) + click_link 'Add Currency Conversion' + + fill_in 'from_currency', with: 'USD' + fill_in 'to_currency', with: 'EUR' + fill_in 'rate', with: '0.89' + + click_button 'Create Currency Conversion' + page.find('#flash') + expect(flash).to eq('Currency conversion was successfully created.') + within('table#currency_conversions') do + expect(page.has_content?('USD')).to be true + expect(page.has_content?('EUR')).to be true + expect(page.assert_selector('tr', count: 1)).to be true + end + end + + it 'Deletes Currency Conversion', feature: true, js: true do + visit admin_conference_currency_conversions_path(conference.short_title) + + # Remove currency conversion + within('table tr:first') do + click_link 'Delete' + end + page.accept_alert + page.find('#flash') + + # Validations + expect(flash).to eq('Difficulty level successfully deleted.') + expect(page.assert_selector('tr', count: 0)).to be true + end + + end +end diff --git a/test/controllers/currency_conversions_controller_test.rb b/test/controllers/currency_conversions_controller_test.rb deleted file mode 100644 index d952bfe19..000000000 --- a/test/controllers/currency_conversions_controller_test.rb +++ /dev/null @@ -1,48 +0,0 @@ -require "test_helper" - -class CurrencyConversionsControllerTest < ActionDispatch::IntegrationTest - setup do - @currency_conversion = currency_conversions(:one) - end - - test "should get index" do - get currency_conversions_url - assert_response :success - end - - test "should get new" do - get new_currency_conversion_url - assert_response :success - end - - test "should create currency_conversion" do - assert_difference("CurrencyConversion.count") do - post currency_conversions_url, params: { currency_conversion: { } } - end - - assert_redirected_to currency_conversion_url(CurrencyConversion.last) - end - - test "should show currency_conversion" do - get currency_conversion_url(@currency_conversion) - assert_response :success - end - - test "should get edit" do - get edit_currency_conversion_url(@currency_conversion) - assert_response :success - end - - test "should update currency_conversion" do - patch currency_conversion_url(@currency_conversion), params: { currency_conversion: { } } - assert_redirected_to currency_conversion_url(@currency_conversion) - end - - test "should destroy currency_conversion" do - assert_difference("CurrencyConversion.count", -1) do - delete currency_conversion_url(@currency_conversion) - end - - assert_redirected_to currency_conversions_url - end -end From 36b8123d7b06ab6a36c2f8697e4ce175821a117b Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 26 Apr 2023 20:50:21 -0700 Subject: [PATCH 077/100] row one for rspec --- .../admin/currency_conversions_controller.rb | 67 ------------------- spec/features/currency_conversions_spec.rb | 35 ++++++++++ spec/features/currency_spec.rb | 52 -------------- 3 files changed, 35 insertions(+), 119 deletions(-) delete mode 100644 spec/features/currency_spec.rb diff --git a/app/controllers/admin/currency_conversions_controller.rb b/app/controllers/admin/currency_conversions_controller.rb index cfabe11c7..e74738223 100644 --- a/app/controllers/admin/currency_conversions_controller.rb +++ b/app/controllers/admin/currency_conversions_controller.rb @@ -7,16 +7,7 @@ class CurrencyConversionsController < Admin::BaseController def index; end # GET /currency_conversions/1 -<<<<<<< HEAD -<<<<<<< HEAD def show; end -======= - def show - end ->>>>>>> 34525cbc4 (routes are working except update) -======= - def show; end ->>>>>>> ac12fba08 (rspec tests for currency conversions) # GET /currency_conversions/new def new @@ -24,37 +15,16 @@ def new end # GET /currency_conversions/1/edit -<<<<<<< HEAD -<<<<<<< HEAD def edit; end -======= - def edit - end ->>>>>>> 34525cbc4 (routes are working except update) -======= - def edit; end ->>>>>>> ac12fba08 (rspec tests for currency conversions) # POST /currency_conversions def create @currency_conversion = @conference.currency_conversions.new(currency_conversion_params) if @currency_conversion.save -<<<<<<< HEAD -<<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' else flash.now[:error] = 'Creating currency conversion failed.' -======= - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Currency conversion was successfully created." - else - flash.now[:error] = "Creating currency conversion failed." ->>>>>>> 34525cbc4 (routes are working except update) -======= - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' - else - flash.now[:error] = 'Creating currency conversion failed.' ->>>>>>> ac12fba08 (rspec tests for currency conversions) render :new end end @@ -62,21 +32,9 @@ def create # PATCH/PUT /currency_conversions/1 def update if @currency_conversion.update(currency_conversion_params) -<<<<<<< HEAD -<<<<<<< HEAD redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' else flash.now[:error] = 'Updating currency conversion failed.' -======= - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Currency conversion was successfully updated." - else - flash.now[:error] = "Updating currency conversion failed." ->>>>>>> 34525cbc4 (routes are working except update) -======= - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' - else - flash.now[:error] = 'Updating currency conversion failed.' ->>>>>>> ac12fba08 (rspec tests for currency conversions) render :edit end end @@ -84,42 +42,17 @@ def update # DELETE /currency_conversions/1 def destroy if @currency_conversion.destroy -<<<<<<< HEAD -<<<<<<< HEAD - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' - else - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Deleting currency conversion failed.' -======= - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Currency conversion was successfully deleted." - else - redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: "Deleting currency conversion failed." ->>>>>>> 34525cbc4 (routes are working except update) -======= redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' else redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Deleting currency conversion failed.' ->>>>>>> ac12fba08 (rspec tests for currency conversions) end end private -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> ac12fba08 (rspec tests for currency conversions) # Only allow a list of trusted parameters through. def currency_conversion_params params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) end -<<<<<<< HEAD -======= - # Only allow a list of trusted parameters through. - def currency_conversion_params - params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) - end ->>>>>>> 34525cbc4 (routes are working except update) -======= ->>>>>>> ac12fba08 (rspec tests for currency conversions) end end diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index eb53c7804..828aadca6 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -4,11 +4,19 @@ describe CurrencyConversion do let!(:conference) { create(:conference, title: 'ExampleCon') } +<<<<<<< HEAD let!(:admin) { create(:admin) } context 'as an admin' do before do sign_in admin +======= + let!(:organizer) { create(:organizer, resource: conference) } + + context 'as a organizer' do + before do + sign_in organizer +>>>>>>> 306aded2d (row one for rspec) end after do @@ -19,21 +27,34 @@ visit admin_conference_currency_conversions_path(conference.short_title) click_link 'Add Currency Conversion' +<<<<<<< HEAD fill_in 'currency_conversion_from_currency', with: 'USD' fill_in 'currency_conversion_to_currency', with: 'EUR' fill_in 'currency_conversion_rate', with: '0.89' click_button 'Create Currency conversion' +======= + fill_in 'from_currency', with: 'USD' + fill_in 'to_currency', with: 'EUR' + fill_in 'rate', with: '0.89' + + click_button 'Create Currency Conversion' +>>>>>>> 306aded2d (row one for rspec) page.find('#flash') expect(flash).to eq('Currency conversion was successfully created.') within('table#currency_conversions') do expect(page.has_content?('USD')).to be true expect(page.has_content?('EUR')).to be true +<<<<<<< HEAD expect(page.assert_selector('tbody tr', count: 1)).to be true +======= + expect(page.assert_selector('tr', count: 1)).to be true +>>>>>>> 306aded2d (row one for rspec) end end it 'Deletes Currency Conversion', feature: true, js: true do +<<<<<<< HEAD conference.currency_conversions << create(:currency_conversion) visit admin_conference_currency_conversions_path(conference.short_title) # Remove currency conversion @@ -49,6 +70,20 @@ within('table#currency_conversions') do expect(page.assert_selector('tbody tr', count: 0)).to be true end +======= + visit admin_conference_currency_conversions_path(conference.short_title) + + # Remove currency conversion + within('table tr:nth-of-type(1)') do + click_link 'Delete' + end + page.accept_alert + page.find('#flash') + + # Validations + expect(flash).to eq('Difficulty level successfully deleted.') + expect(page.assert_selector('tr', count: 0)).to be true +>>>>>>> 306aded2d (row one for rspec) end end end diff --git a/spec/features/currency_spec.rb b/spec/features/currency_spec.rb deleted file mode 100644 index 9e0ea1ded..000000000 --- a/spec/features/currency_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe CurrencyConversion do - let!(:conference) { create(:conference, title: 'ExampleCon') } - let!(:organizer) { create(:organizer, resource: conference) } - - context 'as a organizer' do - before do - sign_in organizer - end - - after do - sign_out - end - - it 'add a currency conversion', feature: true do - visit admin_conference_currency_conversions_path(conference.short_title) - click_link 'Add Currency Conversion' - - fill_in 'from_currency', with: 'USD' - fill_in 'to_currency', with: 'EUR' - fill_in 'rate', with: '0.89' - - click_button 'Create Currency Conversion' - page.find('#flash') - expect(flash).to eq('Currency conversion was successfully created.') - within('table#currency_conversions') do - expect(page.has_content?('USD')).to be true - expect(page.has_content?('EUR')).to be true - expect(page.assert_selector('tr', count: 1)).to be true - end - end - - it 'Deletes Currency Conversion', feature: true, js: true do - visit admin_conference_currency_conversions_path(conference.short_title) - - # Remove currency conversion - within('table tr:first') do - click_link 'Delete' - end - page.accept_alert - page.find('#flash') - - # Validations - expect(flash).to eq('Difficulty level successfully deleted.') - expect(page.assert_selector('tr', count: 0)).to be true - end - - end -end From e8b5ebee7955fb182b09cd44d8fc99cee85ee33e Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 26 Apr 2023 21:22:02 -0700 Subject: [PATCH 078/100] trying as a button --- spec/features/currency_conversions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index 828aadca6..954523a80 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -25,7 +25,7 @@ it 'add a currency conversion', feature: true do visit admin_conference_currency_conversions_path(conference.short_title) - click_link 'Add Currency Conversion' + click_button 'Add Currency Conversion' <<<<<<< HEAD fill_in 'currency_conversion_from_currency', with: 'USD' From b1e606f986d8da579a3774ed3a882a49078e56f9 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Wed, 26 Apr 2023 21:45:19 -0700 Subject: [PATCH 079/100] delete --- spec/features/currency_conversions_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index 954523a80..dc39008e1 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -25,7 +25,7 @@ it 'add a currency conversion', feature: true do visit admin_conference_currency_conversions_path(conference.short_title) - click_button 'Add Currency Conversion' + click_link 'Add Currency Conversion' <<<<<<< HEAD fill_in 'currency_conversion_from_currency', with: 'USD' @@ -74,7 +74,7 @@ visit admin_conference_currency_conversions_path(conference.short_title) # Remove currency conversion - within('table tr:nth-of-type(1)') do + within('table#currency_conversions tr:nth-of-type(1)') do click_link 'Delete' end page.accept_alert From 434a9c2472d12b3feeb46cc2b943c2c7db609ebc Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 27 Apr 2023 11:29:09 -0700 Subject: [PATCH 080/100] login as admin for rspec tests --- config/routes.rb | 2 ++ spec/features/currency_conversions_spec.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 9ed30e8a9..8a3e24428 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ +# rubocop:disable Metrics/BlockLength Osem::Application.routes.draw do mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development? @@ -247,3 +248,4 @@ # A Short Fallback Route get '/:id', to: 'conferences#show' end +# rubocop:enable Metrics/BlockLength diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index dc39008e1..e2db69848 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -4,6 +4,7 @@ describe CurrencyConversion do let!(:conference) { create(:conference, title: 'ExampleCon') } +<<<<<<< HEAD <<<<<<< HEAD let!(:admin) { create(:admin) } @@ -17,6 +18,13 @@ before do sign_in organizer >>>>>>> 306aded2d (row one for rspec) +======= + let!(:admin) { create(:admin) } + + context 'as a organizer' do + before do + sign_in admin +>>>>>>> e01a54549 (login as admin for rspec tests) end after do @@ -27,6 +35,7 @@ visit admin_conference_currency_conversions_path(conference.short_title) click_link 'Add Currency Conversion' +<<<<<<< HEAD <<<<<<< HEAD fill_in 'currency_conversion_from_currency', with: 'USD' fill_in 'currency_conversion_to_currency', with: 'EUR' @@ -40,6 +49,13 @@ click_button 'Create Currency Conversion' >>>>>>> 306aded2d (row one for rspec) +======= + fill_in 'currency_conversion_from_currency', with: 'USD' + fill_in 'currency_conversion_to_currency', with: 'EUR' + fill_in 'currency_conversion_rate', with: '0.89' + + click_button 'Create Currency conversion' +>>>>>>> e01a54549 (login as admin for rspec tests) page.find('#flash') expect(flash).to eq('Currency conversion was successfully created.') within('table#currency_conversions') do From f707f8c93504cce9aab480bb0ff04db116d1710f Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Thu, 27 Apr 2023 16:18:16 -0700 Subject: [PATCH 081/100] factories and testing delete --- spec/features/currency_conversions_spec.rb | 51 ---------------------- 1 file changed, 51 deletions(-) diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index e2db69848..eb53c7804 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -4,27 +4,11 @@ describe CurrencyConversion do let!(:conference) { create(:conference, title: 'ExampleCon') } -<<<<<<< HEAD -<<<<<<< HEAD let!(:admin) { create(:admin) } context 'as an admin' do before do sign_in admin -======= - let!(:organizer) { create(:organizer, resource: conference) } - - context 'as a organizer' do - before do - sign_in organizer ->>>>>>> 306aded2d (row one for rspec) -======= - let!(:admin) { create(:admin) } - - context 'as a organizer' do - before do - sign_in admin ->>>>>>> e01a54549 (login as admin for rspec tests) end after do @@ -35,42 +19,21 @@ visit admin_conference_currency_conversions_path(conference.short_title) click_link 'Add Currency Conversion' -<<<<<<< HEAD -<<<<<<< HEAD fill_in 'currency_conversion_from_currency', with: 'USD' fill_in 'currency_conversion_to_currency', with: 'EUR' fill_in 'currency_conversion_rate', with: '0.89' click_button 'Create Currency conversion' -======= - fill_in 'from_currency', with: 'USD' - fill_in 'to_currency', with: 'EUR' - fill_in 'rate', with: '0.89' - - click_button 'Create Currency Conversion' ->>>>>>> 306aded2d (row one for rspec) -======= - fill_in 'currency_conversion_from_currency', with: 'USD' - fill_in 'currency_conversion_to_currency', with: 'EUR' - fill_in 'currency_conversion_rate', with: '0.89' - - click_button 'Create Currency conversion' ->>>>>>> e01a54549 (login as admin for rspec tests) page.find('#flash') expect(flash).to eq('Currency conversion was successfully created.') within('table#currency_conversions') do expect(page.has_content?('USD')).to be true expect(page.has_content?('EUR')).to be true -<<<<<<< HEAD expect(page.assert_selector('tbody tr', count: 1)).to be true -======= - expect(page.assert_selector('tr', count: 1)).to be true ->>>>>>> 306aded2d (row one for rspec) end end it 'Deletes Currency Conversion', feature: true, js: true do -<<<<<<< HEAD conference.currency_conversions << create(:currency_conversion) visit admin_conference_currency_conversions_path(conference.short_title) # Remove currency conversion @@ -86,20 +49,6 @@ within('table#currency_conversions') do expect(page.assert_selector('tbody tr', count: 0)).to be true end -======= - visit admin_conference_currency_conversions_path(conference.short_title) - - # Remove currency conversion - within('table#currency_conversions tr:nth-of-type(1)') do - click_link 'Delete' - end - page.accept_alert - page.find('#flash') - - # Validations - expect(flash).to eq('Difficulty level successfully deleted.') - expect(page.assert_selector('tr', count: 0)).to be true ->>>>>>> 306aded2d (row one for rspec) end end end From 68b5f66a7e0e627d3633bfbe085e7d8ea696327e Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Fri, 28 Apr 2023 02:24:23 -0700 Subject: [PATCH 082/100] renaming id for curr conv index form cause of linter err --- app/views/admin/currency_conversions/index.html.haml | 2 +- spec/features/currency_conversions_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/currency_conversions/index.html.haml b/app/views/admin/currency_conversions/index.html.haml index d85152938..b0b9ee68a 100644 --- a/app/views/admin/currency_conversions/index.html.haml +++ b/app/views/admin/currency_conversions/index.html.haml @@ -6,7 +6,7 @@ Enter the currency conversions for this conference .row .col-md-12 - %table.table.table-hover#currency_conversions + %table.table.table-hover#currency-conversions %thead %th From Curr %th To Curr diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index eb53c7804..afe3e6755 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -26,7 +26,7 @@ click_button 'Create Currency conversion' page.find('#flash') expect(flash).to eq('Currency conversion was successfully created.') - within('table#currency_conversions') do + within('table#currency-conversions') do expect(page.has_content?('USD')).to be true expect(page.has_content?('EUR')).to be true expect(page.assert_selector('tbody tr', count: 1)).to be true @@ -46,7 +46,7 @@ # Validations expect(flash).to eq('Currency conversion was successfully deleted.') - within('table#currency_conversions') do + within('table#currency-conversions') do expect(page.assert_selector('tbody tr', count: 0)).to be true end end From 7b2e833c154d7ab2e82811701fc5dd70a4171f34 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Fri, 28 Apr 2023 02:43:03 -0700 Subject: [PATCH 083/100] validats that no two rows in a the same conference curr conv tbl have the same from curr and to curr --- app/models/currency_conversion.rb | 1 + app/views/layouts/_admin_sidebar.html.haml | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/currency_conversion.rb b/app/models/currency_conversion.rb index d4ac6eba3..21bd001a0 100644 --- a/app/models/currency_conversion.rb +++ b/app/models/currency_conversion.rb @@ -12,4 +12,5 @@ class CurrencyConversion < ApplicationRecord belongs_to :conference validates :rate, numericality: { greater_than: 0 } + validates :from_currency, uniqueness: { scope: :to_currency } end diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index eb88c3df0..4f76228db 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -116,8 +116,9 @@ - if can? :update, @conference.tickets.build %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) - %li - = link_to 'Currency', admin_conference_currency_conversions_path(@conference.short_title) + - if can? :update, @conference.currency_conversions.build + %li + = link_to 'Currency', admin_conference_currency_conversions_path(@conference.short_title) From 3a08318fe63cdcf0966f7d04fc3bbb7ca27322a3 Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Sat, 29 Apr 2023 11:37:32 -0700 Subject: [PATCH 084/100] edit curr conv test and a warning not to add curr conv --- app/models/currency_conversion.rb | 2 +- .../currency_conversions/index.html.haml | 3 +++ spec/features/currency_conversions_spec.rb | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/models/currency_conversion.rb b/app/models/currency_conversion.rb index 21bd001a0..c675b5a27 100644 --- a/app/models/currency_conversion.rb +++ b/app/models/currency_conversion.rb @@ -12,5 +12,5 @@ class CurrencyConversion < ApplicationRecord belongs_to :conference validates :rate, numericality: { greater_than: 0 } - validates :from_currency, uniqueness: { scope: :to_currency } + validates :from_currency, uniqueness: { scope: :to_currency }, on: :create end diff --git a/app/views/admin/currency_conversions/index.html.haml b/app/views/admin/currency_conversions/index.html.haml index b0b9ee68a..e86817e8c 100644 --- a/app/views/admin/currency_conversions/index.html.haml +++ b/app/views/admin/currency_conversions/index.html.haml @@ -4,6 +4,9 @@ %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/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index afe3e6755..f4409d33f 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -29,6 +29,28 @@ within('table#currency-conversions') do expect(page.has_content?('USD')).to be true expect(page.has_content?('EUR')).to be true + expect(page.has_content?('0.89')).to be true + expect(page.assert_selector('tbody tr', count: 1)).to be true + end + end + + + it 'edit a currency conversion', feature: true do + conference.currency_conversions << create(:currency_conversion) + visit admin_conference_currency_conversions_path(conference.short_title) + within('table tbody tr:nth-of-type(1) td:nth-of-type(4)') do + click_link 'Edit' + end + fill_in 'currency_conversion_from_currency', with: 'USD' + fill_in 'currency_conversion_to_currency', with: 'RMB' + fill_in 'currency_conversion_rate', with: '6.9' + click_button 'Update Currency conversion' + page.find('#flash') + expect(flash).to eq('Currency conversion was successfully updated.') + within('table#currency-conversions') do + expect(page.has_content?('USD')).to be true + expect(page.has_content?('RMB')).to be true + expect(page.has_content?('6.9')).to be true expect(page.assert_selector('tbody tr', count: 1)).to be true end end From ddf7dbff4306db88fb5d7b1df43155466a9d3f5b Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Sat, 29 Apr 2023 11:42:34 -0700 Subject: [PATCH 085/100] extra space before click_link edit --- spec/features/currency_conversions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index f4409d33f..da2201053 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -39,7 +39,7 @@ conference.currency_conversions << create(:currency_conversion) visit admin_conference_currency_conversions_path(conference.short_title) within('table tbody tr:nth-of-type(1) td:nth-of-type(4)') do - click_link 'Edit' + click_link 'Edit' end fill_in 'currency_conversion_from_currency', with: 'USD' fill_in 'currency_conversion_to_currency', with: 'RMB' From 269cb83646b3518abb52813a61d221f329e5af3d Mon Sep 17 00:00:00 2001 From: Justin Pau Date: Sat, 29 Apr 2023 11:45:29 -0700 Subject: [PATCH 086/100] extra line --- spec/features/currency_conversions_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/currency_conversions_spec.rb b/spec/features/currency_conversions_spec.rb index da2201053..52f3388ce 100644 --- a/spec/features/currency_conversions_spec.rb +++ b/spec/features/currency_conversions_spec.rb @@ -34,7 +34,6 @@ end end - it 'edit a currency conversion', feature: true do conference.currency_conversions << create(:currency_conversion) visit admin_conference_currency_conversions_path(conference.short_title) From bfdabc4e75be801e7d7bff23fedc9b66f51d38ef Mon Sep 17 00:00:00 2001 From: killamonis Date: Mon, 1 May 2023 11:47:21 -0700 Subject: [PATCH 087/100] quick fix for email subject feature --- app/models/ticket_purchase.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 34d5b87f4..79b5ea6d6 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -114,7 +114,7 @@ def registration_ticket_already_purchased def generate_confirmation_mail(event_template) parser = EmailTemplateParser.new(conference, user) values = parser.retrieve_values(nil, nil, quantity, ticket) - parser.parse_template(event_template, values) + EmailTemplateParser.parse_template(event_template, values) end end From 720ecf650b05ccf5b3c53cf7b8a2cc7857434d85 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Tue, 13 Feb 2024 03:12:49 -0800 Subject: [PATCH 088/100] Resolve lockfile simplecov versions --- Gemfile.lock | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 03bda93df..062600de3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -404,8 +404,6 @@ GEM nio4r (2.7.0) nokogiri (1.16.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.2-x86_64-linux) - racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) @@ -639,13 +637,11 @@ GEM simple_po_parser (1.1.6) simplecov (0.17.1) docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-cobertura (2.1.0) - rexml - simplecov (~> 0.19) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.4) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-cobertura (1.4.2) + simplecov (~> 0.8) + simplecov-html (0.10.2) snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) @@ -657,7 +653,6 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.6.3-arm64-darwin) - sqlite3 (1.6.3-x86_64-linux) ssrf_filter (1.1.1) stripe (5.55.0) stripe-ruby-mock (3.1.0.rc3) @@ -719,9 +714,6 @@ GEM PLATFORMS arm64-darwin-23 - x86_64-darwin-21 - x86_64-darwin-22 - x86_64-linux DEPENDENCIES active_model_serializers From ac4a9b4820d06c016ba88f7fa738ef19fc31d039 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Tue, 13 Feb 2024 03:14:40 -0800 Subject: [PATCH 089/100] Re-add CI platform to lockfile?? --- Gemfile.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 062600de3..e857a1cbe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -404,6 +404,8 @@ GEM nio4r (2.7.0) nokogiri (1.16.2-arm64-darwin) racc (~> 1.4) + nokogiri (1.16.2-x86_64-linux) + racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) @@ -653,6 +655,7 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.6.3-arm64-darwin) + sqlite3 (1.6.3-x86_64-linux) ssrf_filter (1.1.1) stripe (5.55.0) stripe-ruby-mock (3.1.0.rc3) @@ -714,6 +717,7 @@ GEM PLATFORMS arm64-darwin-23 + x86_64-linux DEPENDENCIES active_model_serializers From 9006ab1c38141ad795c077157fd669c9b3408d24 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Tue, 13 Feb 2024 03:51:41 -0800 Subject: [PATCH 090/100] attempt a workflow file update --- .github/workflows/spec.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 61601747e..4f0e600ca 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -82,5 +82,5 @@ jobs: run: | export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" $CCTR sum-coverage coverage/codeclimate.*.json - $CCTR upload-coverage --id $CCTR_ID - $CCTR after-build --id $CCTR_ID + $CCTR upload-coverage --id ${{ secrets.CC_TEST_REPORTER_ID }} + $CCTR after-build --id ${{ secrets.CC_TEST_REPORTER_ID }} From cc2eaba6fb29aa48122a0caebefcc66e1385165b Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Tue, 13 Feb 2024 03:55:20 -0800 Subject: [PATCH 091/100] Obviously, this is a bad idea --- .github/workflows/spec.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 4f0e600ca..4c7af5acf 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -82,5 +82,5 @@ jobs: run: | export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" $CCTR sum-coverage coverage/codeclimate.*.json - $CCTR upload-coverage --id ${{ secrets.CC_TEST_REPORTER_ID }} - $CCTR after-build --id ${{ secrets.CC_TEST_REPORTER_ID }} + $CCTR upload-coverage --id "6d21ff1a59b134f3741779d50325f7bd5183cbe6b205051573d955705148960f" + $CCTR after-build --id "6d21ff1a59b134f3741779d50325f7bd5183cbe6b205051573d955705148960f" From cf5fcb1659bd5be83d483de64b1b2bed3c6c16ca Mon Sep 17 00:00:00 2001 From: Warren Huang Date: Sat, 17 Feb 2024 12:01:41 -0800 Subject: [PATCH 092/100] Clean up legacy code from SP23 Main --- .codeclimate.yml | 2 +- README.md | 16 ++-------------- app/helpers/admin/tickets_helper.rb | 14 ++++++++++++++ app/models/ticket_purchase.rb | 1 - app/services/email_template_parser.rb | 6 +++--- .../admin/currency_conversions/index.html.haml | 9 +++++---- app/views/admin/tickets/_form.html.haml | 10 ++++++---- app/views/shared/_help.html.haml | 2 +- readme | 0 9 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 app/helpers/admin/tickets_helper.rb delete mode 100644 readme diff --git a/.codeclimate.yml b/.codeclimate.yml index 581ab3566..c9e9a0539 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,4 +1,4 @@ plugins: rubocop: enabled: true - channel: rubocop-1-39-0 + channel: rubocop-1-56-3 diff --git a/README.md b/README.md index 6c32456ea..f8805568f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ +## Srping 2024 CS169L [![Specs](https://github.com/snap-cloud/snapcon/actions/workflows/spec.yml/badge.svg)](https://github.com/snap-cloud/snapcon/actions/workflows/spec.yml) +[![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) [![Maintainability](https://api.codeclimate.com/v1/badges/b7b0d559a03bf218663a/maintainability)](https://codeclimate.com/github/snap-cloud/snapcon/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/b7b0d559a03bf218663a/test_coverage)](https://codeclimate.com/github/snap-cloud/snapcon/test_coverage) [![codecov](https://codecov.io/gh/snap-cloud/snapcon/branch/snapcon/graph/badge.svg?token=EViEwaSjH4)](https://codecov.io/gh/snap-cloud/snapcon) @@ -6,20 +8,6 @@ Deploy
-## Spring 2022 CS169L: -[![Bluejay Dashboard](https://img.shields.io/badge/Bluejay-Dashboard_Snap!Con-blue.svg)](http://dashboard.bluejay.governify.io/dashboard/script/dashboardLoader.js?dashboardURL=https://reporter.bluejay.governify.io/api/v4/dashboards/tpa-CS169L-22-GH-yewchung_snapcon/main) -[![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) -[![Ruby on Rails CI](https://github.com/cs169L-spring2022-snapcon/snapcon/actions/workflows/rubyonrails.yml/badge.svg)](https://github.com/cs169L-spring2022-snapcon/snapcon/actions/workflows/rubyonrails.yml) -[![Maintainability](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/maintainability)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/maintainability) -[![Test Coverage](https://api.codeclimate.com/v1/badges/6b5dc427c6d2ae2b810e/test_coverage)](https://codeclimate.com/github/cs169L-spring2022-snapcon/snapcon/test_coverage) - -## Spring 2023 CS169L: -[![Bluejay Dashboard](https://img.shields.io/badge/Bluejay-Dashboard_02-blue.svg)](http://dashboard.bluejay.governify.io/dashboard/script/dashboardLoader.js?dashboardURL=https://reporter.bluejay.governify.io/api/v4/dashboards/tpa-CS169L-23-GH-cs169_snapcon/main) -[![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) -[![CodeQL](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/codeql-analysis.yml) -[![build](https://github.com/cs169/snapcon/actions/workflows/main.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/main.yml) -[![Specs](https://github.com/cs169/snapcon/actions/workflows/spec.yml/badge.svg)](https://github.com/cs169/snapcon/actions/workflows/spec.yml) - # [Snap!Con](https://snapcon.org) Forked From: ## Open Source Event Manager - [osem.io](https://osem.io) diff --git a/app/helpers/admin/tickets_helper.rb b/app/helpers/admin/tickets_helper.rb new file mode 100644 index 000000000..5db9f82de --- /dev/null +++ b/app/helpers/admin/tickets_helper.rb @@ -0,0 +1,14 @@ +module Admin + module TicketsHelper + def default_ticket_email_template + { + subject_input_id: 'ticket_email_subject', + subject_text: '{conference} | Ticket Confirmation and PDF!', + body_input_id: 'ticket_email_body', + body_text: "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} +ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, +find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team" + } + end + end +end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index 79b5ea6d6..5a076ea9d 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -16,7 +16,6 @@ # user_id :integer # -# add a currency field class TicketPurchase < ApplicationRecord belongs_to :ticket belongs_to :user diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb index fa2f5390f..9c74e2d78 100644 --- a/app/services/email_template_parser.rb +++ b/app/services/email_template_parser.rb @@ -13,14 +13,14 @@ def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) 'conference_start_date' => @conference.start_date, 'conference_end_date' => @conference.end_date, 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + @conference.short_title, host: Rails.application.routes.default_url_options[:host] ), 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + @conference.short_title, host: Rails.application.routes.default_url_options[:host] ), 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + @conference.short_title, host: Rails.application.routes.default_url_options[:host] ) } if @conference.program.cfp diff --git a/app/views/admin/currency_conversions/index.html.haml b/app/views/admin/currency_conversions/index.html.haml index e86817e8c..1d5ef815b 100644 --- a/app/views/admin/currency_conversions/index.html.haml +++ b/app/views/admin/currency_conversions/index.html.haml @@ -11,10 +11,11 @@ .col-md-12 %table.table.table-hover#currency-conversions %thead - %th From Curr - %th To Curr - %th Rate - %th Actions + %tr + %th From Curr + %th To Curr + %th Rate + %th Actions %tbody - @conference.currency_conversions.each do |currency_conversion| %tr diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 74cea4f97..5ad23df1f 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -12,10 +12,12 @@ .form-group = f.label :email_body = f.text_area :email_body, rows: 10, cols: 20, class: 'form-control' - %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'ticket_email_subject', - 'data-subject-text' => '{conference} | Ticket Confirmation and PDF!', - 'data-body-input-id' => 'ticket_email_body', - 'data-body-text' => "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team"} Load Default Email + - email_template = default_ticket_email_template + %a.btn.btn-link.control_label.load_template{'data-subject-input-id' => email_template[:subject_input_id], + 'data-subject-text' => email_template[:subject_text], + 'data-body-input-id' => email_template[:body_input_id], + 'data-body-text' => email_template[:body_text] + } Load Default Email %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help = render partial: 'shared/help', locals: { id: 'accepted_help', show_event_variables: true, show_ticket_variables: true} .form-group diff --git a/app/views/shared/_help.html.haml b/app/views/shared/_help.html.haml index a41276da0..8bf1ffc33 100644 --- a/app/views/shared/_help.html.haml +++ b/app/views/shared/_help.html.haml @@ -10,7 +10,7 @@ %tr %td {conference} %td The full conference title - - if (show_event_variables.present?) + - if show_event_variables.present? %tr %td {proposalslink} %td A link to the user's proposal page diff --git a/readme b/readme deleted file mode 100644 index e69de29bb..000000000 From 9eb48986b84d8985c3f733b3352dfa468dcc03ed Mon Sep 17 00:00:00 2001 From: Owen Hu Date: Mon, 19 Feb 2024 16:33:51 -0800 Subject: [PATCH 093/100] fix admin toggle showing and is confirm toggleable --- app/views/users/_form.haml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/views/users/_form.haml b/app/views/users/_form.haml index 22aa8589b..1750a5b69 100644 --- a/app/views/users/_form.haml +++ b/app/views/users/_form.haml @@ -4,11 +4,8 @@ Confirmed? = check_box_tag @user.id, @user.id, @user.confirmed?, url: "#{toggle_confirmation_admin_user_path(@user.id)}?user[to_confirm]=", - class: 'switch-checkbox' -= f.label :is_admin -= f.check_box :is_admin, class: 'switch-checkbox' -.help-block - An admin can create a new conference, manage users and make other users admins. + class: 'switch-checkbox', + readonly: true %h2 Basic Information From 16d8faa339dcea9b88c47e8041bed83ca2eb90e6 Mon Sep 17 00:00:00 2001 From: Tiffany Lam Date: Thu, 22 Feb 2024 16:21:51 -0800 Subject: [PATCH 094/100] changed 'Submission instructions' to 'Submission Template' in new event types view --- app/views/admin/event_types/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/event_types/_form.html.haml b/app/views/admin/event_types/_form.html.haml index f22a518f2..8ef22367b 100644 --- a/app/views/admin/event_types/_form.html.haml +++ b/app/views/admin/event_types/_form.html.haml @@ -24,7 +24,7 @@ %abbr{title: 'This field is required'} * = f.number_field :maximum_abstract_length, size: 3, required: true, class: 'form-control' .form-group - = f.label :submission_instructions + = f.label :submission_instructions, 'Submission Template' = f.text_area :submission_instructions, rows: 5, data: { provide: 'markdown' } .help-block= markdown_hint .form-group From 1503b9079d0aa2da44e01399263636c299a2fb98 Mon Sep 17 00:00:00 2001 From: Owen Yang Date: Fri, 23 Feb 2024 02:23:28 -0800 Subject: [PATCH 095/100] update INSTALL_SNAPCON.md --- INSTALL_SNAPCON.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/INSTALL_SNAPCON.md b/INSTALL_SNAPCON.md index ddbab8cbf..d3cc13d26 100644 --- a/INSTALL_SNAPCON.md +++ b/INSTALL_SNAPCON.md @@ -18,6 +18,19 @@ The recommended setup steps are as follows: 1. Other features will require more environment variables. See [Environment Variables](#environment-variables) and [INSTALL.md#configuration](INSTALL.md#configuration) for all the environment variables that may be set. 1. Run `rake db:setup` (this command and all following commands may need to prefixed with `bundle exec`) to initialize the database. +## Setting Environment Variables for macOS + +For developers using macOS, it's necessary to set an environment variable to prevent issues related to forking processes. Before starting the application, please set `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` by following these steps: + +### For Temporary Use in the Current Terminal Session + +Execute the following command in your terminal: + +``` +export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES +``` +This will set the environment variable for the duration of your current terminal session. You'll need to run this command each time you open a new terminal window. + ## Local Deployment To run Snap!Con, using [Overmind](https://github.com/DarthSim/overmind) or [Foreman](https://github.com/ddollar/foreman) is recommended. Since a release command is run which automatically performs migrations, it is necessary to flag the `release` command as able to be exited without closing all other processes. The Rails server may be run via the typical `rails server` command, but do note that no jobs will be run. From debc26a91c3e70db8ce2ec6fdbb6f501b6ad2bb4 Mon Sep 17 00:00:00 2001 From: Warren Huang Date: Sat, 17 Feb 2024 23:16:28 -0800 Subject: [PATCH 096/100] Update .codeclimate.yml --- .codeclimate.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index c9e9a0539..4aa460724 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,4 +1,6 @@ +version: "2" plugins: rubocop: enabled: true - channel: rubocop-1-56-3 + channel: rubocop-1-48-1 + From 6543594282034d08b270e4c5f46087f73ba656f8 Mon Sep 17 00:00:00 2001 From: Tiffany Lam Date: Mon, 26 Feb 2024 15:25:56 -0800 Subject: [PATCH 097/100] renamed event_types attribute submission_instructions to submission_template --- .../admin/event_types_controller.rb | 2 +- app/helpers/event_types_helper.rb | 2 +- app/models/event_type.rb | 2 +- app/views/admin/event_types/_form.html.haml | 4 ++-- app/views/admin/event_types/index.html.haml | 2 +- .../_submission_type_content_form.haml | 4 ++-- ...ion_instructions_to_submission_template.rb | 5 +++++ db/schema.rb | 4 ++-- spec/factories/event_types.rb | 4 ++-- spec/features/event_types_spec.rb | 2 +- spec/features/proposals_spec.rb | 4 ++-- spec/models/event_type_spec.rb | 2 +- .../event_schedule_serializer_spec.rb | 20 +++++++++++++++++++ 13 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20240226175634_rename_submission_instructions_to_submission_template.rb diff --git a/app/controllers/admin/event_types_controller.rb b/app/controllers/admin/event_types_controller.rb index 34c3c6428..0cd2eed20 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_instructions, :color, :conference_id, :description) + :submission_template, :color, :conference_id, :description) end end end diff --git a/app/helpers/event_types_helper.rb b/app/helpers/event_types_helper.rb index f6e4abc11..ca7c3e511 100644 --- a/app/helpers/event_types_helper.rb +++ b/app/helpers/event_types_helper.rb @@ -15,7 +15,7 @@ def event_type_select_options(event_types = {}) { data: { min_words: type.minimum_abstract_length, max_words: type.maximum_abstract_length, - instructions: type.submission_instructions + instructions: type.submission_template } } ] end diff --git a/app/models/event_type.rb b/app/models/event_type.rb index f04756984..0d2943dc9 100644 --- a/app/models/event_type.rb +++ b/app/models/event_type.rb @@ -10,7 +10,7 @@ # length :integer default(30) # maximum_abstract_length :integer default(500) # minimum_abstract_length :integer default(0) -# submission_instructions :text +# submission_template :text # title :string not null # created_at :datetime # updated_at :datetime diff --git a/app/views/admin/event_types/_form.html.haml b/app/views/admin/event_types/_form.html.haml index 8ef22367b..d0a2739c1 100644 --- a/app/views/admin/event_types/_form.html.haml +++ b/app/views/admin/event_types/_form.html.haml @@ -24,8 +24,8 @@ %abbr{title: 'This field is required'} * = f.number_field :maximum_abstract_length, size: 3, required: true, class: 'form-control' .form-group - = f.label :submission_instructions, 'Submission Template' - = f.text_area :submission_instructions, rows: 5, data: { provide: 'markdown' } + = f.label :submission_template + = f.text_area :submission_template, rows: 5, data: { provide: 'markdown' } .help-block= markdown_hint .form-group = f.label :color diff --git a/app/views/admin/event_types/index.html.haml b/app/views/admin/event_types/index.html.haml index cac7985c4..56411fb78 100644 --- a/app/views/admin/event_types/index.html.haml +++ b/app/views/admin/event_types/index.html.haml @@ -24,7 +24,7 @@ %td = markdown(event_type.description) %td - = markdown(event_type.submission_instructions) + = markdown(event_type.submission_template) %td = event_type.length Minutes diff --git a/app/views/proposals/_submission_type_content_form.haml b/app/views/proposals/_submission_type_content_form.haml index 04d54aa32..6a66fa5a1 100644 --- a/app/views/proposals/_submission_type_content_form.haml +++ b/app/views/proposals/_submission_type_content_form.haml @@ -33,7 +33,7 @@ - program.event_types.each do |event_type| .help-block.select-help-text.event_event_type_id.collapse{ id: "#{dom_id(event_type)}-instructions" } - - if event_type.submission_instructions.blank? + - if event_type.submission_template.blank? %p Use this space to include any additional inforrmation that is helpful in reviewing your submission. @@ -43,7 +43,7 @@ committee review your submission with all the details they need. .panel.panel-primary .panel-heading= "#{event_type.name} Template" - .panel-body= markdown(event_type.submission_instructions) + .panel-body= markdown(event_type.submission_template) .panel-footer %button.btn.btn-warning.btn-xs.js-resetSubmissionText{ type: 'button', data: { confirm: 'Do you really want to reset your submission text to the provided template?' } } diff --git a/db/migrate/20240226175634_rename_submission_instructions_to_submission_template.rb b/db/migrate/20240226175634_rename_submission_instructions_to_submission_template.rb new file mode 100644 index 000000000..e6c70a134 --- /dev/null +++ b/db/migrate/20240226175634_rename_submission_instructions_to_submission_template.rb @@ -0,0 +1,5 @@ +class RenameSubmissionInstructionsToSubmissionTemplate < ActiveRecord::Migration[7.0] + def change + rename_column :event_types, :submission_instructions, :submission_template + end +end diff --git a/db/schema.rb b/db/schema.rb index ba49c16e0..0846383e0 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: 2023_03_09_012731) do +ActiveRecord::Schema[7.0].define(version: 2024_02_26_175634) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -223,7 +223,7 @@ t.integer "program_id" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil - t.text "submission_instructions" + t.text "submission_template" end create_table "event_users", force: :cascade do |t| diff --git a/spec/factories/event_types.rb b/spec/factories/event_types.rb index e74b0b363..43436a55e 100644 --- a/spec/factories/event_types.rb +++ b/spec/factories/event_types.rb @@ -10,7 +10,7 @@ # length :integer default(30) # maximum_abstract_length :integer default(500) # minimum_abstract_length :integer default(0) -# submission_instructions :text +# submission_template :text # title :string not null # created_at :datetime # updated_at :datetime @@ -24,7 +24,7 @@ description { 'Example Event Description\nThis event type is an example.' } minimum_abstract_length { 0 } maximum_abstract_length { 500 } - submission_instructions { 'Example Event Instructions _with_ **markdown**' } + submission_template { 'Example Event Instructions _with_ **markdown**' } color { '#ffffff' } program end diff --git a/spec/features/event_types_spec.rb b/spec/features/event_types_spec.rb index 73267a56f..09342050c 100644 --- a/spec/features/event_types_spec.rb +++ b/spec/features/event_types_spec.rb @@ -23,7 +23,7 @@ fill_in 'event_type_title', with: 'Party' fill_in 'event_type_length', with: '240' fill_in 'event_type_description', with: '**Description**' - fill_in 'event_type_submission_instructions', with: '**Instructions**' + fill_in 'event_type_submission_template', with: '**Instructions**' fill_in 'event_type_minimum_abstract_length', with: '0' fill_in 'event_type_maximum_abstract_length', with: '13042' page.find('#event_type_color').set('#e4e4e4') diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 2e6b665fd..6af19fcec 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -222,7 +222,7 @@ it 'can reset to text template', feature: true, js: true do event_type = conference.program.event_types[-1] event_type.description = 'Example event description' - event_type.submission_instructions = '## Fill Me In!' + event_type.submission_template = '## Fill Me In!' event_type.save! sign_in participant @@ -237,7 +237,7 @@ # click_button 'Reset Submission to Template' # end - # expect(page.find('#event_submission_text').value).to eq(event_type.submission_instructions) + # expect(page.find('#event_submission_text').value).to eq(event_type.submission_template) end end diff --git a/spec/models/event_type_spec.rb b/spec/models/event_type_spec.rb index 973e23d31..2c30203c2 100644 --- a/spec/models/event_type_spec.rb +++ b/spec/models/event_type_spec.rb @@ -10,7 +10,7 @@ # length :integer default(30) # maximum_abstract_length :integer default(500) # minimum_abstract_length :integer default(0) -# submission_instructions :text +# submission_template :text # title :string not null # created_at :datetime # updated_at :datetime diff --git a/spec/serializers/event_schedule_serializer_spec.rb b/spec/serializers/event_schedule_serializer_spec.rb index 9d4cc64e7..e9bb0ed7a 100644 --- a/spec/serializers/event_schedule_serializer_spec.rb +++ b/spec/serializers/event_schedule_serializer_spec.rb @@ -1,5 +1,25 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: event_schedules +# +# id :bigint not null, primary key +# enabled :boolean default(TRUE) +# start_time :datetime +# created_at :datetime not null +# updated_at :datetime not null +# event_id :integer +# room_id :integer +# schedule_id :integer +# +# Indexes +# +# index_event_schedules_on_event_id (event_id) +# index_event_schedules_on_event_id_and_schedule_id (event_id,schedule_id) UNIQUE +# index_event_schedules_on_room_id (room_id) +# index_event_schedules_on_schedule_id (schedule_id) +# require 'spec_helper' describe EventScheduleSerializer, type: :serializer do From c47dd478e6b7a2fb50603daf2c0af781ce925605 Mon Sep 17 00:00:00 2001 From: Owen Hu Date: Mon, 26 Feb 2024 16:36:39 -0800 Subject: [PATCH 098/100] Give up on using partials and just follow the same structure as OSEM's repo --- app/views/users/edit.html.haml | 61 +++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index 6f76f6fa9..d1b967620 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -6,4 +6,63 @@ .row .col-md-12 = form_for(@user) do |f| - = render 'form', f: f + %h2 Basic Information + .form-group + = f.label :name + = f.text_field :name, class: 'form-control' + %span.help-block + This is your real name + .form-group + = f.label :nickname + = f.text_field :nickname, class: 'form-control' + .form-group + = f.label :timezone + %br + = f.select :timezone, time_zone_options_for_select(selected: @user.timezone), include_blank: true + %span.help-block + 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 :avatar + %br + = image_tag(@user.gravatar_url(size: '48'), title: "Yo #{@user.name}!", alt: '', class: 'img-rounded') + %span.help-block + Change your avatar on + = link_to 'gravatar.com', 'https://gravatar.com' + %p + Or upload a picture. + = f.file_field :picture, hint: 'If you upload a picture, it will be used in place of Gravatar.' + - if @user.picture? + %p + Current Picture + %br + = image_tag(@user.picture.thumb.url, width: '20%') + .form-group + = f.label :affiliation + = f.text_field :affiliation, class: 'form-control' + %span.help-block + The company you work work, the user group you belong to, or nothing at all. + .form-group + = f.label :biography + = f.text_area :biography, rows: 5, data: { provide: 'markdown' }, class: 'form-control' + %span.help-block + You have used + %span#bio-length + = @user.biography ? @user.biography.split.length : 0 + words. Biographies are limited to 200 words. + = markdown_hint + .form-group + .text-right + = f.submit nil, class: 'btn btn-primary' + + :javascript + $(document).ready(function() { + $('#user_timezone').selectize({}) + }); + + let localOffset = (new Date()).getTimezoneOffset()/60; + // UTC JS offsets are "-1 *" of how they're displayed. + let operator = localOffset < 0 ? '+' : '-'; + $('.js-localTimezone').text(`(GMT ${operator}${Math.abs(localOffset)}).`); + From 27240a2906868636373bbcdd3878371137db6f55 Mon Sep 17 00:00:00 2001 From: Owen Hu Date: Mon, 26 Feb 2024 18:12:11 -0800 Subject: [PATCH 099/100] Update style from checkbox to yes-no toggle --- app/views/admin/users/_form.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 51a50069a..12f44433e 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -14,9 +14,10 @@ url: "#{toggle_confirmation_admin_user_path(@user.id)}?user[to_confirm]=", class: 'switch-checkbox', readonly: true - .checkbox + .form-group.switch %label - = f.check_box :is_admin + = f.check_box :is_admin, + class: 'switch-checkbox' Is admin %span.help-block An admin can create a new conference, manage users and make other users admins. .form-group From fe586cd7ec64703cbc7c00b6fb1f5199c40fd23c Mon Sep 17 00:00:00 2001 From: Owen Hu Date: Mon, 26 Feb 2024 18:12:19 -0800 Subject: [PATCH 100/100] get rid of dead code --- app/views/users/_form.haml | 69 -------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 app/views/users/_form.haml diff --git a/app/views/users/_form.haml b/app/views/users/_form.haml deleted file mode 100644 index 1750a5b69..000000000 --- a/app/views/users/_form.haml +++ /dev/null @@ -1,69 +0,0 @@ -- if !@user.new_record? && can?(:toggle_confirmation, @user) - .pull-right - %b - Confirmed? - = check_box_tag @user.id, @user.id, @user.confirmed?, - url: "#{toggle_confirmation_admin_user_path(@user.id)}?user[to_confirm]=", - class: 'switch-checkbox', - readonly: true - -%h2 Basic Information - -.form-group - = f.label :name - = f.text_field :name, class: 'form-control' - %span.help-block - This is your real name -.form-group - = f.label :nickname - = f.text_field :nickname, class: 'form-control' -.form-group - = f.label :timezone - %br - = f.select :timezone, time_zone_options_for_select(selected: @user.timezone), include_blank: true - %span.help-block - 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 :avatar - %br - = image_tag(@user.gravatar_url(size: '48'), title: "Yo #{@user.name}!", alt: '', class: 'img-rounded') - %span.help-block - Change your avatar on - = link_to 'gravatar.com', 'https://gravatar.com' - %p - Or upload a picture. - = f.file_field :picture, hint: 'If you upload a picture, it will be used in place of Gravatar.' - - if @user.picture? - %p - Current Picture - %br - = image_tag(@user.picture.thumb.url, width: '20%') -.form-group - = f.label :affiliation - = f.text_field :affiliation, class: 'form-control' - %span.help-block - The company you work work, the user group you belong to, or nothing at all. -.form-group - = f.label :biography - = f.text_area :biography, rows: 5, data: { provide: 'markdown' }, class: 'form-control' - %span.help-block - You have used - %span#bio-length - = @user.biography ? @user.biography.split.length : 0 - words. Biographies are limited to 200 words. - = markdown_hint -.form-group - .text-right - = f.submit nil, class: 'btn btn-primary' - -:javascript - $(document).ready(function() { - $('#user_timezone').selectize({}) - }); - - let localOffset = (new Date()).getTimezoneOffset()/60; - // UTC JS offsets are "-1 *" of how they're displayed. - let operator = localOffset < 0 ? '+' : '-'; - $('.js-localTimezone').text(`(GMT ${operator}${Math.abs(localOffset)}).`);