diff --git a/Capfile b/Capfile new file mode 100644 index 0000000..f072e31 --- /dev/null +++ b/Capfile @@ -0,0 +1,28 @@ +# Load DSL and set up stages +require "capistrano/setup" + +# Include default deployment tasks +require "capistrano/deploy" + +# Include tasks from other gems included in your Gemfile +# +# For documentation on these, see for example: +# +# https://github.com/capistrano/rvm +# https://github.com/capistrano/rbenv +# https://github.com/capistrano/chruby +# https://github.com/capistrano/bundler +# https://github.com/capistrano/rails +# https://github.com/capistrano/passenger +# +# require 'capistrano/rvm' +# require 'capistrano/rbenv' +# require 'capistrano/chruby' +# require 'capistrano/bundler' +# require 'capistrano/rails/assets' +# require 'capistrano/rails/migrations' +# require 'capistrano/passenger' +require 'capistrano/faster_assets' + +# Load custom tasks from `lib/capistrano/tasks` if you have any defined +Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } diff --git a/Gemfile b/Gemfile index 883e851..451be9a 100644 --- a/Gemfile +++ b/Gemfile @@ -6,9 +6,6 @@ gem 'pg' gem 'rack-mini-profiler' gem 'haml' -# gem 'jquery-rails' -# gem 'jquery-ui-rails' -# gem 'coffee-rails', '~> 4.0.0' gem 'nokogiri', '~> 1.6.8.rc2' if Dir.exists?('../lingua_franca') @@ -33,8 +30,6 @@ gem 'oauth2', '~> 0.8.0' gem 'carrierwave' gem 'carrierwave-imageoptimizer' gem 'mini_magick' -# gem 'nested_form' -# gem 'acts_as_list' gem 'geocoder' gem 'paper_trail', '~> 3.0.5' gem 'sitemap_generator' @@ -55,8 +50,13 @@ group :development do gem 'better_errors' gem 'binding_of_caller' gem 'meta_request' -# gem 'haml-rails' -# gem 'awesome_print' + + gem 'capistrano', '~> 3.1' + gem 'capistrano-rails', '~> 1.1' + gem 'capistrano-faster-assets', '~> 1.0' + + gem 'eventmachine', :github => 'krzcho/eventmachine', :branch => 'master' + gem 'thin', :github => 'krzcho/thin', :branch => 'master' end group :test do diff --git a/app/assets/images/edit.svg b/app/assets/images/edit.svg new file mode 100644 index 0000000..68f41ae --- /dev/null +++ b/app/assets/images/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/img.svg b/app/assets/images/img.svg new file mode 100644 index 0000000..9a3e09d --- /dev/null +++ b/app/assets/images/img.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/link.svg b/app/assets/images/link.svg new file mode 100644 index 0000000..f279380 --- /dev/null +++ b/app/assets/images/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/ol.svg b/app/assets/images/ol.svg new file mode 100644 index 0000000..405f9a0 --- /dev/null +++ b/app/assets/images/ol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/quote.svg b/app/assets/images/quote.svg new file mode 100644 index 0000000..53c1304 --- /dev/null +++ b/app/assets/images/quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/ul.svg b/app/assets/images/ul.svg new file mode 100644 index 0000000..145e61f --- /dev/null +++ b/app/assets/images/ul.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js new file mode 100644 index 0000000..ed4c0be --- /dev/null +++ b/app/assets/javascripts/editor.js @@ -0,0 +1,79 @@ +(function() { + var pens = {}; + + Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) { + startEditing(editor); + }); + + function startEditing(editor) { + var name = editor.dataset.name; + pens[name] = new Pen({ + editor: editor, + class: 'pen', + textarea: '', + list: ['p', 'h3', 'h4', 'blockquote', 'insertorderedlist', 'insertunorderedlist', 'bold', 'italic', 'underline', 'strikethrough', 'createlink', 'insertimage'], + title: { + 'p': 'Paragraph', + 'h3': 'Major Heading', + 'h4': 'Minor Heading', + 'blockquote': 'Quotation', + 'insertorderedlist': 'Ordered List', + 'insertunorderedlist': 'Unordered List', + 'bold': 'Bold', + 'italic': 'Italic', + 'underline': 'Underline', + 'strikethrough': 'Strikethrough', + 'createlink': 'Link', + 'insertimage': 'Image' + } + }); + } + + Array.prototype.forEach.call(document.querySelectorAll('form'), function(form) { + var shouldAllowAlert = false; + form.addEventListener('submit', function() { + if (shouldAllowAlert) { + return; + } + Array.prototype.forEach.call(document.querySelectorAll('.textarea'), function(editor) { + var name = editor.dataset.name; + var textarea = document.querySelector('textarea[name="' + name + '"]'); + if (!textarea) { + textarea = document.createElement('textarea'); + textarea.name = name; + textarea.style.display = 'none'; + form.appendChild(textarea); + } + textarea.value = editor.innerHTML; + pens[name].destroy(); + }); + }, false); + Array.prototype.forEach.call(form.querySelectorAll('button'), function(button) { + form.addEventListener('click', function(event) { + shouldAllowAlert = (event.target.value === 'cancel'); + }); + }); + }); + + Array.prototype.forEach.call(document.querySelectorAll('.check-box-field .other input'), function(input) { + var checkbox = document.getElementById(input.parentElement.parentElement.attributes.for.value); + input.addEventListener('keyup', function(event) { + if (event.target.value) { + checkbox.checked = true; + } + }); + input.addEventListener('click', function(event) { + checkbox.checked = true; + }); + var setRequired = function() { + if (checkbox.checked) { + input.setAttribute('required', 'required'); + } else { + input.removeAttribute('required'); + } + }; + Array.prototype.forEach.call(document.querySelectorAll('.check-box-field input'), function(_input) { + _input.addEventListener('change', function(event) { setRequired(); }); + }); + }); +})(); diff --git a/app/assets/javascripts/editor.js.coffee b/app/assets/javascripts/editor.js.coffee deleted file mode 100644 index 56ba4ef..0000000 --- a/app/assets/javascripts/editor.js.coffee +++ /dev/null @@ -1,37 +0,0 @@ -#= require froala_editor.min.js -$ -> - $('[data-editable]').editable({inlineMode: true, blockTags: ["n", "p", "h2", "blockquote", "pre"], buttons: ["formatBlock", "bold", "italic", "underline", "insertOrderedList", "insertUnorderedList", "sep", "createLink", "insertImage", "insertVideo", "html", "undo", "redo"]}) - $('[data-editor]').editable({inlineMode: false, blockTags: ["n", "p", "h2", "blockquote", "pre"], buttons: ["formatBlock", "bold", "italic", "underline", "insertOrderedList", "insertUnorderedList", "sep", "createLink", "html", "undo", "redo"]}) - $('.field.country-select-field select').change () -> - $country = $(this) - country = $country.val() - $territory = $('.field.subregion-select-field select') - if $territory.data().country == country - $territory.removeClass('can cant').addClass('can') - return - - $.post '/location/territories', {country: country}, - (json) -> - $territory.html('') - if json && Object.keys(json).length - $.each json, (code, name) -> - $territory.append($('').text(name).attr('value', code)) - return - $territory.removeClass('can cant').addClass('can') - $territory.data().country = country - else - $territory.removeClass('can cant').addClass('cant') - return - , 'json' - $('img + input[type="file"]').change () -> - readURL(this); - return - -readURL = (input) -> - reader = null - if input.files && input.files[0] - reader = new FileReader() - reader.readAsDataURL input.files[0] - reader.onload = (e) -> - $(input).prev().attr('src', e.target.result) - return diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js new file mode 100644 index 0000000..e961998 --- /dev/null +++ b/app/assets/javascripts/main.js @@ -0,0 +1,16 @@ +(function() { + Array.prototype.forEach.call(document.querySelectorAll('.number-field,.email-field,.text-field'), function(field) { + var input = field.querySelector('input'); + var positionLabel = function(input) { + field.classList[input.value ? 'remove' : 'add']('empty'); + } + positionLabel(input); + input.addEventListener('keyup', function(event) { positionLabel(event.target); }); + input.addEventListener('blur', function(event) { field.classList.remove('focused'); }); + input.addEventListener('focus', function(event) { field.classList.add('focused'); }); + }); + var errorField = document.querySelector('.input-field.has-error input, .input-field.has-error textarea'); + if (errorField) { + errorField.focus(); + } +})(); diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index 9d04a42..bf8354e 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -4,7 +4,7 @@ $zindex-base: 0; html, body { - color: #333; + color: $black; position: relative; z-index: $zindex-base; } @@ -29,7 +29,7 @@ p { a { text-decoration: none; - color: $colour-1; + color: $link-colour; border-bottom: 0 solid; outline: 0; position: relative; @@ -57,6 +57,14 @@ a { } } +.screen-reader-text { + clip: rect(1px, 1px, 1px, 1px); + height: 1px; + width: 1px; + overflow: hidden; + position: absolute !important; +} + table { margin-bottom: 2em; margin-left: 1em; @@ -109,6 +117,18 @@ button, #main &.secondary { background-color: $colour-1; } + + #main &.subdued { + background-color: #888; + } + + #main &.accented { + background-color: $colour-2; + } + + &.facebook { + background-color: #3A5795; + } } a.button { @@ -175,33 +195,36 @@ nav.sub-menu { } } -textarea { - $line-height: 2em; +textarea, .textarea { display: block; width: 100%; - min-height: $line-height * 4; - font-size: 1.25em; + min-height: 15em; + font-size: 1em; margin: 1em 0; - padding: 0 0.5em; - line-height: $line-height; + padding: 0.75em; border: 0.1rem solid #E8E8E8; outline: 0; - background: linear-gradient( - to bottom, - transparent, - transparent ($line-height - 0.05em), - rgba($colour-1, 0.33) 0.05em, - rgba($colour-1, 0.33) - ); - background-size: 100% $line-height; - border-radius: 0 0 1em 0; - color: #000; - font-weight: bold; - @include default-box-shadow(top, 2); - @include _(transition, box-shadow 100ms ease-in-out); + @include _(border-radius, 0.25rem); + color: inherit; + background-image: repeating-linear-gradient(135deg, rgba(#000, 0.025), rgba(#000, 0.025) 0.1em, transparent 0.1em, transparent 0.4em); + @include _(box-shadow, 0 0 0 0 rgba(0,0,0,0.05)); + @include _(transition, box-shadow 200ms ease-in-out); + will-change: box-shadow; &:hover, &:focus, &:active { - @include default-box-shadow(top, 1); + @include _(box-shadow, 0 0 0 0.3em rgba(0,0,0,0.05)); + } +} + +.textarea { + > :first-child { + margin-top: 0; + } + > :last-child { + margin-bottom: 0; + } + p { + font-size: 1.125em; } } @@ -223,17 +246,19 @@ input { .text-field { position: relative; margin-bottom: 2em; - background-color: #F8F8F8; - @include default-box-shadow(top, 1.5, false, 0 0.05em 0 0 #666); - @include _(transition, background-image 100ms ease-in-out); - $fn: ''; - $capability: get_capability(map-get($compatibility-map, css-gradients)); - @if $capability == yx or $capability == ax or $capability == y or $capability == a { - $fn: '-#{$browser_prefix}-'; + color: inherit; + + &.empty { + label { + z-index: $zindex-base + 3; + @include _(transform, translateY(-100%) scale(1)); + background-color: transparent; + color: #666; + } } - @include _(background-size, 8px 8px); - label { + label, + &.focused label { position: absolute; z-index: $zindex-base + 3; font-size: 1em; @@ -241,27 +266,14 @@ input { width: auto; @include _(transition, 'transform 250ms ease-in-out, color 250ms ease-in-out, background-color 250ms ease-in-out'); top: 100%; + left: 0; @include _(transform, translateY(0) scale(0.75)); @include _(transform-origin, 0 0); line-height: 1.5em; background-color: transparent; - color: #333; - } - - &.empty { - background-color: $white;//#E8E8E8; - background-image: - #{$fn}repeating-linear-gradient( - -45deg, #DDD, - #DDD 1px, transparent 1px, - transparent 6px - ); - label { - z-index: $zindex-base + 1; - @include _(transform, translateY(-100%) scale(1)); - background-color: transparent; - color: #888; - } + color: $black; + cursor: text; + will-change: transform, color, background-color; } input { @@ -269,22 +281,68 @@ input { position: relative; z-index: $zindex-base + 2; padding: 0.15em 0.5em; - background-color: transparent; - border: 0; + background-color: #F8F8F8; + @include _(border-radius, 0.25rem); + @include _(box-shadow, 0 0 0 0 rgba(0,0,0,0.05)); + @include _(transition, box-shadow 200ms ease-in-out); + background-image: repeating-linear-gradient(135deg, rgba(0, 0, 0, 0.025), rgba(0, 0, 0, 0.025) 0.1rem, transparent 0.1rem, transparent 0.4rem); + border: 0.1rem solid #E8E8E8; + will-change: box-shadow; } &.big input { font-size: 2em; + @include font-family(secondary); + + .composition & { + margin-top: 1em; + } } - &:focus, &:active { + &:focus, &:active, &:focus { + label { - color: #333; + color: $black; } } input { - &:focus, &:active { - @include _(box-shadow, inset 0 0 1em 1em #E8E8E8); + &:focus, &:active, &:hover { + @include _(box-shadow, 0 0 0 0.3em rgba(0,0,0,0.05)); + } + } +} + +@include keyframes(bend) { + to { + @include _(transform, skewX(-5deg)); + } +} + +.input-field { + .field-error { + display: block; + float: right; + background-color: rgba($colour-2, 0.333); + @include font-family(secondary); + padding: 0.5em 1em; + margin: 0 0.2em; + text-align: center; + @include _(transform, skewX(-15deg)); + @include _(transform-origin, 0 100%); + @include default-box-shadow(top, 2); + @include _(animation, bend ease-in-out 500ms infinite alternate both); + } + + &.check-box-field { + &.has-error { + margin-top: 3em; + } + + .field-error { + position: absolute; + bottom: 100%; + right: 0; + margin-right: 0; } } } @@ -297,7 +355,6 @@ input { &:checked { + div input { - //display: block; z-index: $zindex-base + 2; @include _(opacity, 1); } @@ -341,117 +398,134 @@ input { } .check-box-field, +.check-box-field.vertical, .radio-button-field { @include clearfix; - margin-bottom: 1em; + margin-bottom: 3em; position: relative; + @include default-box-shadow(top); label { - float: left; - @include default-box-shadow(top); - background-color: $colour-2; + display: block; + background-color: #FFF; + height: 2.333em; + font-weight: normal; + font-size: 1.25em; + @include font-family(secondary); text-align: center; position: relative; - margin: 0 1em 3em; - padding: 0.5em; + padding: 0.5em 0.5em 0.5em 3em; cursor: pointer; + border: 0.1em solid; + border-top: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; @include _(transition, #{'transform, background-color 100ms, 100ms ease-in-out, ease-in-out'}); - &:hover { - @include _(transform, scale(1.1)); - } - @include before-and-after { - content: ''; - position: absolute; - visibility: hidden; - @include _(transition, transform 200ms ease-in-out); - @include _(transition, transform 200ms cubic-bezier(0, 0.38, 0.9, 2)); - } + content: ''; + position: absolute; + @include _(transition, transform 200ms ease-in-out); + } @include before { - background-color: $colour-5; - bottom: -0.3em; - right: -0.5em; - width: 2em; - height: 2em; - border-radius: 50%; - @include _(transform, scale(0)); - @include default-box-shadow(top); + content: ''; + position: absolute; + top: 0; + left: 0; + width: 2.333em; + height: 100%; + border-right: inherit; } @include after { - border: 0.5em solid $white; - border-left: none; - border-top: none; - width: 1em; - height: 2em; - font-size: 0.6em; - bottom: 0.3em; - right: 0.3em; - @include _(transform, scale(0) rotate(45deg)); + content: ''; + position: absolute; + visibility: hidden; + left: 0.75em; + top: 0.25em; + border: 0.2em solid #FFF; + width: 0.75em; + height: 1.5em; + border-width: 0 .2em 0.2em 0; + @include _(transform, rotate(45deg) scale(0)); + @include _(transition, transform 200ms cubic-bezier(0, 0.38, 0.9, 2)); } } - svg { - width: 100%; - height: 100%; - margin-bottom: 0.75em; - fill: $white; - stroke: rgba(0, 0, 0, 0.667); - stroke-width: 0.05em; - - + svg { - position: absolute; - top: 0; - left: 0; - right: 0; - @include _(opacity, 0.75); - } + input:first-child + label { + border: 0.1em solid; } input { - position: absolute; - opacity: 0; - left: 1em; - bottom: 0.5em; + position: fixed; + opacity: 0 !important; + left: -100000px; + z-index: -10; &:checked + label { - background-color: $colour-1; - @include _(transform, scale(1.25)); - @include before { - visibility: visible; - @include _(transform, scale(1)); + background-color: $colour-5; } - @include after { visibility: visible; - @include _(transform, scale(1) rotate(45deg)); + @include _(transform, rotate(45deg) scale(1)); } } - } -} -.check-box-field { - label { - color: $white; - font-weight: normal; - @include font-family(secondary); - @include _(text-stroke, 0.5px #000); - - @include before { - font-size: 0.6em; - bottom: -0.8em; + &:hover + label { + @include before { + background-color: $colour-3; + } } + } - @include after { - font-size: 0.3em; - bottom: -0.5em; + .other { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 2.333em; + + input { + z-index: 1; + opacity: 1!important; + position: static; + margin: 0; + font: inherit; + height: 100%; + text-align: inherit; + cursor: inherit; } } } +.check-box-field.vertical { + display: block; + margin: 0 2em 5em; + font-size: 0.75em; +} + +// .check-box-field { +// label { +// color: $white; +// font-weight: normal; +// @include font-family(secondary); +// @include _(text-stroke, 0.5px #000); + +// @include before { +// font-size: 0.6em; +// bottom: -0.8em; +// } + +// @include after { +// font-size: 0.3em; +// bottom: -0.5em; +// } +// } +// } + .radio-button-field { label { width: 7em; @@ -504,6 +578,13 @@ form { } } +.flex-form, .flex-column { + button, .button { + width: 100%; + text-align: center; + } +} + .actions { text-align: center; margin: 0 1em; @@ -531,6 +612,59 @@ form { float: left; } } + + .conferences-view_workshop & { + margin-bottom: 5em; + } + + &.next-prev { + display: flex; + flex-direction: row-reverse; + margin: 0; + } +} + +ul.warnings { + list-style: none; + padding: 0; +} + +ul.warnings li, +.warning-info { + position: relative; + min-height: 4em; + border: 0.2em solid rgba(0, 0, 0, 0.1); + background-color: rgba($colour-3, 0.333); + padding: 1em 1em 1em 4em; + @include font-family(secondary); + text-align: left; + margin: 1em; + width: auto; + @include default-box-shadow(top, 2); + + @include before { + content: '!'; + display: block; + position: absolute; + top: 0.5em; + left: 0.5em; + width: 1.5em; + height: 1.5em; + background-color: $black; + color: $white; + text-align: center; + line-height: 1.5em; + font-size: 1.5em; + background-color: $colour-3; + @include _(border-radius, 50%); + @include default-box-shadow(top, 2); + } +} + +.warning-info { + display: inline-block; + margin: 0; + vertical-align: bottom; } ::-webkit-resizer { @@ -569,7 +703,7 @@ form { @include _(order, 1); } - form { + form, .register { background-color: $colour-2; } @@ -596,6 +730,10 @@ form { @include after { display: none; } + + .title { + font-size: 0.8em; + } } button { @@ -613,11 +751,130 @@ form { form { display: inline; } + + .strlen-12, .strlen-13 { + .title { + font-size: 0.7em; + } + } + + .strlen-14, .strlen-15 { + .title { + font-size: 0.6333em; + } + } } .logo { font-size: 5em; } + + .user-nav-bar { + position: absolute; + right: 0; + width: auto; + text-align: right; + top: 1em; + font-size: 0.9em; + width: 100%; + padding-left: 13em; + + a { + display: block; + padding: 0.5em 0; + color: $black; + z-index: 1; + + &:after { + display: none; + } + } + + .my-account { + overflow: hidden; + text-overflow: ellipsis; + } + } +} + +#registration-steps { + ul { + display: block; + position: relative; + list-style: none; + padding: 0; + margin-top: -1em; + overflow: hidden; + text-align: center; + @include default-box-shadow(top, 2); + @include _(border-radius, 0.15em); + @include clearfix; + } + + li { + position: relative; + display: block; + @include font-family(secondary); + color: $white; + background-color: #BBB; + border: 0; + padding: 1em 1.5em; + font-size: 1.25em; + outline: 0; + cursor: default; + @include _(text-stroke, 1px rgba(0, 0, 0, 0.25)); + + @include after { + content: ''; + position: absolute; + z-index: 1; + top: 2.25em; + left: 0; + right: 0; + width: 1.25em; + height: 1.25em; + margin: auto; + border: 0 solid; + border-width: 0.1em 0.1em 0 0; + background-color: inherit; + @include _(transform, rotate(135deg)); + @include _(transition, 'transform 200ms ease-in-out, background-color 200ms ease-in-out'); + @include _(box-shadow, 0.2em -0.1em 0.8em -0.4em #000); + } + + &.enabled { + background-color: $colour-5; + } + + &.current { + background-color: lighten(desaturate($colour-5, 33%), 15%); + } + + &:last-child { + padding-right: 1.5em; + + @include after { + display: none; + } + } + } + + a { + color: inherit; + + @include before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + @include after { + display: none; + } + } } #main { @@ -632,7 +889,7 @@ form { &.supplementary { margin: rems(1) 7.5%; overflow: hidden; - border-radius: 0.33em; + @include _(border-radius, 0.33em); border: 0.1rem solid #DDD; background-color: #F7F7F7; @include default-box-shadow(top, 2); @@ -690,7 +947,7 @@ a.logo { fill: $colour-1; } svg.bb-icon-logo-text { - fill: #333; + fill: $black; } .logo:hover svg.bb-icon-logo { @@ -730,9 +987,13 @@ a.logo { font-size: 5vw; margin: 1em auto 2em; - h1, h3 { + h1, h2 { margin: 0; } + + h2 { + font-size: 1.15em; + } } } @@ -850,6 +1111,7 @@ $header-tilt: 8deg; footer { display: flex; flex-flow: row wrap; + font-size: 4.1vw; } .github, @@ -869,7 +1131,7 @@ $header-tilt: 8deg; } .facebook { - flex: 0; + flex: none; margin: 0.5em; a { @@ -891,7 +1153,7 @@ $header-tilt: 8deg; .icons { width: 1.5em; height: 1.5em; - fill: #333; + fill: $black; vertical-align: middle; } } @@ -899,7 +1161,7 @@ $header-tilt: 8deg; .github { position: relative; background-color: $white; - border-radius: 0.25em; + @include _(border-radius, 0.25em); border: 0.1em solid #DDD; padding: 0.5em 0.5em 0.5em 2em; text-align: center; @@ -942,7 +1204,44 @@ $header-tilt: 8deg; } p { - font-size: 1.4em; + font-size: 1.333em; + } + + form { + &.flex-form { + display: flex; + align-items: flex-start; + + .input-field { + flex: 1; + } + + button, .button { + margin-left: 1em; + height: 2.6em; + } + } + } + + .flex-column { + display: flex; + align-items: flex-start; + margin-top: 1em; + + p:first-child { + margin-top: 0; + } + + .stretch-item { + flex: 1; + margin-right: 1em; + } + } + + .flex-form, .flex-column { + button, .button { + width: auto; + } } #main-nav { @@ -966,11 +1265,12 @@ $header-tilt: 8deg; .nav a, .nav button { width: auto; + min-width: 3.5em; overflow: visible; - margin-left: 0.75em; + margin-left: 0.725em; line-height: 3em; padding: 0 0 1em; - color: #333; + color: $black; @include _(box-shadow, none); @include _(text-shadow, none); @@ -1020,17 +1320,17 @@ $header-tilt: 8deg; } .nav a { - &.policy { + &[class] { background-color: transparent; + } + &.policy { @include before-and-after { background-color: $colour-5; } } &.about { - background-color: transparent; - @include before-and-after { background-color: $colour-3; } @@ -1045,6 +1345,42 @@ $header-tilt: 8deg; @include _(transform, none); } } + + .user-nav-bar { + top: 0; + left: 0; + right: auto; + width: auto; + float: none; + text-align: left; + background-color: #EEE; + @include _(border-radius, 0 0 0.5em 0); + font-size: 0.8em; + padding: 0 1em 0 0.5em; + @include default-box-shadow(top, 2); + + a { + display: inline-block; + padding: 0.25em 0; + + &:hover { + color: lighten($black, 25%); + } + } + + .my-account { + max-width: none; + overflow: visible; + + @include after { + content: '|'; + display: inline; + @include _(opacity, 0.25); + position: static; + margin: 0 0.5em; + } + } + } } #banner { @@ -1065,6 +1401,45 @@ $header-tilt: 8deg; } } + #registration-steps { + ul { + display: inline-block; + text-align: left; + margin-top: -4em; + } + + li { + display: inline-block; + float: left; + + @include after { + content: ''; + top: 1.125em; + left: auto; + right: -0.25em; + @include _(transform, rotate(45deg)); + } + + &.enabled:hover, + &.enabled:hover ~ li { + @include after { + @include _(transform, rotate(-135deg)); + background-color: transparent; + } + } + } + } + + ul.warnings { + li { + margin: 1em 4em; + } + } + + .warning-info { + margin-left: 1em; + } + #main { clear: right; @@ -1139,16 +1514,20 @@ $header-tilt: 8deg; } #footer { + footer { + font-size: 1em; + } + .github { flex: none; flex-basis: auto; bottom: 0.5em; float: left; margin-right: 1em; - margin-left: 0; + margin-left: 1em; } .facebook { - margin: 0 1em 0 0; + margin: 0; } ul.locales { flex: none; @@ -1156,22 +1535,55 @@ $header-tilt: 8deg; flex-grow: 1; } } + + .check-box-field, + .radio-button-field { + display: flex; + + label { + flex: 1; + border: 0.1em solid; + border-left: 0; + text-align: left; + } + + input:first-child + label { + border: 0.1em solid; + } + } } @include keyframes(fade-out) { - to { @include _(opacity, 0.25); } + to { + @include _(opacity, 0.25); + } } -:focus { +body :focus, +input[type="submit"]:focus, +.check-box-field input:focus + label, +.radio-button-field input:focus + label { @include _(animation, fade-out ease-in-out 500ms infinite alternate both); } +input:focus, +textarea:focus, +.textarea { + @include _(animation, none); +} + @include breakpoint(large) { #main-nav { .nav { font-size: 1.9em; margin-top: 0; } + + .user-nav-bar { + @include _(border-radius, 0 0 0.5em 0.5em); + padding: 0 1em; + font-size: 1em; + } } #banner { @@ -1295,7 +1707,7 @@ $header-tilt: 8deg; height: 1.9em; border: 0; @include default-box-shadow(top); - background-color: #333; + background-color: $black; color: $colour-3; vertical-align: bottom; } @@ -1406,7 +1818,7 @@ $header-tilt: 8deg; .info { position: fixed; background-color: $white; - color: #333; + color: $black; left: auto; right: auto; top: auto; @@ -1431,8 +1843,10 @@ $header-tilt: 8deg; .conferences-register .policy-agreement { padding: 0.25em 1em; - background-color: #F8F8F8; - @include default-box-shadow(top, 2, true); + margin: 0 0 3em; + border: 0.1em solid #DDD; + @include _(border-radius, 0.25em); + @include default-box-shadow(top, 2); } body.policy .policy-agreement ul { @@ -1462,7 +1876,7 @@ body.policy .policy-agreement ul { height: 2em; line-height: 2em; text-align: center; - border-radius: 50%; + @include _(border-radius, 50%); } } @@ -1513,12 +1927,34 @@ body.policy .policy-agreement ul { .facilitators { display: inline-block; + .facilitator { + margin: 0 0 0.5em 1.25em; + } + + .name { + position: relative; + font-weight: bold; + + @include before { + content: ''; + position: absolute; + width: 0.3em; + height: 0.3em; + background-color: #333; + border-radius: 50%; + left: -0.75em; + top: 0.5em; + @include _(opacity, 0.5); + } + } + .name, .role { display: inline; } .role { color: #666; + white-space: nowrap; @include before { content: '('; @@ -1544,7 +1980,7 @@ body.policy .policy-agreement ul { p { padding: 0 1rem 0.5rem; font-size: 1em; - color: #333; + color: $black; } h4 { @@ -1619,7 +2055,7 @@ html[data-lingua-franca-example="html"] { float: left; padding: 0.25em 2em 0 1em; margin: 0.25em 1em; - border-radius: 0.5em 0.5em 0 0; + @include _(border-radius, 0.5em 0.5em 0 0); color: #444; @include font-family(secondary); @@ -1722,14 +2158,23 @@ html[data-lingua-franca-example="html"] { padding: 0 2em; .workshop-description { - font-size: 0.75em; + max-height: 10em; + padding: 1em; + overflow: hidden; } - li { - border-bottom: 1px dashed #CCC; + > li { + margin-bottom: 1em; + border: 0.2em solid; + @include _(border-radius, 0.25em); - &:last-child { - border-bottom: 0; + > a { + display: block; + color: inherit; + + @include after { + display: none; + } } ul { @@ -1740,6 +2185,41 @@ html[data-lingua-franca-example="html"] { li { border: 0; } + + &:hover { + @include default-box-shadow; + } + + &.interested { + .workshop-interest { + background-color: rgba($colour-5, 0.5); + } + } + + &.mine { + .workshop-interest { + background-color: lighten($colour-1, 25%); + background-color: rgba($colour-1, 0.5); + } + } + } + + .title { + margin: 0; + background-color: $black; + color: #FFF; + padding: 0.25em 0.5em 0.5em; + } + + p { + margin-top: 0; + font-size: 1em; + } + + .workshop-interest { + padding: 0.5em 1em; + font-weight: bold; + background-color: #EEE; } } @@ -1753,7 +2233,7 @@ html[data-lingua-franca-example="html"] { button { font-size: 0.9em; margin: 1em; - background-color: $colour-5; + background-color: $colour-1; &.delete { background-color: $colour-4; @@ -1793,18 +2273,9 @@ html[data-lingua-franca-example="html"] { } #main form.add-facilitator { - text-align: left; - margin-top: 0; - button { + height: 2.4em; font-size: 0.9em; - float: right; - } - - .email-field { - width: 80%; - font-size: 0.9em; - float: left; } } @@ -1822,7 +2293,7 @@ html[data-lingua-franca-example="html"] { .conflict-score { padding: 0.25em; background-color: $colour-3; - color: #333; + color: $black; } .all-workshops, .all-events { list-style: none; @@ -1860,7 +2331,7 @@ html[data-lingua-franca-example="html"] { .warnings { background-color: $colour-3; - color: #333; + color: $black; padding: 0.5em; margin-top: 0.5em; list-style: none; diff --git a/app/assets/stylesheets/_editor.scss b/app/assets/stylesheets/_editor.scss new file mode 100644 index 0000000..1b7b3bc --- /dev/null +++ b/app/assets/stylesheets/_editor.scss @@ -0,0 +1,212 @@ +$bumbleberry-no-markup: true; + +@import "bumbleberry"; +@import "settings"; + +$white: #FFF !default; +$black: #333 !default; + +.pen-icon { + font-size: 16px; + line-height: 40px; + min-width: 20px; + font-style: normal; + display: block; + float: left; + padding: 0 10px; + height: 2.2em; + overflow: hidden; + color: $white; + text-align: center; + cursor: pointer; + @include _(user-select, none); +} + +.pen { + position: relative; + + &:focus { + outline: none; + } +} + +.pen-menu, .pen-input { + font-size: 14px; + line-height:1; +} + +.pen-input { + display :none; + position: absolute; + width: 100%; + left: 0; + top: 0; + height: 2.6em; + line-height: 20px; + background-color: $black; + color: $white; + border: none; + text-align: center; + + &:focus { + outline: none; + } +} + +.pen-textarea { + display: block; + background: $white; + padding: 20px; +} +.pen textarea { + font-size: 14px; + border: none; + background: none; + width: 100%; + _height: 200px; + min-height: 200px; + resize: none; +} + +.pen-menu-below { + @include after { + top: -11px; + display: block; + @include _(transform, rotate(180deg)); + } +} + +.pen-menu { + white-space: nowrap; + box-shadow:1px 2px 3px -2px #222; + background: #333; + background-image: linear-gradient(to bottom, #222, #333); + opacity: 0.9; + position: fixed; + height: 5.2em; + border: 1px solid #333; + border-radius: 0.5em; + display:none; + z-index: 1000; + + @include after { + content: " "; + top: 100%; + border: solid transparent; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(51, 51, 51, 0); + border-top-color: #333; + border-width: 6px; + left: 50%; + margin-left: -6px; + } + + [class^="icon-"], [class*=" icon-"] { + @include before { + speak: none; + display: inline-block; + width: 1em; + margin-right: .2em; + text-align: center; + font-variant: normal; + text-transform: none; + line-height: 1em; + margin-left: .2em; + vertical-align: text-top; + } + } + + .pen-icon { + &:hover { + background:#000; + } + &.active { + background: #000; + @include _(box-shadow, #{inset 2px 2px 4px #000, 0 0 0.25em #000}); + } + } + + .icon-h1, + .icon-h2, + .icon-h3, + .icon-h4, + .icon-h5, + .icon-h6 { + @include before { @include font-family(secondary); } + } + .icon-h1 { + @include before { content: 'H1'; } + } + .icon-h2 { + @include before { content: 'H2'; } + } + .icon-h3 { + @include before { content: 'H1'; } + } + .icon-h4 { + @include before { content: 'H2'; } + } + .icon-h5 { + @include before { content: 'H5'; } + } + .icon-h6 { + @include before { content: 'H6'; } + } + .icon-bold { + border-bottom-left-radius: 0.5em; + clear: left; + @include before { content: 'B'; font-weight: bold; } + } + .icon-italic { + @include before { content: 'I'; font-style: italic;; } + } + .icon-underline { + @include before { content: 'U'; text-decoration: underline; } + } + .icon-p { + @include font-family(secondary); + @include before { content: '¶'; } + } + .icon-blockquote { + @include before { + background-image: inline-svg-image('quote.svg'); + } + } + .icon-insertorderedlist { + border-top-right-radius: 0.5em; + + @include before { + background-image: inline-svg-image('ol.svg'); + } + } + .icon-insertunorderedlist { @include before { + background-image: inline-svg-image('ul.svg'); + } } + + .icon-strikethrough { @include before { content: 'S'; text-decoration: line-through; } } + + .icon-insertunorderedlist, + .icon-insertorderedlist, + .icon-blockquote, + .icon-insertimage, + .icon-createlink { + @include before { + content: ''; + background-size: contain; + height: 1em; + } + } + .icon-createlink { + @include before { + background-image: inline-svg-image('link.svg'); + } + } + .icon-insertimage { + @include before { + background-image: inline-svg-image('img.svg'); + } + } +} diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index 37fcd6e..2000476 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -13,6 +13,9 @@ $colour-4: #D89E59; // orange $colour-5: #02CA9E; // green $white: #FFFEFE; +$black: #333; + +$link-colour: darken($colour-1, 13%); @mixin default-box-shadow($direction: top, $distance: 1, $inset: false, $additional-shadow: false) { @if capable_of(box-shadow) { diff --git a/app/assets/stylesheets/bumbleberry-settings.json b/app/assets/stylesheets/bumbleberry-settings.json index 407ddb7..5beaf14 100644 --- a/app/assets/stylesheets/bumbleberry-settings.json +++ b/app/assets/stylesheets/bumbleberry-settings.json @@ -1,11 +1,15 @@ { - "stylesheets": ["application", "translations", "email-example"], + "stylesheets": ["application", "editor"], "precompile": { "test": { "chrome": ["50"] }, "development": { + "and_chr": ["50"], "chrome": ["50"], + "edge": ["13"], + "firefox": ["44"], + "ie": ["11"], "ios_saf": ["8", "9"] } }, diff --git a/app/assets/stylesheets/user-mailer.scss b/app/assets/stylesheets/user-mailer.scss new file mode 100644 index 0000000..048c1fa --- /dev/null +++ b/app/assets/stylesheets/user-mailer.scss @@ -0,0 +1,183 @@ +@import "settings"; + +body { + width: 100% !important; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 0; + padding: 0; +} + +.ExternalClass { + width: 100%; + line-height: 100%; + + p, span, font, td, div { + line-height: 100%; + } +} + +#backgroundTable { + margin: 0; + padding: 0; + width: 100% !important; + line-height: 100% !important; +} + +img { + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + + a & { + border: none; + } +} + +.image_fix { + display: block; + max-width: 100%; +} + +a { + color: $colour-1; + + #outlook & { + padding: 0; + } + + &:link { color: $colour-1; } + &:visited { color: $colour-1; } + &:hover { color: $colour-5; } + + @media only screen and (max-device-width: 480px) { + &[href^="tel"], &[href^="sms"] { + text-decoration: none; + color: $black; + pointer-events: none; + cursor: default; + } + + .mobile_link &[href^="tel"], .mobile_link &[href^="sms"] { + text-decoration: default; + color: $colour-1 !important; + pointer-events: auto; + cursor: default; + } + } +} + +p { + margin: 1em; + color: $black !important; +} + +h1, h2, h3, h4, h5, h6 { + color: $black !important; + + a { + color: $colour-1 !important; + + &:active { + color: $colour-4 !important; + } + + &:visited { + color: $colour-2 !important; + } + } + +} + +table { + border-collapse: collapse; + max-width: 512px; + min-width: 280px; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + + td { + border-collapse: collapse; + } +} + +table#bb_full_width, +table#ecxbb_full_width { + + &, table { + max-width: none; + width: 100%; + } +} + +.diff, .ecxdiff { + margin: 1em 0 5em 1em; + overflow: auto; + + ul { + list-style: none; + padding: 0; + margin: 0; + position: relative; + } + + li { + width: 40%; + display: inline-block; + padding: 0.5em; + margin: 0; + background-color: lighten($colour-3, 33%); + border: 2px solid #DDD; + white-space: nowrap; + + &:nth-child(odd) { + margin-right: -3px; + float: left; + } + + &:nth-child(even) { + background-color: lighten($colour-5 , 33%); + } + } +} + +// Buttons +h3 { + b { + padding: 10px 20px; + line-height: 50px; + + a, a:visited { + color: #FFF !important; + background-color: $colour-5; + text-decoration: none !important; + border-radius: 4px; + padding: 10px 15px; + margin-left: 20px; + border-bottom: 3px solid darken($colour-5, 10%); + -webkit-box-shadow: 0 0.5em 1.5em -0.75em #000; + box-shadow: 0 0.5em 1.5em -0.75em #000; + cursor: pointer !important; + } + + a:hover { + background-color: darken($colour-5, 10%); + } + } +} + +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { + a[href^="tel"], a[href^="sms"] { + text-decoration: none; + color: $colour-1; + pointer-events: none; + cursor: default; + + .mobile_link & { + text-decoration: default; + color: $colour-1 !important; + pointer-events: auto; + cursor: default; + } + } +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 08a2b76..9517fc7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -18,6 +18,7 @@ class ApplicationController < LinguaFrancaApplicationController def capture_page_info # set the translator to the current user if we're logged in I18n.config.translator = current_user + I18n.config.callback = self # get the current confernece and set it globally @conference = Conference.order("start_date DESC").first @@ -78,7 +79,8 @@ class ApplicationController < LinguaFrancaApplicationController end def do_404 - params[:action] = 'error-403' + params[:_original_action] = params[:action] + params[:action] = 'error-404' render 'application/404', status: 404 end @@ -88,6 +90,7 @@ class ApplicationController < LinguaFrancaApplicationController def do_403(template = nil) @template = template + params[:_original_action] = params[:action] params[:action] = 'error-403' render 'application/permission_denied', status: 403 end @@ -143,13 +146,12 @@ class ApplicationController < LinguaFrancaApplicationController end user = User.find_by_email(params[:email]) - if !user + unless user # not really a good UX so we should fix this later #do_404 #return user = User.new(:email => params[:email]) user.save! - user = User.find_by_email(params[:email]) end # genereate the confirmation, send the email and show the 403 @@ -218,4 +220,28 @@ class ApplicationController < LinguaFrancaApplicationController auto_login(u) end + def on_translation_change(object, data, locale, translator_id) + translator = User.find(translator_id) + mailer = "#{object.class.table_name.singularize}_translated" + + object.get_translators(data, locale).each do |id, user| + if user.id != current_user.id && user.id != translator_id + UserMailer.send_mail mailer do + { :args => [object, data, locale, user, translator] } + end + end + end + end + + def on_translatable_content_change(object, data) + mailer = "#{object.class.table_name.singularize}_original_content_changed" + + object.get_translators(data).each do |id, user| + if user.id != current_user.id + UserMailer.send_mail mailer do + { :args => [object, data, user, current_user] } + end + end + end + end end diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 4b9f8a1..372336a 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -514,150 +514,219 @@ class ConferencesController < ApplicationController end def register - is_post = request.post? || session[:registration_step] + # is_post = request.post? || session[:registration_step] set_conference - #if !@this_conference.registration_open - # do_404 - # return - #end - - set_conference_registration - @register_template = nil if logged_in? - unless @this_conference.registration_open || @registration - do_404 - return - end - # if the user is logged in start them off on the policy - # page, unless they have already begun registration then - # start them off with questions - @register_template = @registration ? (@registration.registration_fees_paid ? :done : :payment) : :policy + set_or_create_conference_registration @name = current_user.firstname # we should phase out last names @name += " #{current_user.lastname}" if current_user.lastname + @name ||= current_user.username + @is_host = @this_conference.host? current_user + + steps = registration_steps + return do_404 unless steps.present? + else + @register_template = :confirm_email end - # process data from the last view - case (params[:button] || '').to_sym - when :confirm_email - @register_template = :email_sent if is_post - when :policy - @register_template = :questions if is_post - when :save - if is_post - if (new_registration = (!@registration)) - @registration = ConferenceRegistration.new - end + @errors = {} + @warnings = [] + form_step = params[:button] ? params[:button].to_sym : nil - @registration.conference_id = @this_conference.id - @registration.user_id = current_user.id - @registration.is_attending = 'yes' - @registration.is_confirmed = true - @registration.city = params[:location] - @registration.arrival = params[:arrival] - @registration.languages = params[:languages].keys.to_json - @registration.departure = params[:departure] - @registration.housing = params[:housing] - @registration.bike = params[:bike] - @registration.food = params[:food] - @registration.allergies = params[:allergies] - @registration.other = params[:other] - @registration.save - - current_user.firstname = params[:name].squish - current_user.lastname = nil - current_user.save - - if new_registration - UserMailer.send_mail :registration_confirmation do - { - :args => @registration - } + if form_step + if form_step.to_s =~ /^prev_(.+)$/ + @register_template = steps[steps.find_index($1.to_sym) - 1] + else + + case form_step + when :confirm_email + return do_confirm + when :contact_info + if params[:name].present? && params[:name].gsub(/[\s\W]/, '').present? + current_user.firstname = params[:name].squish + current_user.lastname = nil + else + @errors[:name] = :empty end - end - @register_template = @registration.registration_fees_paid ? :done : :payment - end - when :payment - if is_post && @registration - amount = params[:amount].to_f + if params[:location].present? && params[:location].gsub(/[\s\W]/, '').present? && (l = Geocoder.search(params[:location])).present? + corrected = view_context.location(l.first) - if amount > 0 - @registration.payment_confirmation_token = ENV['RAILS_ENV'] == 'test' ? 'token' : Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) - @registration.save - - host = "#{request.protocol}#{request.host_with_port}" - response = PayPal!.setup( - PayPalRequest(amount), - register_paypal_confirm_url(@this_conference.slug, :paypal_confirm, @registration.payment_confirmation_token), - register_paypal_confirm_url(@this_conference.slug, :paypal_cancel, @registration.payment_confirmation_token) - ) - if ENV['RAILS_ENV'] != 'test' - redirect_to response.redirect_uri + if corrected.present? + @registration.city = corrected + if params[:location].gsub(/[\s,]/, '').downcase != @registration.city.gsub(/[\s,]/, '').downcase + @warnings << view_context._('warnings.messages.location_corrected',"Your location was corrected from \"#{params[:location]}\" to \"#{corrected}\". If this doesn't reflect your intended location, you can change this again in the contact info step.", vars: {original: params[:location], corrected: corrected}) + end + else + @errors[:location] = :unknown + end + else + @errors[:location] = :empty end - return - end - @register_template = :done - end - when :paypal_confirm - if @registration && @registration.payment_confirmation_token == params[:confirmation_token] - if ENV['RAILS_ENV'] == 'test' - @amount = YAML.load(@registration.payment_info)[:amount] - else - @amount = PayPal!.details(params[:token]).amount.total - # testing this does't work in test but it works in devo and prod - @registration.payment_info = {:payer_id => params[:PayerID], :token => params[:token], :amount => @amount}.to_yaml + if params[:languages].present? + current_user.languages = params[:languages].keys + else + @errors[:languages] = :empty + end + + current_user.save! unless @errors.present? end - @amount = (@amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') + if @errors.present? + @register_template = form_step + else + unless @registration.nil? + @register_template = steps[steps.find_index(form_step) + 1] + + # have we reached a new level? + unless @registration.steps_completed.include? form_step.to_s + @registration.steps_completed ||= [] + @registration.steps_completed << form_step + + # workshops is the last step + if @register_template == :workshops + UserMailer.send_mail :registration_confirmation do + { + :args => @registration + } + end + end + end - @registration.save! - @register_template = :paypal_confirm - end - when :paypal_confirmed - info = YAML.load(@registration.payment_info) - @amount = nil - status = nil - if ENV['RAILS_ENV'] == 'test' - status = info[:status] - @amount = info[:amount] - else - paypal = PayPal!.checkout!(info[:token], info[:payer_id], PayPalRequest(info[:amount])) - status = paypal.payment_info.first.payment_status - @amount = paypal.payment_info.first.amount.total - end - if status == 'Completed' - @registration.registration_fees_paid = @amount - @registration.save! - @register_template = :done - else - @register_template = :payment - end - when :paypal_cancel - if @registration - @registration.payment_confirmation_token = nil - @registration.save - @register_template = :payment + @registration.save! + end + end end - when :register - @register_template = :questions end - if @register_template == :payment && !@this_conference.paypal_username - @register_template = :done - end + @register_template ||= (params[:step] || current_step).to_sym - # don't let the user edit registration if registration is closed - if !@conference.registration_open && @register_template == :questions - @register_template = :done - end + return redirect_to register_path(@this_conference.slug) if + logged_in? && @register_template != current_step && + !@registration.steps_completed.include?(@register_template.to_s) + + # process data from the last view + # case (params[:button] || '').to_sym + # when :confirm_email + # @register_template = :email_sent if is_post + # when :policy + # @register_template = :questions if is_post + # when :save + # if is_post + # new_registration = !@registration + # @registration = ConferenceRegistration.new if new_registration + + # @registration.conference_id = @this_conference.id + # @registration.user_id = current_user.id + # @registration.is_attending = 'yes' + # @registration.is_confirmed = true + # @registration.city = params[:location] + # @registration.arrival = params[:arrival] + # @registration.languages = params[:languages].keys.to_json + # @registration.departure = params[:departure] + # @registration.housing = params[:housing] + # @registration.bike = params[:bike] + # @registration.food = params[:food] + # @registration.allergies = params[:allergies] + # @registration.other = params[:other] + # @registration.save + + # current_user.firstname = params[:name].squish + # current_user.lastname = nil + # current_user.save + + # if new_registration + # UserMailer.send_mail :registration_confirmation do + # { + # :args => @registration + # } + # end + # end + + # @register_template = @registration.registration_fees_paid ? :done : :payment + # end + # when :payment + # if is_post && @registration + # amount = params[:amount].to_f + + # if amount > 0 + # @registration.payment_confirmation_token = ENV['RAILS_ENV'] == 'test' ? 'token' : Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) + # @registration.save + + # host = "#{request.protocol}#{request.host_with_port}" + # response = PayPal!.setup( + # PayPalRequest(amount), + # register_paypal_confirm_url(@this_conference.slug, :paypal_confirm, @registration.payment_confirmation_token), + # register_paypal_confirm_url(@this_conference.slug, :paypal_cancel, @registration.payment_confirmation_token) + # ) + # if ENV['RAILS_ENV'] != 'test' + # redirect_to response.redirect_uri + # end + # return + # end + # @register_template = :done + # end + # when :paypal_confirm + # if @registration && @registration.payment_confirmation_token == params[:confirmation_token] + + # if ENV['RAILS_ENV'] == 'test' + # @amount = YAML.load(@registration.payment_info)[:amount] + # else + # @amount = PayPal!.details(params[:token]).amount.total + # # testing this does't work in test but it works in devo and prod + # @registration.payment_info = {:payer_id => params[:PayerID], :token => params[:token], :amount => @amount}.to_yaml + # end + + # @amount = (@amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') + + # @registration.save! + # @register_template = :paypal_confirm + # end + # when :paypal_confirmed + # info = YAML.load(@registration.payment_info) + # @amount = nil + # status = nil + # if ENV['RAILS_ENV'] == 'test' + # status = info[:status] + # @amount = info[:amount] + # else + # paypal = PayPal!.checkout!(info[:token], info[:payer_id], PayPalRequest(info[:amount])) + # status = paypal.payment_info.first.payment_status + # @amount = paypal.payment_info.first.amount.total + # end + # if status == 'Completed' + # @registration.registration_fees_paid = @amount + # @registration.save! + # @register_template = :done + # else + # @register_template = :payment + # end + # when :paypal_cancel + # if @registration + # @registration.payment_confirmation_token = nil + # @registration.save + # @register_template = :payment + # end + # when :register + # @register_template = :questions + # end + + # if @register_template == :payment && !@this_conference.paypal_username + # @register_template = :done + # end + + # # don't let the user edit registration if registration is closed + # if !@conference.registration_open && @register_template == :questions + # @register_template = :done + # end # prepare data for the next view case @register_template @@ -680,12 +749,17 @@ class ConferencesController < ApplicationController @languages = JSON.parse(@registration.languages).map &:to_sym end when :workshops - @my_workshops = [1,2,3,4].map { |i| - { - :title => (Forgery::LoremIpsum.sentence({:random => true}).gsub(/\.$/, '').titlecase), - :info => (Forgery::LoremIpsum.sentences(rand(1...5), {:random => true})) - } - } + @page_title = 'articles.conference_registration.headings.Workshops' + @workshops = Workshop.where(conference_id: @this_conference.id) + @my_workshops = Workshop.joins(:workshop_facilitators).where( + workshop_facilitators: { user_id: current_user.id }, + conference_id: @this_conference.id + ) + @workshops_in_need = Workshop.where(conference_id: @this_conference.id, needs_facilitators: true) + when :contact_info + @page_title = 'articles.conference_registration.headings.Contact_Info' + when :policy + @page_title = 'articles.conference_registration.headings.Policy_Agreement' when :done @amount = ((@registration.registration_fees_paid || 0) * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') end @@ -709,7 +783,7 @@ class ConferencesController < ApplicationController session[:registration_step] = 'confirm' redirect_to action: 'register' else - do_404 + return do_404 end end @@ -734,7 +808,7 @@ class ConferencesController < ApplicationController redirect_to action: 'register' end else - do_404 + return do_404 end end @@ -754,7 +828,7 @@ class ConferencesController < ApplicationController # session[:registration_step] = 'paypal-confirmed' # redirect_to action: 'register' # else - # do_404 + # return do_404 # end # end @@ -825,32 +899,67 @@ class ConferencesController < ApplicationController set_conference set_conference_registration @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless @workshop + return do_404 unless @workshop + + @translations_available_for_editing = [] + I18n.backend.enabled_locales.each do |locale| + @translations_available_for_editing << locale if @workshop.can_translate?(current_user, locale) + end + @page_title = 'page_titles.conferences.View_Workshop' + render 'workshops/show' end def create_workshop set_conference set_conference_registration - @languages = [] + @workshop = Workshop.new + @languages = [I18n.locale.to_sym] @needs = [] + @page_title = 'page_titles.conferences.Create_Workshop' render 'workshops/new' end + def translate_workshop + @is_translating = true + @translation = params[:locale] + @page_title = 'page_titles.conferences.Translate_Workshop' + @page_title_vars = { language: view_context.language_name(@translation) } + + edit_workshop + end + def edit_workshop set_conference set_conference_registration @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless @workshop + + return do_404 unless @workshop.present? + + @page_title ||= 'page_titles.conferences.Edit_Workshop' + @can_edit = @workshop.can_edit?(current_user) - do_403 unless @can_edit || @workshop.can_translate?(current_user, I18n.locale) - @title = @workshop.title - @info = @workshop.info + + @is_translating ||= false + if @is_translating + return do_404 if @translation.to_s == @workshop.locale.to_s || !I18n.backend.enabled_locales.include?(@translation.to_s) + return do_403 unless @workshop.can_translate?(current_user, @translation) + + @title = @workshop._title(@translation) + @info = @workshop._info(@translation) + else + return do_403 unless @can_edit + + @title = @workshop.title + @info = @workshop.info + end + @needs = JSON.parse(@workshop.needs || '[]').map &:to_sym @languages = JSON.parse(@workshop.languages || '[]').map &:to_sym @space = @workshop.space.to_sym if @workshop.space @theme = @workshop.theme.to_sym if @workshop.theme @notes = @workshop.notes + render 'workshops/new' end @@ -859,15 +968,8 @@ class ConferencesController < ApplicationController set_conference_registration @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - if !@workshop - do_404 - return - end - - if !@workshop.can_delete?(current_user) - do_403 - return - end + return do_404 unless @workshop.present? + return do_403 unless @workshop.can_delete?(current_user) if request.post? if params[:button] == 'confirm' @@ -876,11 +978,9 @@ class ConferencesController < ApplicationController @workshop.destroy end - redirect_to workshops_url - return + return redirect_to workshops_url end - redirect_to edit_workshop_url(@this_conference.slug, @workshop.id) - return + return redirect_to edit_workshop_url(@this_conference.slug, @workshop.id) end render 'workshops/delete' @@ -890,30 +990,57 @@ class ConferencesController < ApplicationController set_conference set_conference_registration - if params[:workshop_id] + if params[:button].to_sym != :save + if params[:workshop_id].present? + return redirect_to view_workshop_url(@this_conference.slug, params[:workshop_id]) + end + return redirect_to register_step_path(@this_conference.slug, 'workshops') + end + + if params[:workshop_id].present? workshop = Workshop.find(params[:workshop_id]) - do_404 unless workshop + return do_404 unless workshop.present? + can_edit = workshop.can_edit?(current_user) else workshop = Workshop.new(:conference_id => @this_conference.id) workshop.workshop_facilitators = [WorkshopFacilitator.new(:user_id => current_user.id, :role => :creator)] + can_edit = true end - can_edit = workshop.can_edit?(current_user) - do_403 unless can_edit || workshop.can_translate?(current_user, I18n.locale) + title = params[:title] + info = params[:info].gsub(/^\s*(.*?)\s*$/, '\1') - workshop.title = params[:title] - workshop.info = params[:info] + if params[:translation].present? && workshop.can_translate?(current_user, params[:translation]) + old_title = workshop._title(params[:translation]) + old_info = workshop._info(params[:translation]) - if can_edit - # dont allow translators to edit these fields - workshop.languages = (params[:languages] || {}).keys.to_json - workshop.needs = (params[:needs] || {}).keys.to_json - workshop.theme = params[:theme] == 'other' ? params[:other_theme] : params[:theme] - workshop.space = params[:space] - workshop.notes = params[:notes] + do_save = false + + unless title == old_title + workshop.set_column_for_locale(:title, params[:translation], title, current_user.id) + do_save = true + end + unless info == old_info + workshop.set_column_for_locale(:info, params[:translation], info, current_user.id) + do_save = true + end + + # only save if the text has changed, if we want to make sure only to update the translator id if necessary + workshop.save_translations if do_save + elsif can_edit + workshop.title = title + workshop.info = info + workshop.languages = (params[:languages] || {}).keys.to_json + workshop.needs = (params[:needs] || {}).keys.to_json + workshop.theme = params[:theme] == 'other' ? params[:other_theme] : params[:theme] + workshop.space = params[:space] + workshop.notes = params[:notes] + workshop.needs_facilitators = params[:needs_facilitators].present? + workshop.save + else + return do_403 end - workshop.save redirect_to view_workshop_url(@this_conference.slug, workshop.id) end @@ -921,7 +1048,7 @@ class ConferencesController < ApplicationController set_conference set_conference_registration workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless workshop + return do_404 unless workshop # save the current state interested = workshop.interested? current_user @@ -939,8 +1066,8 @@ class ConferencesController < ApplicationController set_conference set_conference_registration @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless @workshop - do_403 if @workshop.facilitator?(current_user) || !current_user + return do_404 unless @workshop + return do_403 if @workshop.facilitator?(current_user) || !current_user render 'workshops/facilitate' end @@ -949,8 +1076,8 @@ class ConferencesController < ApplicationController set_conference set_conference_registration workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless workshop - do_403 if workshop.facilitator?(current_user) || !current_user + return do_404 unless workshop + return do_403 if workshop.facilitator?(current_user) || !current_user # create the request by making the user a facilitator but making their role 'requested' WorkshopFacilitator.create(user_id: current_user.id, workshop_id: workshop.id, role: :requested) @@ -968,8 +1095,8 @@ class ConferencesController < ApplicationController set_conference set_conference_registration @workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless @workshop - do_403 unless @workshop.requested_collaborator?(current_user) + return do_404 unless @workshop + return do_403 unless @workshop.requested_collaborator?(current_user) render 'workshops/facilitate_request_sent' end @@ -978,7 +1105,7 @@ class ConferencesController < ApplicationController set_conference set_conference_registration workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless workshop && current_user + return do_404 unless workshop && current_user user_id = params[:user_id].to_i action = params[:approve_or_deny].to_sym @@ -1019,7 +1146,7 @@ class ConferencesController < ApplicationController end end - do_403 + return do_403 end def add_workshop_facilitator @@ -1029,7 +1156,7 @@ class ConferencesController < ApplicationController set_conference_registration workshop = Workshop.find_by_id_and_conference_id(params[:workshop_id], @this_conference.id) - do_404 unless workshop && current_user + return do_404 unless workshop && current_user unless workshop.facilitator?(user) WorkshopFacilitator.create(user_id: user.id, workshop_id: workshop.id, role: :collaborator) @@ -1046,7 +1173,7 @@ class ConferencesController < ApplicationController def schedule set_conference - do_404 unless @this_conference.workshop_schedule_published || @this_conference.host?(current_user) + return do_404 unless @this_conference.workshop_schedule_published || @this_conference.host?(current_user) @events = Event.where(:conference_id => @this_conference.id) @locations = EventLocation.where(:conference_id => @this_conference.id) @@ -1057,7 +1184,7 @@ class ConferencesController < ApplicationController def edit_schedule set_conference set_conference_registration - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) @workshops = Workshop.where(:conference_id => @this_conference.id) @events = Event.where(:conference_id => @this_conference.id) @@ -1126,7 +1253,7 @@ class ConferencesController < ApplicationController def save_schedule set_conference - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) @days = Array.new start_day = @this_conference.start_date.strftime('%u').to_i @@ -1164,7 +1291,6 @@ class ConferencesController < ApplicationController session[:workshops][id] = { :start_time => @workshops[i].start_time, :end_time => @workshops[i].end_time, - :end_time => @workshops[i].end_time, :event_location_id => @workshops[i].event_location_id } end @@ -1189,7 +1315,6 @@ class ConferencesController < ApplicationController session[:events][id] = { :start_time => @events[i].start_time, :end_time => @events[i].end_time, - :end_time => @events[i].end_time, :event_location_id => @events[i].event_location_id } end @@ -1228,7 +1353,7 @@ class ConferencesController < ApplicationController def add_event set_conference set_conference_registration - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) render 'events/edit' end @@ -1236,22 +1361,22 @@ class ConferencesController < ApplicationController def edit_event set_conference set_conference_registration - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) @event = Event.find(params[:id]) - do_403 unless @event.conference_id == @this_conference.id + return do_403 unless @event.conference_id == @this_conference.id render 'events/edit' end def save_event set_conference - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) if params[:event_id] event = Event.find(params[:event_id]) - do_403 unless event.conference_id == @this_conference.id + return do_403 unless event.conference_id == @this_conference.id else event = Event.new(:conference_id => @this_conference.id) end @@ -1268,7 +1393,7 @@ class ConferencesController < ApplicationController def add_location set_conference set_conference_registration - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) render 'event_locations/edit' end @@ -1276,10 +1401,10 @@ class ConferencesController < ApplicationController def edit_location set_conference set_conference_registration - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) @location = EventLocation.find(params[:id]) - do_403 unless @location.conference_id == @this_conference.id + return do_403 unless @location.conference_id == @this_conference.id @amenities = JSON.parse(@location.amenities || '[]').map &:to_sym @@ -1288,12 +1413,12 @@ class ConferencesController < ApplicationController def save_location set_conference - do_404 unless @this_conference.host?(current_user) + return do_404 unless @this_conference.host?(current_user) if params[:location_id] location = EventLocation.find(params[:location_id]) - do_403 unless location.conference_id == @this_conference.id + return do_403 unless location.conference_id == @this_conference.id else location = EventLocation.new(:conference_id => @this_conference.id) end @@ -1313,6 +1438,61 @@ class ConferencesController < ApplicationController # redirect_to conferences_url, notice: 'Conference was successfully destroyed.' #end + helper_method :registration_steps + helper_method :current_registration_steps + helper_method :registration_complete? + + def registration_steps(conference = nil) + conference ||= @this_conference || @conference + { + pre: [:policy, :contact_info, :workshops], + open: [:policy, :contact_info, :questions, :payment, :workshops] + }[conference.registration_status] + end + + def required_steps(conference = nil) + # return the intersection of current steps and reuired steps + registration_steps(conference || @this_conference || @conference) & # current steps + [:policy, :contact_info, :questions] # all required steps + end + + def registration_complete?(registration = @registration) + completed_steps = registration.steps_completed || [] + required_steps(registration.conference).each do | step | + return false unless completed_steps.include?(step.to_s) + end + return true + end + + def current_registration_steps(registration = @registration) + return nil unless registration.present? + + steps = registration_steps(registration.conference) + current_steps = [] + disable_steps = false + completed_steps = registration.steps_completed || [] + steps.each do | step | + # disable the step if we've already found an incomplete step + enabled = !disable_steps + # record whether or not we've found an incomplete step + disable_steps ||= !completed_steps.include?(step.to_s) + + current_steps << { + name: step, + enabled: enabled + } + end + return current_steps + end + + def current_step(registration = @registration) + completed_steps = registration.steps_completed || [] + (registration_steps(registration.conference) || []).each do | step | + return step unless completed_steps.include?(step.to_s) + end + return registration_steps(registration.conference).last + end + private # Use callbacks to share common setup or constraints between actions. def set_conference @@ -1321,24 +1501,20 @@ class ConferencesController < ApplicationController def set_conference_registration @registration = logged_in? ? ConferenceRegistration.find_by(:user_id => current_user.id, :conference_id => @this_conference.id) : nil - @is_host = @this_conference.host?(current_user) - if @registration || @is_host - @submenu = { - register_path(@this_conference.slug) => 'registration.Registration', - workshops_path(@this_conference.slug) => 'registration.Workshops' - } - @submenu[schedule_path(@this_conference.slug)] = 'registration.Schedule' if @this_conference.workshop_schedule_published || @is_host - if @is_host - @submenu[edit_conference_path(@this_conference.slug)] = 'registration.Edit' - @submenu[stats_path(@this_conference.slug)] = 'registration.Stats' - @submenu[broadcast_path(@this_conference.slug)] = 'registration.Broadcast' - end - end + end + + def set_or_create_conference_registration + set_conference_registration + @registration ||= ConferenceRegistration.new( + conference: @this_conference, + user_id: current_user.id, + steps_completed: [] + ) end # Only allow a trusted parameter "white list" through. def conference_params - params.require(:conference).permit(:title, :slug, :start_date, :end_date, :info, :poster, :cover, :workshop_schedule_published, :registration_open, :meals_provided, :meal_info, :travel_info, :conference_type_id, conference_types: [:id]) + params.require(:conference).permit(:title, :slug, :start_date, :end_date, :info, :poster, :cover, :workshop_schedule_published, :registration_status, :meals_provided, :meal_info, :travel_info, :conference_type_id, conference_types: [:id]) end def update_field_position(field_id, position) diff --git a/app/controllers/oauths_controller.rb b/app/controllers/oauths_controller.rb index f46e373..d170844 100644 --- a/app/controllers/oauths_controller.rb +++ b/app/controllers/oauths_controller.rb @@ -4,24 +4,37 @@ class OauthsController < ApplicationController # sends the user on a trip to the provider, # and after authorizing there back to the callback url. def oauth + session[:oauth_last_url] = request.referer login_at(auth_params[:provider]) end def callback - provider = auth_params[:provider] - if @user = login_from(provider) - redirect_to root_path, :notice => "Logged in with #{provider.titleize}!" - else - begin - @user = create_from(auth_params[:provider]) - - reset_session - auto_login(@user) - redirect_to root_path, :notice => "Signed up with #{provider.titleize}!" - rescue - redirect_to root_path, :alert => "Failed to login with #{provider.titleize}!" - end + user_info = (sorcery_fetch_user_hash auth_params[:provider] || {})[:user_info] + user = User.find_by_email(user_info['email']) + + # create the user if the email is not recognized + unless user + user = User.new(email: user_info['email'], firstname: user_info['name']) + user.save! end + + # log in the user + auto_login(user) if user + + redirect_to (session[:oauth_last_url] || home_path) + #, :notice => "Logged in with #{provider.titleize}!" + # if @user = login_from(provider) + # else + # begin + # @user = create_from(auth_params[:provider]) + + # reset_session + # auto_login(@user) + # redirect_to redirect_url, :notice => "Signed up with #{provider.titleize}!" + # rescue + # redirect_to redirect_url, :alert => "Failed to login with #{provider.titleize}!" + # end + # end end private diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2cf3889..72fc43e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -98,6 +98,35 @@ module ApplicationHelper content_for(:banner) { ('' + banner_title.to_s + '').html_safe } end + def add_stylesheet(sheet) + @stylesheets ||= [] + @stylesheets << sheet + end + + def stylesheets + html = '' + Rack::MiniProfiler.step('inject_css') do + html += inject_css! + end + (@stylesheets || []).each do |css| + Rack::MiniProfiler.step("inject_css #{css}") do + html += inject_css! css.to_s + end + end + html += stylesheet_link_tag 'i18n-debug' if request.params['i18nDebug'] + return html.html_safe + end + + def add_inline_script(script) + @_inline_scripts ||= [] + @_inline_scripts << Rails.application.assets.find_asset("#{script.to_s}.js").to_s + end + + def inline_scripts + return '' unless @_inline_scripts.present? + "".html_safe + end + def banner_attribution if @@banner_image && @@banner_attribution_details src = @@banner_attribution_details[:src] @@ -185,12 +214,17 @@ module ApplicationHelper false end + def off_screen(text) + "#{text}".html_safe + end + def url_for_locale(locale) - url_for(params - .merge({action: (params[:_original_action] || params[:action])} - .merge(url_params(locale))) - .delete(:_original_action) - ) + new_params = params.merge({action: (params[:_original_action] || params[:action])}) + new_params.delete(:_original_action) + + return url_for(new_params.merge({lang: locale.to_s})) if Rails.env.development? || Rails.env.test? + return "https://preview-#{locale.to_s}.bikebike.org#{url_for(new_params)}" if Rails.env.preview? + "https://#{locale.to_s}.bikebike.org#{url_for(new_params)}" end def registration_steps(conference = @conference) @@ -550,6 +584,18 @@ module ApplicationHelper country = location.country region = location.territory city = location.city + elsif location.data.present? && location.data['address_components'].present? + component_map = { + 'locality' => :city, + 'administrative_area_level_1' => :region, + 'country' => :country + } + location.data['address_components'].each do | component | + types = component['types'] + country = component['short_name'] if types.include? 'country' + region = component['short_name'] if types.include? 'administrative_area_level_1' + city = component['long_name'] if types.include? 'locality' + end else country = location.data['country_code'] region = location.data['region_code'] @@ -565,6 +611,14 @@ module ApplicationHelper return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", vars: hash) : hash.values.first end + def show_errors(field) + return '' unless @errors && @errors[field].present? + + error_txt = _"errors.messages.fields.#{field.to_s}.#{@errors[field]}", :s + + "#{error_txt}".html_safe + end + def nav_link(link, title = nil, class_name = nil) if title.nil? && link.is_a?(Symbol) title = link @@ -572,12 +626,13 @@ module ApplicationHelper end if class_name.nil? && title.is_a?(Symbol) class_name = title - title = _"page_titles.#{title.to_s.titlecase}" end + title = _"page_titles.#{title.to_s.titlecase.gsub(/\s/, '_')}" classes = [] classes << class_name if class_name.present? - classes << 'current' if current_page?(link.gsub(/(^.*)\/$/, '\1')) - link_to title.html_safe, link, :class => classes + classes << "strlen-#{strip_tags(title).length}" + classes << 'current' if request.fullpath.start_with?(link.gsub(/^(.*?)\/$/, '\1')) + link_to "#{title}".html_safe, link, :class => classes end def date(date, format = :long) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 213329b..baa4bb5 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,3 +1,5 @@ +require 'diffy' + class UserMailer < ActionMailer::Base add_template_helper(ApplicationHelper) include LinguaFrancaHelper @@ -26,10 +28,6 @@ class UserMailer < ActionMailer::Base mail to: "to@example.org" end - def test_email - mail to: 'goodgodwin@hotmail.com', subject: 'This is a test', from: 'info@preview.bikebike.org' - end - def conference_registration_email(conference, data, conference_registration) @data = data @conference = conference @@ -49,8 +47,8 @@ class UserMailer < ActionMailer::Base def email_confirmation(confirmation) @confirmation = confirmation @host = UserMailer.default_url_options[:host] - mail to: confirmation.user.email, - subject: (_'email.subject.confirm_email','Please confirm your email address') + @subject = _'email.subject.confirm_email','Please confirm your email address' + mail to: confirmation.user.email, subject: @subject end def registration_confirmation(registration) @@ -58,19 +56,27 @@ class UserMailer < ActionMailer::Base @registration = registration @conference = Conference.find(@registration.conference_id) @user = User.find(@registration.user_id) - mail to: @user.email, - subject: _('email.subject.registration_confirmed', - "Thank you for registering for #{@conference.title}", - :vars => {:conference_title => @conference.title}) + @subject = @conference.registration_status.to_sym == :pre ? + _( + 'email.subject.pre_registration_confirmed', + "Thank you for pre-registering for #{@conference.title}", + :vars => {:conference_title => @conference.title} + ) : _( + 'email.subject.registration_confirmed', + "Thank you for registering for #{@conference.title}", + :vars => {:conference_title => @conference.title} + ) + mail to: @user.email, subject: @subject end def broadcast(host, subject, content, user, conference) @host = host @content = content @banner = nil#(@host || 'http://localhost/') + (conference ? (conference.poster.preview.url || '') : image_url('logo.png')) + @subject = "[#{conference ? conference.title : 'Bike!Bike!'}] #{subject}" if user && user.email email = user.email - mail to: email, subject: "[#{conference ? conference.title : 'Bike!Bike!'}] #{subject}" + mail to: email, subject: @subject end end @@ -84,11 +90,10 @@ class UserMailer < ActionMailer::Base end @message = message @conference = Conference.find(@workshop.conference_id) - mail to: addresses, - from: @requester.email, - subject: _('email.subject.workshop_facilitator_request', - "Request to facilitate #{@workshop.title} from #{@requester.firstname}", + @subject = _('email.subject.workshop_facilitator_request', + "Request to facilitate #{@workshop.title} from #{@requester.name}", :vars => {:workshop_title => @workshop.title, :requester_name => @requester.firstname}) + mail to: addresses, from: @requester.email, subject: @subject end def workshop_facilitator_request_approved(workshop, user) @@ -96,10 +101,10 @@ class UserMailer < ActionMailer::Base @workshop = workshop @conference = Conference.find(@workshop.conference_id) @user = user - mail to: user.email, - subject: (_'email.subject.workshop_request_approved', - "You have been added as a facilitator of #{@workshop.title}", - :vars => {:workshop_title => @workshop.title}) + @subject = (_'email.subject.workshop_request_approved', + "You have been added as a facilitator of #{@workshop.title}", + :vars => {:workshop_title => @workshop.title}) + mail to: user.email, subject: @subject end def workshop_facilitator_request_denied(workshop, user) @@ -107,10 +112,56 @@ class UserMailer < ActionMailer::Base @workshop = workshop @conference = Conference.find(@workshop.conference_id) @user = user - mail to: user.email, - subject: (_'email.subject.workshop_request_denied', - "Your request to facilitate #{@workshop.title} has been denied", - :vars => {:workshop_title => @workshop.title}) + @subject = (_'email.subject.workshop_request_denied', + "Your request to facilitate #{@workshop.title} has been denied", + :vars => {:workshop_title => @workshop.title}) + mail to: user.email, subject: @subject + end + + def workshop_translated(workshop, data, locale, user, translator) + @host = UserMailer.default_url_options[:host] + @workshop = workshop + @data = data + @locale = locale + @locale_name = language_name(locale) + @user = user + @translator = translator + @subject = (_'email.subject.workshop_translated', + "The #{@locale_name} translation for #{@workshop.title} has been modified", + vars: {language: @language_name, workshop_title: @workshop.title}) + @data.each do |field, values| + diff = Diffy::Diff.new(values[:old], values[:new]) + @data[field][:diff] = { + text: diff.to_s(:text), + html: diff.to_s(:html) + } + end + + @wrapper_id = :full_width + + mail to: user.email, subject: @subject + end + + def workshop_original_content_changed(workshop, data, user, translator) + @host = UserMailer.default_url_options[:host] + @workshop = workshop + @data = data + @user = user + @translator = translator + @subject = (_'email.subject.workshop_original_content_changed', + "Original content for #{@workshop.title} has been modified", + vars: {workshop_title: @workshop.title}) + @data.each do |field, values| + diff = Diffy::Diff.new(values[:old], values[:new]) + @data[field][:diff] = { + text: diff.to_s(:text), + html: diff.to_s(:html) + } + end + + @wrapper_id = :full_width + + mail to: user.email, subject: @subject end end diff --git a/app/models/conference.rb b/app/models/conference.rb index d6f90af..3288d06 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -42,8 +42,23 @@ class Conference < ActiveRecord::Base def registered?(user) registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id) - return false unless registration - return registration.is_attending + return registration ? registration.is_attending : false + end + + def registration_exists?(user) + ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id).present? + end + + def registration_open + registration_status == :open + end + + def registration_status + read_attribute(:registration_status).to_sym + end + + def registration_status=(new_registration_status) + write_attribute :registration_status, new_registration_status.to_s end end diff --git a/app/models/conference_registration.rb b/app/models/conference_registration.rb index fce90c9..136a5d8 100644 --- a/app/models/conference_registration.rb +++ b/app/models/conference_registration.rb @@ -1,5 +1,11 @@ -class ConferenceRegistration < ActiveRecord::Base - has_many :conference_registration_responses - - AttendingOptions = [:yes, :no] -end +class ConferenceRegistration < ActiveRecord::Base + belongs_to :conference + belongs_to :user + has_many :conference_registration_responses + + AttendingOptions = [:yes, :no] + + def languages + user.languages + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 85af0dd..74de4be 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -21,8 +21,18 @@ class User < ActiveRecord::Base has_many :authentications, :dependent => :destroy accepts_nested_attributes_for :authentications - def can_translate? - is_translator + def can_translate?(to_locale = nil, from_locale = nil) + is_translator unless to_locale.present? + + from_locale = I18n.locale unless from_locale.present? + return languages.present? && + to_locale.to_s != from_locale.to_s && + languages.include?(to_locale.to_s) && + languages.include?(from_locale.to_s) + end + + def name + firstname || username || email end end diff --git a/app/models/workshop.rb b/app/models/workshop.rb index e3171cc..c8ef90c 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -18,7 +18,7 @@ class Workshop < ActiveRecord::Base return nil unless user workshop_facilitators.each do |u| if u.user_id == user.id - return conference.registered?(user) ? u.role.to_sym : :unregistered + return conference.registration_exists?(user) ? u.role.to_sym : :unregistered end end return nil @@ -31,7 +31,7 @@ class Workshop < ActiveRecord::Base def active_facilitators users = [] workshop_facilitators.each do |u| - users << User.find(u.user_id) if u.role.to_sym != :request + users << User.find(u.user_id) unless u.role.to_sym == :requested end return users end @@ -75,16 +75,18 @@ class Workshop < ActiveRecord::Base end def interested_count + return 0 unless id collaborators = [] workshop_facilitators.each do |f| collaborators << f.user_id unless f.role.to_sym == :requested end - interested = WorkshopInterest.where("workshop_id=#{id} AND user_id NOT IN (#{collaborators.join ','})") + return 10 unless collaborators.present? + interested = WorkshopInterest.where("workshop_id=#{id} AND user_id NOT IN (#{collaborators.join ','})") || [] interested ? interested.size : 0 end def can_translate?(user, lang) - (user.can_translate? && lang.to_sym != locale.to_sym) || can_edit?(user) + user.can_translate?(lang, locale) || (can_edit?(user) && lang.to_s != locale.to_s) end def conference_day @@ -100,6 +102,28 @@ class Workshop < ActiveRecord::Base ((end_time - start_time) / 60).to_i end + def self.all_themes + [:race_gender, :mechanics, :funding, :organization, :community] + end + + def get_translators(data, loc = nil) + notify_list = {} + active_facilitators.each do |facilitator| + notify_list[facilitator.id] = facilitator + end + + data.each do | column, value | + ( + loc.present? ? + get_translators_for_column_and_locale(column, loc) : + get_translators_for_column(column) + ).each do |id| + notify_list[id] = User.find(id) + end + end + return notify_list + end + private def make_slug if !self.slug diff --git a/app/views/application/404.html.haml b/app/views/application/404.html.haml index 0f311b4..fde5422 100644 --- a/app/views/application/404.html.haml +++ b/app/views/application/404.html.haml @@ -5,5 +5,4 @@ = columns(medium: 12) do %h1 =_'error.404.title','This page does not exist!' - %p - =p(_'error.404.description', :p) \ No newline at end of file + =paragraph(_'error.404.description', :p) \ No newline at end of file diff --git a/app/views/application/_header.html.haml b/app/views/application/_header.html.haml index 525cb8d..1c7a5e4 100644 --- a/app/views/application/_header.html.haml +++ b/app/views/application/_header.html.haml @@ -5,13 +5,14 @@ - figure = nil - if capable_of(:css_mixblendmode) - cover = "" - - elsif capable_of(:svg) && Rails.env != 'test' - - banner_image = 'application/banner_image.svg' - else - style = "background-image: url(#{image})" #header-title.short{style: style} - = (render banner_image, {:image => image}) if banner_image + = (render banner_image, {:image => image}) if defined?(banner_image) == "local-variable" = cover.html_safe if cover - = row do - = columns do - %h1=_(@page_title || "page_titles.#{page_group.to_s}.#{page_key.to_s}") + - if @page_title.present? || defined?(page_group) + - content_for :title do + =@page_title.present? ? I18n.t(@page_title, @page_title_vars) : I18n.t("page_titles.#{page_group.to_s}.#{page_key.to_s}") + = row do + = columns do + %h1=(_"page_titles.#{page_group.to_s}.#{page_key.to_s}") diff --git a/app/views/application/_login_confirmation_sent.html.haml b/app/views/application/_login_confirmation_sent.html.haml index 8d03d41..fee35d4 100644 --- a/app/views/application/_login_confirmation_sent.html.haml +++ b/app/views/application/_login_confirmation_sent.html.haml @@ -2,4 +2,4 @@ = columns(medium: 12) do %h2=_'articles.permission_denied.headings.confirmation_sent','Confirmation Sent' = columns(medium: 6) do - =m('articles.permission_denied.paragraphs.confirmation_sent', :p) + %p='articles.permission_denied.paragraphs.confirmation_sent', :p diff --git a/app/views/application/home.html.haml b/app/views/application/home.html.haml index a95c422..0ac427d 100644 --- a/app/views/application/home.html.haml +++ b/app/views/application/home.html.haml @@ -9,16 +9,16 @@ - if @schedule %h3=_'articles.workshops.headings.Schedule' = render 'schedule/programme', :schedule => @schedule, :conference => @conference, :workshops => @workshops, :events => @events, :locations => @locations, :show_interest => true, :day_parts => @day_parts, :show_previews => true - - elsif @conference.registration_open - %h3=_'articles.workshops.headings.Proposed_Workshops' - %p=_'articles.workshops.paragraphs.Proposed_Workshops', "Would you like to facilitate your own workshop? Simply register and visit the workshops page. If you have already registered you can access the page by restarting the registration process." - %ul.workshop-list - - @conference.workshops.sort_by{ |w| w.title.downcase }.each do |w| - %li - %h4=w.title - .workshop-interest - - if w.interested?(current_user) - =_'articles.workshops.info.you_are_interested_count', "You and #{w.interested_count - 1} others are interested in this workshop", :vars => {:count => (w.interested_count - 1)} - - elsif w.interested_count > 0 - =_'articles.workshops.info.interested_count', "#{w.interested_count} people are interested in this workshop", :vars => {:count => w.interested_count} - .workshop-description=markdown w.info + - if @conference.registration_status == :open + %h3=_'articles.workshops.headings.Proposed_Workshops' + %p=_'articles.workshops.paragraphs.Proposed_Workshops', "Would you like to facilitate your own workshop? Simply register and visit the workshops page. If you have already registered you can access the page by restarting the registration process." + %ul.workshop-list + - @conference.workshops.sort_by{ |w| w.title.downcase }.each do |w| + %li + %h4=w.title + .workshop-interest + - if w.interested?(current_user) + =_'articles.workshops.info.you_are_interested_count', "You and #{w.interested_count - 1} others are interested in this workshop", :vars => {:count => (w.interested_count - 1)} + - elsif w.interested_count > 0 + =_'articles.workshops.info.interested_count', "#{w.interested_count} people are interested in this workshop", :vars => {:count => w.interested_count} + .workshop-description=markdown w.info diff --git a/app/views/application/permission_denied.html.haml b/app/views/application/permission_denied.html.haml index 34cf389..c89daec 100644 --- a/app/views/application/permission_denied.html.haml +++ b/app/views/application/permission_denied.html.haml @@ -1,7 +1,9 @@ = render :partial => 'application/header', :locals => {:image_file => @banner_image || '403.jpg'} %article - - if @template + - if @template.present? =render @template - else - %h2=_'articles.permission_denied.headings.main','Sorry, you currently don\'t have access to this page' - %p=_'articles.permission_denied.paragraphs.main', :p + = row do + = columns do + %h2=_'articles.permission_denied.headings.main','Sorry, you currently don\'t have access to this page' + %p=_'articles.permission_denied.paragraphs.main', :p diff --git a/app/views/conferences/_confirm_email.html.haml b/app/views/conferences/_confirm_email.html.haml index 166907a..e2d1831 100644 --- a/app/views/conferences/_confirm_email.html.haml +++ b/app/views/conferences/_confirm_email.html.haml @@ -1,6 +1,15 @@ %article = columns(medium: 12) do - %h2=_'articles.conference_registration.headings.Confirm_You_Email_Address','Confirm Email Address' - = columns(medium: 6, large: 5) do - = form_tag register_path(@this_conference.slug) do + %h2=_'articles.conference_registration.headings.Pre_Registration_Details','Pre-Registration is now open!' + %p=_'articles.conference_registration.paragraphs.Pre_Registration_Details', 'By completing the pre-registration process you are letting us know that you are interested in coming, it allows us to contact you when we have news, and lets us better plan by knowing who might be coming. Once registration is fully open, we will need to ask a few more questions and you can confirm whether or not you will actually be coming.' + %h3=_'articles.conference_registration.headings.Verify_Account','Verify your account' + %p=_'articles.conference_registration.paragraphs.Verify_Account', 'In order to confirm that you are a real person and that we will be able to contact you later, please verify your email address. Before continuing with pre-registration, we will send you an email that will allow you to continue with the process.' + = columns(medium: 12) do + = form_tag register_path(@this_conference.slug), class: 'flex-form' do + .email-field.input-field.big + = email_field_tag :email, nil, required: true + = label_tag :email = button_tag :continue, :value => :confirm_email + = columns(medium: 12, class: 'flex-column') do + %p.stretch-item=_'articles.conference_registration.paragraphs.facebook_sign_in','Alternatively you can sign in using your Facebook account and skip waiting for us to send you an email.' + = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(:provider => :facebook), class: [:button, :facebook] diff --git a/app/views/conferences/_contact_info.html.haml b/app/views/conferences/_contact_info.html.haml new file mode 100644 index 0000000..ff8e5c9 --- /dev/null +++ b/app/views/conferences/_contact_info.html.haml @@ -0,0 +1,26 @@ += columns(medium: 12) do + %h2=_(@page_title) + %p=_'articles.conference_registration.paragraphs.Contact_Info', :s, 2 += columns(medium: 12) do + = form_tag register_path(@this_conference.slug) do + %h3=_'articles.conference_registration.headings.name','What is your name?' + .text-field.input-field{class: @errors[:name].present? ? 'has-error' : nil} + = show_errors :name + = label_tag :name + = text_field_tag :name, @name, required: true + %h3=_'articles.conference_registration.headings.location','Where are you coming from?' + .text-field.input-field{class: @errors[:location].present? ? 'has-error' : nil} + = show_errors :location + = label_tag :location + = text_field_tag :location, @registration.city || location(lookup_ip_location), required: true + %h3=_'articles.conference_registration.headings.languages','Which languages do you speak?' + - puts @errors.to_json.to_s + .check-box-field.input-field{class: @errors[:languages].present? ? 'has-error' : nil} + - [:en, :es, :fr].each do |language| + = check_box_tag "languages[#{language}]", 1, (@registration.languages || [I18n.locale.to_s]).include?(language.to_s) + = label_tag "languages_#{language}" do + = _"languages.#{language}" + = show_errors :languages + .actions.next-prev + = button_tag (params[:step] == :save ? :save : :next), value: :contact_info + = button_tag :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true diff --git a/app/views/conferences/_email_confirm.html.haml b/app/views/conferences/_email_confirm.html.haml index c0472c7..89b384b 100644 --- a/app/views/conferences/_email_confirm.html.haml +++ b/app/views/conferences/_email_confirm.html.haml @@ -3,4 +3,4 @@ = columns(medium: 12) do %h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address' = columns(medium: 12) do - =m('articles.conference_registration.paragraphs.email_confirm', :p) + %p=_'articles.conference_registration.paragraphs.email_confirm', :p diff --git a/app/views/conferences/_header.html.haml b/app/views/conferences/_header.html.haml index 6dc40df..79e6555 100644 --- a/app/views/conferences/_header.html.haml +++ b/app/views/conferences/_header.html.haml @@ -2,7 +2,7 @@ .title %h1=_!@conference.title .details - %h3.primary=location(@conference.organizations.first.locations.first) + %h2.primary=location(@conference.organizations.first.locations.first) .secondary = date_span(@conference.start_date.to_date, @conference.end_date.to_date) - %img{src: @conference.poster.full.url || image_path('default_poster.jpg')} + %img{src: @conference.poster.full.url || image_path('default_poster.jpg'), role: :presentation, alt: ''} diff --git a/app/views/conferences/_policy.html.haml b/app/views/conferences/_policy.html.haml index 2715e51..a2aef04 100644 --- a/app/views/conferences/_policy.html.haml +++ b/app/views/conferences/_policy.html.haml @@ -1,10 +1,9 @@ -%article - = columns(medium: 12) do - %h2=_'articles.conference_registration.headings.Policy_Agreement' - %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 - = columns(medium: 10, push: 1) do - = render 'application/policy' - = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p - = form_tag register_path(@this_conference.slug) do - .actions= button_tag :agree, :value => :policy += columns(medium: 12) do + %h2=_@page_title + %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 += columns(medium: 10, push: 1) do + = render 'application/policy' += columns(medium: 12) do + %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p + = form_tag register_path(@this_conference.slug) do + .actions= button_tag :agree, :value => :policy diff --git a/app/views/conferences/_workshops.html.haml b/app/views/conferences/_workshops.html.haml index eb1dc4c..b0c2bcb 100644 --- a/app/views/conferences/_workshops.html.haml +++ b/app/views/conferences/_workshops.html.haml @@ -1,42 +1,20 @@ = columns(medium: 12) do - %h2=_'articles.conference_registration.headings.Workshops','Workshops' -= columns(medium: 6) do - %h3=_'articles.conference_registration.headings.Add_Workshop' - = form_tag register_path(@this_conference.slug) do - .text-field.input-field - = text_field_tag :title, nil, required: true - = label_tag :title - = text_area_tag :info, nil, required: true - = button_tag :add -= columns(medium: 6) do - %h3=_'articles.conference_registration.headings.Your_Workshops' - %ul.workshops.my-workshops - - @my_workshops.each do |workshop| - %li - %h4=workshop[:title] - .info - %h5{:contenteditable => true}=workshop[:title] - %p{:contenteditable => true}=workshop[:info] - = form_tag register_path(@this_conference.slug) do - = button_tag :save, :value => :save - = button_tag :delete, :value => :delete - = button_tag :cancel, :value => :cancel + %h2=_@page_title = columns(medium: 12) do - = form_tag register_path(@this_conference.slug) do - = button_tag :previous, :value => :registration - = button_tag :next, :value => :workshops -:javascript - function makeWorkshopsClickable() { - var workshops = document.querySelectorAll('ul.workshops h4'); - for (var i = 0; i < workshops.length; i++) { - workshops[i].addEventListener('click', function(e) { - var workshop = e.target.parentElement; - workshop.className = 'view'; - workshop.querySelector('form').onsubmit = function() { - workshop.removeAttribute('class'); - return false; - } - }, false); - } - } - makeWorkshopsClickable(); + - if @my_workshops.present? + %h3=_'articles.conference_registration.headings.Your_Workshops' + %p=_'articles.conference_registration.paragraphs.Your_Workshops', :p + = render 'workshops/workshop_previews', :workshops => @my_workshops + - else + %p=_'articles.conference_registration.paragraphs.Create_Workshop', :p + = link_to (_'articles.conference_registration.headings.Add_Workshop'), create_workshop_path(@this_conference.slug), class: :button +- if @workshops_in_need.present? + = columns(medium: 12) do + %h3=_'articles.conference_registration.headings.Workshops_Looking_For_Facilitators' + %p=_'articles.conference_registration.paragraphs.Workshops_Looking_For_Facilitators', :p + = render 'workshops/workshop_previews', :workshops => @workshops_in_need +- if @workshops.present? + = columns(medium: 12) do + %h3=_'articles.conference_registration.headings.All_Workshops' + -#%p=_'articles.conference_registration.paragraphs.All_Workshops', :p + = render 'workshops/workshop_previews', :workshops => @workshops diff --git a/app/views/conferences/edit.html.haml b/app/views/conferences/edit.html.haml index 6e0f79e..7b1e2bd 100644 --- a/app/views/conferences/edit.html.haml +++ b/app/views/conferences/edit.html.haml @@ -13,18 +13,18 @@ = button_tag :save, :value => :save -:javascript - window.jQuery || document.write(' + + + + + + + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. + + + + + + + + + + + +