diff --git a/.env.example b/.env.example index 5796d781c..9eab453cf 100644 --- a/.env.example +++ b/.env.example @@ -14,7 +14,7 @@ SENDGRID_USERNAME=test_smtp_username SENDGRID_PASSWORD=test_smtp_password AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=xxx -AWS_REGION=xxx +AWS_REGION=xxx AWS_S3_BUCKET_NAME=xxx DISPLAY_SOCIAL_MOBILITY_AWARD=true PUSHER_SOCKET_HOST=localhost @@ -24,4 +24,5 @@ PUSHER_APP_KEY=app_key PUSHER_SECRET=secret DEBOUNCE_API_KEY=test GOV_UK_NOTIFY_API_KEY=key -GOV_UK_NOTIFY_API_TEMPLATE_ID=id \ No newline at end of file +GOV_UK_NOTIFY_API_TEMPLATE_ID=id +SESSION_TIMEOUT=1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eea4c6fee..8db9f826c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.2.2 - uses: actions/setup-node@v2-beta with: node-version: '16' @@ -54,14 +54,13 @@ jobs: env: DISPLAY_SOCIAL_MOBILITY_AWARD: true DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/qae_test' - BUNDLER_VERSION: 2.4.8 + BUNDLER_VERSION: 2.4.19 DOCKER_TLS_CERTDIR: '' run: | sudo apt update sudo apt-get -yqq install postgresql postgresql-client libpq-dev xvfb unzip libcurl4 libcurl3-gnutls libcurl4-openssl-dev gem install bundler gem update --system && gem update bundler - bundle config path vendor/bundle yarn install bundle install --jobs 4 --retry 3 RAILS_ENV=test bundle exec rake db:create db:migrate @@ -83,7 +82,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.2.2 - uses: actions/setup-node@v2-beta with: node-version: '16' @@ -124,7 +123,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.2.2 - uses: actions/setup-node@v2-beta with: node-version: '16' diff --git a/.github/workflows/deploy_development.yml b/.github/workflows/deploy_development.yml index 08eef44d0..74bd31db8 100644 --- a/.github/workflows/deploy_development.yml +++ b/.github/workflows/deploy_development.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.2.2 - uses: actions/setup-node@v2-beta with: node-version: "16" diff --git a/.github/workflows/deploy_production.yml b/.github/workflows/deploy_production.yml index 6b52c737e..044f20bf3 100644 --- a/.github/workflows/deploy_production.yml +++ b/.github/workflows/deploy_production.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.2.2 - uses: actions/setup-node@v2-beta with: node-version: "16" diff --git a/.github/workflows/deploy_staging.yml b/.github/workflows/deploy_staging.yml index 94fb40315..e9eb9b1ed 100644 --- a/.github/workflows/deploy_staging.yml +++ b/.github/workflows/deploy_staging.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.2.2 - uses: actions/setup-node@v2-beta with: node-version: '16' diff --git a/.ruby-version b/.ruby-version index 1f7da99d4..be94e6f53 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.7 +3.2.2 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..29d3872ec --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +ruby 3.2.2 +nodejs 18.17.1 diff --git a/Dockerfile.local b/Dockerfile.local index 7578b7fc8..c26f559c9 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,6 +1,6 @@ -ARG RUBY_VERSION=2.7.7 +ARG RUBY_VERSION=3.2.2 -FROM ruby:2.7.7 +FROM ruby:3.2.2 ENV HOME=/app WORKDIR /app @@ -18,7 +18,7 @@ COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN bundle config set --local path 'vendor/bundle' RUN bundle config set --local without 'development test' -RUN gem install bundler:2.4.8 && bundle install --jobs 4 --retry 3 +RUN gem install bundler:2.4.19 && bundle install --jobs 4 --retry 3 COPY . /app RUN yarn install diff --git a/Gemfile b/Gemfile index 61fe26583..1b99ba0a8 100644 --- a/Gemfile +++ b/Gemfile @@ -2,9 +2,9 @@ source 'https://rubygems.org' git_source(:github) { |name| "https://github.com/#{name}.git" } -ruby '~> 2.7.7' +ruby '~> 3.2.2' -gem 'rails', '6.1.7.3' +gem 'rails', '7.0.5.1' gem 'websocket-extensions', '~> 0.1.5' # SSL redirect @@ -14,12 +14,13 @@ gem 'rack-ssl-enforcer' gem 'pg' # Track Changes -gem 'paper_trail', '~> 10.3' +gem 'paper_trail', '~> 12.2.0' +gem 'paper_trail-association_tracking' # Assets & Templates gem 'sprockets', '~> 3.7.2' gem 'sprockets-rails', '>= 2.0.0' -gem 'sassc-rails', '~> 2.0.0' +gem 'sassc-rails', '~> 2.1.2' gem 'slim-rails', '~> 3.2.0' gem 'coffee-rails', '5.0' gem 'jquery-rails', '4.4.0' @@ -29,7 +30,7 @@ gem 'govuk-components' gem 'uglifier', '>= 2.7.2' gem 'js_cookie_rails', '2.1.4' gem 'ckeditor' -gem 'webpacker', '6.0.0.beta.7' +gem 'webpacker', '6.0.0.rc.6' # Autolinking in admin mass user mailer gem 'rails_autolink' @@ -37,7 +38,6 @@ gem 'rails_autolink' # Decorators & Exposing named methods gem 'draper', '~> 4.0' gem 'decent_exposure' -gem 'decent_decoration' gem 'hashie', '~> 3.5' @@ -48,7 +48,7 @@ gem 'responders', '~> 3.0' gem 'rails-html-sanitizer', '~> 1.4.4' # JSON -gem 'json', '2.3.0' +gem 'json' gem 'jbuilder', '~> 2.10.1' gem 'gon', '>= 6.4.0' @@ -87,7 +87,6 @@ gem 'nokogiri' # Uploads gem 'carrierwave', '~> 1.3' -gem 'fog', "1.42.1" gem "fog-aws" gem 'vigilion', '~> 1.0.4' gem 'vigilion-rails', '~> 2.2.0' @@ -126,7 +125,7 @@ gem 'nilify_blanks' gem 'curb', '0.9.10' # Web server -gem 'puma', '~> 4.3.12' +gem 'puma', '~> 6.3.1' # Performance & Error reporting gem 'appsignal' @@ -144,6 +143,8 @@ gem 'shog' gem 'rails-healthcheck' +gem 'matrix' + # Used to convert HTML to text, with the exception of whitelisted attributes. # This makes it easier for us to display HTML content within PDF documents. gem 'sanitize' @@ -179,7 +180,7 @@ end group :test do gem 'factory_bot_rails' - gem 'capybara', '3.33' + gem 'capybara', '~> 3.39.0' gem 'poltergeist' gem 'database_cleaner-active_record' gem 'launchy' @@ -189,6 +190,6 @@ group :test do gem 'codeclimate_circle_ci_coverage' gem 'rspec_junit_formatter', '0.2.3' gem 'timecop' - gem 'webmock', '3.5.0' + gem 'webmock', '3.18.1' gem 'rspec-sidekiq' end diff --git a/Gemfile.lock b/Gemfile.lock index 1c637713a..1191e9fde 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,81 +10,83 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) - CFPropertyList (2.3.6) - actioncable (6.1.7.3) - actionpack (= 6.1.7.3) - activesupport (= 6.1.7.3) + actioncable (7.0.5.1) + actionpack (= 7.0.5.1) + activesupport (= 7.0.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.3) - actionpack (= 6.1.7.3) - activejob (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionmailbox (7.0.5.1) + actionpack (= 7.0.5.1) + activejob (= 7.0.5.1) + activerecord (= 7.0.5.1) + activestorage (= 7.0.5.1) + activesupport (= 7.0.5.1) mail (>= 2.7.1) - actionmailer (6.1.7.3) - actionpack (= 6.1.7.3) - actionview (= 6.1.7.3) - activejob (= 6.1.7.3) - activesupport (= 6.1.7.3) + net-imap + net-pop + net-smtp + actionmailer (7.0.5.1) + actionpack (= 7.0.5.1) + actionview (= 7.0.5.1) + activejob (= 7.0.5.1) + activesupport (= 7.0.5.1) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.7.3) - actionview (= 6.1.7.3) - activesupport (= 6.1.7.3) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.5.1) + actionview (= 7.0.5.1) + activesupport (= 7.0.5.1) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.3) - actionpack (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + actiontext (7.0.5.1) + actionpack (= 7.0.5.1) + activerecord (= 7.0.5.1) + activestorage (= 7.0.5.1) + activesupport (= 7.0.5.1) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.3) - activesupport (= 6.1.7.3) + actionview (7.0.5.1) + activesupport (= 7.0.5.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_hash (3.1.1) + active_hash (3.2.0) activesupport (>= 5.0.0) - activejob (6.1.7.3) - activesupport (= 6.1.7.3) + activejob (7.0.5.1) + activesupport (= 7.0.5.1) globalid (>= 0.3.6) - activemodel (6.1.7.3) - activesupport (= 6.1.7.3) + activemodel (7.0.5.1) + activesupport (= 7.0.5.1) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.1.7.3) - activemodel (= 6.1.7.3) - activesupport (= 6.1.7.3) - activestorage (6.1.7.3) - actionpack (= 6.1.7.3) - activejob (= 6.1.7.3) - activerecord (= 6.1.7.3) - activesupport (= 6.1.7.3) + activerecord (7.0.5.1) + activemodel (= 7.0.5.1) + activesupport (= 7.0.5.1) + activestorage (7.0.5.1) + actionpack (= 7.0.5.1) + activejob (= 7.0.5.1) + activerecord (= 7.0.5.1) + activesupport (= 7.0.5.1) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.3) + activesupport (7.0.5.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) afm (0.2.2) - aliyun-sdk (0.8.0) - nokogiri (~> 1.6) - rest-client (~> 2.0) amoeba (3.0.0) activerecord (>= 3.2.6) - appsignal (3.3.10) + appsignal (3.4.8) rack ast (2.4.2) authy (3.0.1) @@ -95,7 +97,7 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - bcrypt (3.1.18) + bcrypt (3.1.19) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) bootscale (0.7.0) @@ -105,13 +107,14 @@ GEM browser (2.4.0) builder (3.2.4) byebug (11.1.3) - capybara (3.33.0) + capybara (3.39.2) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) carrierwave (1.3.3) activemodel (>= 4.0.0) @@ -136,9 +139,9 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - colorize (0.8.1) + colorize (1.1.0) concurrent-ruby (1.2.2) - connection_pool (2.4.0) + connection_pool (2.4.1) countries (2.1.4) i18n_data (~> 0.8.0) money (~> 6.9) @@ -161,8 +164,6 @@ GEM database_cleaner-core (2.0.1) date (3.3.3) debug_inspector (1.1.0) - decent_decoration (0.1.0) - decent_exposure (~> 3.0) decent_exposure (3.0.4) activesupport (>= 4.0) descendants_tracker (0.0.4) @@ -194,15 +195,14 @@ GEM activesupport (>= 5.0) request_store (>= 1.0) ruby2_keywords - dry-inflector (1.0.0) email_validator (2.2.4) activemodel - enumerize (2.6.1) + enumerize (2.7.0) activesupport (>= 3.2) erubi (1.12.0) et-orbi (1.2.7) tzinfo - excon (0.99.0) + excon (0.100.0) execjs (2.8.1) factory_bot (6.2.1) activesupport (>= 5.0.0) @@ -237,165 +237,22 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) ffi (1.15.5) - fission (0.5.0) - CFPropertyList (~> 2.2) - fog (1.42.1) - fog-aliyun (>= 0.1.0) - fog-atmos - fog-aws (>= 0.6.0) - fog-brightbox (~> 0.4) - fog-cloudatcost (~> 0.1.0) - fog-core (~> 1.45) - fog-digitalocean (>= 0.3.0) - fog-dnsimple (~> 1.0) - fog-dynect (~> 0.0.2) - fog-ecloud (~> 0.1) - fog-google (<= 0.1.0) - fog-internet-archive - fog-joyent - fog-json - fog-local - fog-openstack - fog-ovirt - fog-powerdns (>= 0.1.1) - fog-profitbricks - fog-rackspace - fog-radosgw (>= 0.0.2) - fog-riakcs - fog-sakuracloud (>= 0.0.4) - fog-serverlove - fog-softlayer - fog-storm_on_demand - fog-terremark - fog-vmfusion - fog-voxel - fog-vsphere (>= 0.4.0) - fog-xenserver - fog-xml (~> 0.1.1) - ipaddress (~> 0.5) - json (~> 2.0) - fog-aliyun (0.4.0) - addressable (~> 2.8.0) - aliyun-sdk (~> 0.8.0) - fog-core - fog-json - ipaddress (~> 0.8) - xml-simple (~> 1.1) - fog-atmos (0.1.0) - fog-core - fog-xml - fog-aws (2.0.1) - fog-core (~> 1.38) - fog-json (~> 1.0) + fog-aws (3.19.0) + fog-core (~> 2.1) + fog-json (~> 1.1) fog-xml (~> 0.1) - ipaddress (~> 0.8) - fog-brightbox (0.16.1) - dry-inflector - fog-core - fog-json - mime-types - fog-cloudatcost (0.1.2) - fog-core (~> 1.36) - fog-json (~> 1.0) - fog-xml (~> 0.1) - ipaddress (~> 0.8) - fog-core (1.45.0) + fog-core (2.3.0) builder - excon (~> 0.58) - formatador (~> 0.2) - fog-digitalocean (0.4.0) - fog-core - fog-json - fog-xml - ipaddress (>= 0.5) - fog-dnsimple (1.0.0) - fog-core (~> 1.38) - fog-json (~> 1.0) - fog-dynect (0.0.3) - fog-core - fog-json - fog-xml - fog-ecloud (0.3.0) - fog-core - fog-xml - fog-google (0.1.0) - fog-core - fog-json - fog-xml - fog-internet-archive (0.0.2) - fog-core - fog-json - fog-xml - fog-joyent (0.0.1) - fog-core (~> 1.42) - fog-json (>= 1.0) + excon (~> 0.71) + formatador (>= 0.2, < 2.0) + mime-types fog-json (1.2.0) fog-core multi_json (~> 1.10) - fog-local (0.8.0) - fog-core (>= 1.27, < 3.0) - fog-openstack (0.3.10) - fog-core (>= 1.45, <= 2.1.0) - fog-json (>= 1.0) - ipaddress (>= 0.8) - fog-ovirt (2.0.2) - activesupport - fog-core - fog-json - fog-xml - ovirt-engine-sdk (>= 4.3.1) - fog-powerdns (0.2.0) - fog-core - fog-json - fog-xml - fog-profitbricks (4.1.1) - fog-core (~> 1.42) - fog-json (~> 1.0) - fog-rackspace (0.1.6) - fog-core (>= 1.35) - fog-json (>= 1.0) - fog-xml (>= 0.1) - ipaddress (>= 0.8) - fog-radosgw (0.0.5) - fog-core (>= 1.21.0) - fog-json - fog-xml (>= 0.0.1) - fog-riakcs (0.1.0) - fog-core - fog-json - fog-xml - fog-sakuracloud (1.7.5) - fog-core - fog-json - fog-serverlove (0.1.2) - fog-core - fog-json - fog-softlayer (1.1.4) - fog-core - fog-json - fog-storm_on_demand (0.1.1) - fog-core - fog-json - fog-terremark (0.1.0) - fog-core - fog-xml - fog-vmfusion (0.1.0) - fission - fog-core - fog-voxel (0.1.0) - fog-core - fog-xml - fog-vsphere (3.6.0) - fog-core - rbvmomi2 (~> 3.0) - fog-xenserver (1.0.0) - fog-core - fog-xml - xmlrpc fog-xml (0.1.4) fog-core nokogiri (>= 1.5.11, < 2.0.0) - formatador (0.3.0) + formatador (1.1.0) fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) @@ -406,24 +263,23 @@ GEM i18n (>= 0.7) multi_json request_store (>= 1.0) - govuk-components (4.0.0) + govuk-components (4.1.0) html-attributes-utils (~> 1.0.0, >= 1.0.0) pagy (~> 6.0) - view_component (~> 3.0.0) + view_component (~> 3.3.0) hashdiff (1.0.1) hashery (2.1.2) hashie (3.6.0) - html-attributes-utils (1.0.0) + html-attributes-utils (1.0.1) activesupport (>= 6.1.4.4) http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) i18n_data (0.8.0) ice_nine (0.11.2) - ipaddress (0.8.3) jbuilder (2.10.2) activesupport (>= 5.0.0) jquery-rails (4.4.0) @@ -434,8 +290,8 @@ GEM railties (>= 3.2.16) js_cookie_rails (2.1.4) railties (>= 3.1) - json (2.3.0) - jwt (2.7.0) + json (2.6.3) + jwt (2.7.1) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -460,9 +316,9 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.20.0) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -476,21 +332,22 @@ GEM notifications-ruby-client (~> 5.1) rack (>= 2.1.4.1) marcel (1.0.2) + matrix (0.4.2) method_source (1.0.0) middleware (0.1.0) mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.0218.1) mini_mime (1.1.2) - mini_portile2 (2.8.2) - minitest (5.18.0) + mini_portile2 (2.8.4) + minitest (5.18.1) money (6.16.0) i18n (>= 0.6.4, <= 2) multi_json (1.15.0) multipart-post (2.3.0) mustermann (3.0.0) ruby2_keywords (~> 0.0.1) - net-imap (0.3.4) + net-imap (0.3.6) date net-protocol net-pop (0.1.2) @@ -504,26 +361,28 @@ GEM activerecord (>= 4.0.0) activesupport (>= 4.0.0) nio4r (2.5.9) - nokogiri (1.14.3) - mini_portile2 (~> 2.8.0) + nokogiri (1.15.3) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.14.3-x86_64-darwin) + nokogiri (1.15.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.15.3-x86_64-linux) racc (~> 1.4) notifications-ruby-client (5.4.0) jwt (>= 1.5, < 3) - optimist (3.0.1) orm_adapter (0.5.0) - ovirt-engine-sdk (4.4.1) - json (>= 1, < 3) pagy (6.0.4) - paper_trail (10.3.1) - activerecord (>= 4.2) + paper_trail (12.2.0) + activerecord (>= 5.2) request_store (~> 1.1) + paper_trail-association_tracking (2.2.1) + paper_trail (>= 12.0) parallel (1.23.0) - parser (3.2.2.1) + parser (3.2.2.3) ast (~> 2.4.1) + racc pdf-core (0.9.0) pdf-inspector (1.3.0) pdf-reader (>= 1.0, < 3.0.a) @@ -557,8 +416,8 @@ GEM pry-byebug (3.10.1) byebug (~> 11.0) pry (>= 0.13, < 0.15) - public_suffix (5.0.1) - puma (4.3.12) + public_suffix (5.0.3) + puma (6.3.1) nio4r (~> 2.0) pundit (0.3.0) activesupport (>= 3.0.0) @@ -568,7 +427,7 @@ GEM pusher-signature (~> 0.1.8) pusher-signature (0.1.8) raabro (1.4.0) - racc (1.6.2) + racc (1.7.1) rack (2.2.7) rack-cors (1.1.1) rack (>= 2.0.0) @@ -581,27 +440,27 @@ GEM rack-ssl-enforcer (0.2.9) rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.3) - actioncable (= 6.1.7.3) - actionmailbox (= 6.1.7.3) - actionmailer (= 6.1.7.3) - actionpack (= 6.1.7.3) - actiontext (= 6.1.7.3) - actionview (= 6.1.7.3) - activejob (= 6.1.7.3) - activemodel (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + rails (7.0.5.1) + actioncable (= 7.0.5.1) + actionmailbox (= 7.0.5.1) + actionmailer (= 7.0.5.1) + actionpack (= 7.0.5.1) + actiontext (= 7.0.5.1) + actionview (= 7.0.5.1) + activejob (= 7.0.5.1) + activemodel (= 7.0.5.1) + activerecord (= 7.0.5.1) + activestorage (= 7.0.5.1) + activesupport (= 7.0.5.1) bundler (>= 1.15.0) - railties (= 6.1.7.3) - sprockets-rails (>= 2.0.0) + railties (= 7.0.5.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.1.1) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) rails-healthcheck (1.4.0) actionpack @@ -617,23 +476,19 @@ GEM railties (> 3.1) rails_serve_static_assets (0.0.5) rails_stdout_logging (0.0.5) - railties (6.1.7.3) - actionpack (= 6.1.7.3) - activesupport (= 6.1.7.3) + railties (7.0.5.1) + actionpack (= 7.0.5.1) + activesupport (= 7.0.5.1) method_source rake (>= 12.2) thor (~> 1.0) + zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rb-readline (0.5.5) - rbvmomi2 (3.6.0) - builder (~> 3.2) - json (~> 2.3) - nokogiri (~> 1.12, >= 1.12.5) - optimist (~> 3.0) redis (5.0.6) redis-client (>= 0.9.0) redis-actionpack (5.3.0) @@ -654,7 +509,7 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.9.2) redis (>= 4, < 6) - regexp_parser (1.8.2) + regexp_parser (2.8.1) request_store (1.5.1) rack (>= 1.4) responders (3.1.0) @@ -665,7 +520,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.5) + rexml (3.2.6) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) @@ -677,10 +532,10 @@ GEM rspec-support (~> 3.12.0) rspec-github (2.4.0) rspec-core (~> 3.0) - rspec-mocks (3.12.5) + rspec-mocks (3.12.6) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.0.2) + rspec-rails (6.0.3) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -691,7 +546,7 @@ GEM rspec-sidekiq (3.1.0) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.12.0) + rspec-support (3.12.1) rspec_junit_formatter (0.2.3) builder (< 4) rspec-core (>= 2, < 4, != 2.12.0) @@ -704,7 +559,7 @@ GEM rubocop-ast (>= 0.6.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (1.28.1) + rubocop-ast (1.29.0) parser (>= 3.2.1.0) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) @@ -713,18 +568,18 @@ GEM nokogiri (>= 1.10.8) rubyzip (>= 1.3.0) rubyzip (2.3.2) - sanitize (6.0.1) + sanitize (6.0.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) sassc (2.4.0) ffi (~> 1.9) - sassc-rails (2.0.0) + sassc-rails (2.1.2) railties (>= 4.0.0) sassc (>= 2.0) sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.9.0) + selenium-webdriver (4.11.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -738,7 +593,7 @@ GEM connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.5.0) - sidekiq-cron (1.10.0) + sidekiq-cron (1.10.1) fugit (~> 1.8) globalid (>= 1.0.1) sidekiq (>= 6) @@ -756,7 +611,7 @@ GEM rack (~> 2.2, >= 2.2.4) rack-protection (= 3.0.5) tilt (~> 2.0) - sixarm_ruby_unaccent (1.2.0) + sixarm_ruby_unaccent (1.2.2) slim (4.1.0) temple (>= 0.7.6, < 0.9) tilt (>= 2.0.6, < 2.1) @@ -776,11 +631,11 @@ GEM ssrf_filter (1.0.8) statesman (3.5.0) temple (0.8.2) - thor (1.2.1) + thor (1.2.2) thread_safe (0.3.6) tilt (2.0.11) timecop (0.9.6) - timeout (0.3.2) + timeout (0.4.0) ttfunk (1.7.0) turnip (4.2.0) cucumber-gherkin (~> 14.0) @@ -794,7 +649,7 @@ GEM unf_ext (0.0.8.2) unicode-display_width (1.8.0) unicode_utils (1.4.0) - view_component (3.0.0) + view_component (3.3.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) @@ -812,34 +667,32 @@ GEM descendants_tracker (~> 0.0, >= 0.0.3) warden (1.2.9) rack (>= 2.0.9) - webmock (3.5.0) - addressable (>= 2.3.6) + webmock (3.18.1) + addressable (>= 2.8.0) crack (>= 0.3.2) - hashdiff - webpacker (6.0.0.beta.7) + hashdiff (>= 0.4.0, < 2.0.0) + webpacker (6.0.0.rc.6) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - webrick (1.8.1) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wicked (1.4.0) railties (>= 3.0.7) - xml-simple (1.1.9) - rexml - xmlrpc (0.3.2) - webrick xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.6.8) zxcvbn (0.1.9) PLATFORMS + arm64-darwin-21 + arm64-darwin-22 ruby x86_64-darwin-21 + x86_64-darwin-22 x86_64-linux DEPENDENCIES @@ -850,7 +703,7 @@ DEPENDENCIES bootscale bootstrap-sass (~> 3.4) browser (= 2.4.0) - capybara (= 3.33) + capybara (~> 3.39.0) carrierwave (~> 1.3) ckeditor codeclimate_circle_ci_coverage @@ -858,7 +711,6 @@ DEPENDENCIES country_select (~> 3.1) curb (= 0.9.10) database_cleaner-active_record - decent_decoration decent_exposure devise (~> 4.7) devise-authy (>= 1.10.0) @@ -869,7 +721,6 @@ DEPENDENCIES email_validator enumerize factory_bot_rails - fog (= 1.42.1) fog-aws gon (>= 6.4.0) govuk-components @@ -878,16 +729,18 @@ DEPENDENCIES jquery-rails (= 4.4.0) jquery-ui-rails (= 6.0.1) js_cookie_rails (= 2.1.4) - json (= 2.3.0) + json kaminari launchy letter_opener listen lograge mail-notify (~> 1.0) + matrix nilify_blanks nokogiri - paper_trail (~> 10.3) + paper_trail (~> 12.2.0) + paper_trail-association_tracking pdf-inspector pg pg_search (~> 2.3.3) @@ -895,14 +748,14 @@ DEPENDENCIES prawn prawn-table pry-byebug - puma (~> 4.3.12) + puma (~> 6.3.1) pundit (~> 0.3) pusher (= 0.15.2) rack-cors (~> 1.0) rack-mini-profiler (>= 0.10.1) rack-protection (= 3.0.5) rack-ssl-enforcer - rails (= 6.1.7.3) + rails (= 7.0.5.1) rails-controller-testing rails-healthcheck rails-html-sanitizer (~> 1.4.4) @@ -920,7 +773,7 @@ DEPENDENCIES rubocop (~> 0.52) rubyXL (~> 3.4) sanitize - sassc-rails (~> 2.0.0) + sassc-rails (~> 2.1.2) selenium-webdriver shog shoulda-matchers @@ -938,13 +791,13 @@ DEPENDENCIES vigilion (~> 1.0.4) vigilion-rails (~> 2.2.0) virtus - webmock (= 3.5.0) - webpacker (= 6.0.0.beta.7) + webmock (= 3.18.1) + webpacker (= 6.0.0.rc.6) websocket-extensions (~> 0.1.5) wicked (~> 1.1) RUBY VERSION - ruby 2.7.7p221 + ruby 3.2.2p53 BUNDLED WITH - 2.4.8 + 2.4.13 diff --git a/README.md b/README.md index 097ee3c9a..79578d481 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ ## Pre-requisites -- Ruby 2.7.7 +- Ruby 3.2.2 - `gem install bundler -v 2.4.8` -- Rails 6.1 +- Rails 7.0.5 - Postgresql 9.5+ - Redis 2.8 - Cloudfountry Client diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 000000000..31485ebf0 Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/icon_load_black.svg b/app/assets/images/icon_load_black.svg new file mode 100644 index 000000000..b8d1ed74b --- /dev/null +++ b/app/assets/images/icon_load_black.svg @@ -0,0 +1,24 @@ + + + + noun_loading_190593 + Created with Sketch. + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/icon_loading_black.svg b/app/assets/images/icon_loading_black.svg new file mode 100644 index 000000000..aa07c1174 --- /dev/null +++ b/app/assets/images/icon_loading_black.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icon_update_black.svg b/app/assets/images/icon_update_black.svg new file mode 100644 index 000000000..73824e58c --- /dev/null +++ b/app/assets/images/icon_update_black.svg @@ -0,0 +1,18 @@ + + + + noun_update_3235036 + Created with Sketch. + + + + + + + + + + + + + diff --git a/app/assets/javascripts/admin/applications-filter.js.coffee b/app/assets/javascripts/admin/applications-filter.js.coffee index df455e626..5547609c7 100644 --- a/app/assets/javascripts/admin/applications-filter.js.coffee +++ b/app/assets/javascripts/admin/applications-filter.js.coffee @@ -1,6 +1,49 @@ ready = -> filterApplicationsDropdowns() + # processing arrow keys in admin dropdowns + $(document).on "keydown", (e) -> + return unless $(".dropdown.open").length > 0 + + if e.keyCode == 40 || e.keyCode == 38 + e.preventDefault() + e.stopPropagation() + + $(document).on "keyup", (e) -> + return unless $(".dropdown.open").length > 0 + + select = $(".dropdown.open").first() + + if e.keyCode == 40 + e.preventDefault() + e.stopPropagation() + + if $(".dropdown-menu li.checkbox input:focus", select).length is 0 + $(".dropdown-menu li.checkbox input", select)[0].focus() + else + element = $(".dropdown-menu li.checkbox input:focus", select).closest("li") + next_element = element.next() + + if next_element.hasClass("divider") + next_element = next_element.next() + + $("input", next_element).focus() + + if e.keyCode == 38 + e.preventDefault() + e.stopPropagation() + + if $(".dropdown-menu li.checkbox input:focus", select).length is 0 + $(".dropdown-menu li.checkbox input", select).last().focus() + else + element = $(".dropdown-menu li.checkbox input:focus", select).closest("li") + prev_element = element.prev() + + if prev_element.hasClass("divider") + prev_element = prev_element.prev() + + $("input", prev_element).focus() + filterApplicationsDropdowns = () -> # Change the checked value on dropbox and the filtered text $(".applications-filter .dropdown-menu").each () -> @@ -29,6 +72,7 @@ filterApplicationsDropdowns = () -> $(this).find("label[data-value='select_all'] input").attr('checked', 'checked') $(this).closest(".dropdown").find(".text-filter").text("All") + $(".applications-filter .dropdown-toggle").on "click", (e) -> e.preventDefault() e.stopPropagation() diff --git a/app/assets/javascripts/admin/comments.js.coffee b/app/assets/javascripts/admin/comments.js.coffee index cab48ec08..1bdc9a03b 100644 --- a/app/assets/javascripts/admin/comments.js.coffee +++ b/app/assets/javascripts/admin/comments.js.coffee @@ -56,8 +56,9 @@ ready = -> else signatureWrapper.html("") - $(@).parents('.comment').remove() + window.fire(@, 'ajax:x:success', null) + $(@).parents('.comment').remove() toggleFlagged() deleteCommentAlert() diff --git a/app/assets/javascripts/admin/dashboard.js.coffee b/app/assets/javascripts/admin/dashboard.js.coffee index c2873378c..c23bcd031 100644 --- a/app/assets/javascripts/admin/dashboard.js.coffee +++ b/app/assets/javascripts/admin/dashboard.js.coffee @@ -18,3 +18,8 @@ jQuery -> $("tbody", wrapper).load href, -> link.removeClass("hidden") $(".updating-data", wrapper).addClass("hidden") + if wrapper.find(".sr-only").text().length == 0 + wrapper.find(".sr-only").text("Data loaded") + else + wrapper.find(".sr-only").empty() + wrapper.find(".sr-only").text("Data updated") diff --git a/app/assets/javascripts/admin/financial_data.js.coffee b/app/assets/javascripts/admin/financial_data.js.coffee index 96df61541..ee147ef99 100644 --- a/app/assets/javascripts/admin/financial_data.js.coffee +++ b/app/assets/javascripts/admin/financial_data.js.coffee @@ -6,13 +6,15 @@ jQuery -> overallBenchmarksTable = ($ "#overall-financial-benchmarks") financialTable = ($ "#financial-table") - $("input", form).on "change keyup keydown paste", -> - timer ||= setTimeout(saveFinancials, 500) - ($ "button", form).on "click", (event) -> do event.preventDefault $(this).closest(".form-group").removeClass("form-edit") + + ($ ".form-save-button", form).on "click", (event) -> + do event.preventDefault + saveFinancials() + updateExportsGrowth = (exports) -> exportsGrowth = ($ 'tr.exports-growth td.value', benchmarksTable) values = exports.map (i, td) -> diff --git a/app/assets/javascripts/admin/form_answers.js.coffee b/app/assets/javascripts/admin/form_answers.js.coffee index d36bc6d88..3dcd60f04 100644 --- a/app/assets/javascripts/admin/form_answers.js.coffee +++ b/app/assets/javascripts/admin/form_answers.js.coffee @@ -54,21 +54,6 @@ ready = -> else $('#commercial-figures-attachment-form').addClass('visuallyhidden') - $("#new_review_audit_certificate").on "ajax:success", (e, data, status, xhr) -> - $(this).find(".form-group").removeClass("form-edit") - $(this).find(".form-edit-link").remove() - $(".save-review-audit").remove() - area = $(".audit-cert-description textarea") - unless area.val() - $(this).find(".form-value").html($("

No change necessary

")) - else - div = "

#{area.val()}

" - $(this).find(".form-value").html(div) - - $("#new_review_audit_certificate").on "click", ".save-review-audit", (e) -> - e.preventDefault() - $("#new_review_audit_certificate").submit() - $(".edit-review-audit").on "click", (e) -> $(".save-review-audit").show() @@ -154,15 +139,17 @@ ready = -> result = $($.parseHTML(result)) $("#commercial-figures-buffer").append(result.text()) + form = document.querySelector('#commercial-figures-attachment-form form') + if $("#commercial-figures-file-valid", $("#commercial-figures-buffer")).length $("#commercial-figures-section").html(result.text()) moveAttachDocumentButton() else - form = $("#commercial-figures-attachment-form form") - section = form.closest("#commercial-figures-section") + section = $(form).closest("#commercial-figures-section") section.find(".document-list .p-empty").addClass("visuallyhidden") section.find(".document-list ul").append(result.text()) + window.fire(form, 'ajax:x:success', null) toggleCommercialFiguresButtonVisibility() $("#commercial-figures-buffer").empty() @@ -179,6 +166,11 @@ ready = -> $.ajax url: form.attr('action'), type: 'DELETE' + success: (_) -> + window.fire(form[0], 'ajax:x:success', null) + error: (_) -> + window.fire(form[0], 'ajax:x:error', null) + form.parents('.commercial-figures-file').remove() if $('.commercial-figures-file').length == 0 section.find(".document-list .p-empty").removeClass("visuallyhidden") @@ -200,16 +192,18 @@ ready = -> result = $($.parseHTML(result)) $("#vat-returns-buffer").append(result.text()) + form = document.querySelector('#vat-returns-attachment-form form') + if $("#vat-returns-file-valid", $("#vat-returns-buffer")).length $("#application-attachment-form").html(result.text()) moveAttachDocumentButton() initializeFileUpload() else - form = $("#vat-returns-attachment-form form") - section = form.closest("#vat-returns-section") + section = $(form).closest("#vat-returns-section") section.find(".document-list .p-empty").addClass("visuallyhidden") section.find(".document-list ul").append(result.text()) + window.fire(form, 'ajax:x:success', null) $("#vat-returns-buffer").empty() $("#vat-returns-attachment-form form").on "fileuploadsubmit", (e, data) -> @@ -225,6 +219,11 @@ ready = -> $.ajax url: form.attr('action'), type: 'DELETE' + success: (_) -> + window.fire(form[0], 'ajax:x:success', null) + error: (_) -> + window.fire(form[0], 'ajax:x:error', null) + form.parents('.vat-returns-file').remove() if $('.vat-returns-file').length == 0 section.find(".document-list .p-empty").removeClass("visuallyhidden") @@ -260,19 +259,21 @@ ready = -> result = $($.parseHTML(result)) $("#attachment-buffer").append(result.text()) + form = document.querySelector('#new_form_answer_attachment') + if $("#form-answer-attachment-valid", $("#attachment-buffer")).length $("#application-attachment-form").html(result.text()) moveAttachDocumentButton() initializeFileUpload() else - form = $("#new_form_answer_attachment") - sidebarSection = form.closest(".sidebar-section") + sidebarSection = $(form).closest(".sidebar-section") sidebarSection.find(".document-list .p-empty").addClass("visuallyhidden") sidebarSection.find(".document-list ul").append(result.text()) sidebarSection.removeClass("show-attachment-form") $("#form_answer_attachment_title").val(null) $("#form_answer_attachment_restricted_to_admin").prop("checked", false) + window.fire(form, 'ajax:x:success', null) $("#attachment-buffer").empty() if $("html").hasClass("lte-ie7") @@ -295,16 +296,18 @@ ready = -> result = $($.parseHTML(result)) $("#audit-certificate-buffer").append(result.text()) + form = document.querySelector('#new_audit_certificate') + if $("#form-audit_certificate-valid", $("#audit-certificate-buffer")).length $("#audit-certificate-form").html(result.text()) moveAttachDocumentButton() initializeFileUpload() else - form = $("#new_audit_certificate") - sidebarSection = form.closest(".sidebar-section") + sidebarSection = $(form).closest(".sidebar-section") sidebarSection.find(".document-list").html(result.text()) sidebarSection.removeClass("show-attachment-form") + window.fire(form, 'ajax:x:success', null) $("#audit-certificate-buffer").empty() moveAttachDocumentButton() @@ -326,6 +329,7 @@ ready = -> $.ajax url: form.attr('action'), type: 'DELETE' + window.fire(form[0], 'ajax:x:success', null) form.parents('.form_answer_attachment').remove() if $('.form_answer_attachment').length == 0 sidebarSection.find(".document-list .p-empty").removeClass("visuallyhidden") @@ -336,29 +340,7 @@ ready = -> if (element) element.classList.add('form-edit') - $('.submit-assessment').on 'ajax:error', (e, data, status, xhr) -> - panel = this.closest('.panel-body') - errors = data.responseJSON - - removeExistingErrorMessages(panel) - - Object.entries(errors).forEach ([key, values]) -> - field = panel.querySelector("[name*='[#{key}]']") - if field and shouldValidateField(field) - showErrorForInvalidField(field, values) - - $(".submit-assessment").on "ajax:success", (e, data, status, xhr) -> - panel = this.closest('.panel-body') - message = "Assessment submitted" - if panel.closest(".panel-collapse").classList.contains('section-case-summary') - message = "Case summary submitted" - - removeExistingErrorMessages(panel) - panel.insertAdjacentHTML('afterbegin', buildBannerHtml(message, 'success')) - - $(this).find('input:submit').remove() - - $(document).on "click", ".form-save-link", (e) -> + $(document).on "click", ".form-save-link:not(.js-form-save-link)", (e) -> link = $(this) e.preventDefault() formGroup = link.closest(".form-group") @@ -393,11 +375,11 @@ ready = -> input.val(updatedSection) form.submit() else - if area.first().val().length - formGroup.find(".form-value p:first").html(area.first().val().replace(/\n/g, '
')) - if area.last().val().length - formGroup.find(".form-value p:last").html(area.last().val().replace(/\n/g, '
')) - form.submit() + if area.first().val().length + formGroup.find(".form-value p:first").html(area.first().val().replace(/\n/g, '
')) + if area.last().val().length + formGroup.find(".form-value p:last").html(area.last().val().replace(/\n/g, '
')) + form.submit() $("#new_review_audit_certificate input[type='radio']").on "change", -> area = $(".audit-cert-description") @@ -461,7 +443,7 @@ buildBannerHtml = (message, type, identifier = null) -> "" @@ -544,6 +526,11 @@ handleWinnersForm = -> $(document).on "ajax:success", attendeeFormHolder, (e, data, status, xhr) -> $(this).closest(attendeeFormHolder).replaceWith(data) + setTimeout (-> + replaced = document.querySelector("##{e.target.id}") + window.fire(replaced, 'ajax:x:success', null) + ), 50 + $(document).on "click", ".remove-palace-attendee", (e) -> e.preventDefault() $(this).closest("form").submit() @@ -552,6 +539,12 @@ handleWinnersForm = -> $(document).on "ajax:success", removeAttendeeForm, (e, data, status, xhr) -> $(this).closest(attendeeFormHolder).remove() $(".attendees-forms").closest(".form-group").find(".empty-message").removeClass("visuallyhidden") + + setTimeout (-> + removed = document.querySelector("##{e.target.id}") + window.fire(removed, 'ajax:x:success', null) + ), 50 + $(document).on "click", ".add-another-attendee", (e) -> e.preventDefault() that = $(this) @@ -636,6 +629,8 @@ handleReviewAuditCertificate = -> else div = "

#{area.val()}

" $(this).find(".form-value").html(div) + window.fire(e.target, 'ajax:x:success', null) + $("#new_review_audit_certificate").on "click", ".save-review-audit", (e) -> e.preventDefault() $("#new_review_audit_certificate").submit() diff --git a/app/assets/javascripts/admin/timeout_popup.js.coffee b/app/assets/javascripts/admin/timeout_popup.js.coffee new file mode 100644 index 000000000..9e63aba6d --- /dev/null +++ b/app/assets/javascripts/admin/timeout_popup.js.coffee @@ -0,0 +1,36 @@ +$(window).load -> + prefix = '/' + window.namespace + hasDisplayedWarning = false + + $(document).on 'click', '.extend-session', (e) -> + e.preventDefault() + $('.js-session-timeout-warning-popup').modal 'hide' + $.post prefix + '/session_checks/extend.json?__t=' + window.lastRequestAt, -> + $('.js-session-timeout-warning-popup').modal 'hide' + hasDisplayedWarning = false + + if window.timeoutTime and window.timeoutTime > 0 + checkInterval = setInterval((-> + $.ajax + url: prefix + '/session_checks.json' + type: 'GET' + success: (data) -> + remaining = Math.round(window.timeoutTime / 60000 - (data.elapsed)) + if remaining == 1 + $('.js-session-timeout-warning-popup .time-target').html ' ' + remaining + ' minute' + else + $('.js-session-timeout-warning-popup .time-target').html ' ' + remaining + ' minutes' + if remaining < 5 and !hasDisplayedWarning + hasDisplayedWarning = true + $('.js-session-timeout-warning-popup').modal + backdrop: 'static' + keyboard: true + complete: (response) -> + if response.status == 401 + clearInterval checkInterval + $('.js-session-timeout-warning-popup').modal 'hide' + $('.js-session-timeout-popup').modal + backdrop: 'static' + keyboard: false + dataType: 'json' + ), 10 * 1000) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 85341422f..f65a9efb3 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -262,6 +262,19 @@ jQuery -> $(".js-conditional-drop-answer input").change () -> dropConditionalQuestion($(this)) + togglePressSummaryTextarea= () -> + if $('[name="press_summary[correct]"]:checked').val() == "true" + $("#press-summary-comment-textarea-container").removeClass("if-js-hide") + else + $("#press-summary-comment-textarea-container").addClass("if-js-hide") + + $('[name="press_summary[correct]"]').on 'change', -> + if $(this).val() is 'true' + $("#press-summary-comment-textarea-container").removeClass("if-js-hide") + else + $("#press-summary-comment-textarea-container").addClass("if-js-hide") + togglePressSummaryTextarea() + # Get the latest financial year date from input updateYearEndInput = () -> fy_latest_changed_input = $(".js-financial-year-changed-dates .fy-latest .govuk-date-input") diff --git a/app/assets/javascripts/frontend/form-validation.js.coffee b/app/assets/javascripts/frontend/form-validation.js.coffee index 6e13747f0..0ef3b4aa2 100644 --- a/app/assets/javascripts/frontend/form-validation.js.coffee +++ b/app/assets/javascripts/frontend/form-validation.js.coffee @@ -159,11 +159,9 @@ window.FormValidation = if subquestions.length for subquestion in subquestions if not @validateSingleQuestion($(subquestion)) - @logThis(question, "validateRequiredQuestion", "This field is required") @addSubfieldError(question, subquestion) else if not @validateSingleQuestion(question) - @logThis(question, "validateRequiredQuestion", "This field is required") @addQuestionError(question) addSubfieldError: (question, subquestion) -> @@ -174,36 +172,58 @@ window.FormValidation = incompleteMessage = "Question #{questionRef} is incomplete. It is required and must be filled in." if question.hasClass('date-DDMMYYYY') - @addErrorMessage($(subquestion), "#{incompleteMessage} Use the format DD/MM/YYYY.") + errorMessage = "#{incompleteMessage} Use the format DD/MM/YYYY." + @logThis(question, "validateRequiredSubQuestion", errorMessage) + @addErrorMessage($(subquestion), errorMessage) else if question.hasClass('date-MMYYYY') - @addErrorMessage($(subquestion), "#{incompleteMessage} Use the format MM/YYYY.") + errorMessage = "#{incompleteMessage} Use the format MM/YYYY." + @logThis(question, "validateRequiredSubQuestion", errorMessage) + @addErrorMessage($(subquestion), errorMessage) else if question.hasClass('date-YYYY') - @addErrorMessage($(subquestion), "#{incompleteMessage} Use the format YYYY.") + errorMessage = "#{incompleteMessage} Use the format YYYY." + @logThis(question, "validateRequiredSubQuestion", errorMessage) + @addErrorMessage($(subquestion), errorMessage) else if input.hasClass("autocomplete__input") - @addErrorMessage($(subquestion), "Question #{questionRef} is incomplete. #{label} is required and an option must be selected from the following dropdown list.") + errorMessage = "Question #{questionRef} is incomplete. #{label} is required and an option must be selected from the following dropdown list." + @logThis(question, "validateRequiredSubQuestion", errorMessage) + @addErrorMessage($(subquestion), errorMessage) else if question.find(".js-financial-year-latest").length #avoid duplicate errors for financial year questions return else - @addErrorMessage($(subquestion), "Question #{questionRef} is incomplete. #{label} is required and must be filled in.") + errorMessage = "Question #{questionRef} is incomplete. #{label} is required and must be filled in." + @logThis(question, "validateRequiredSubQuestion", errorMessage) + @addErrorMessage($(subquestion), errorMessage) addQuestionError: (question) -> questionRef = question.attr("data-question_ref") incompleteMessage = "Question #{questionRef} is incomplete. It is required" if @isOptionsQuestion(question) - @addErrorMessage(question, "#{incompleteMessage} and an option must be chosen from the following list.") + errorMessage = "#{incompleteMessage} and an option must be chosen from the following list." + @logThis(question, "validateRequiredRadioQuestion", errorMessage) + @addErrorMessage(question, errorMessage) else if @isSelectQuestion(question) - @addErrorMessage(question, "#{incompleteMessage} and an option must be selected from the following dropdown list.") + errorMessage = "#{incompleteMessage} and an option must be selected from the following dropdown list." + @logThis(question, "validateRequiredDropdownQuestion", errorMessage) + @addErrorMessage(question, errorMessage) else if @isTextishQuestion(question) && !question.hasClass("question-year") - @addErrorMessage(question, "#{incompleteMessage} and must be filled in.") + errorMessage = "#{incompleteMessage} and must be filled in." + @logThis(question, "validateRequiredTextQuestion", errorMessage) + @addErrorMessage(question, errorMessage) else if question.hasClass("question-year") - @addErrorMessage(question, "#{incompleteMessage} and must be filled in. Use the format YYYY.") + errorMessage = "#{incompleteMessage} and must be filled in. Use the format YYYY." + @logThis(question, "validateRequiredYearQuestion", errorMessage) + @addErrorMessage(question, errorMessage) else if @isCheckboxQuestion(question) if question.find("input[type='checkbox']").length > 1 - @addErrorMessage(question, "#{incompleteMessage} and at least one option must be chosen from the following list.") + errorMessage = "#{incompleteMessage} and at least one option must be chosen from the following list." + @logThis(question, "validateRequiredCheckboxQuestion", errorMessage) + @addErrorMessage(question, errorMessage) else - @addErrorMessage(question, "#{incompleteMessage} and confirmation must be given by ticking the checkbox.") + errorMessage = "#{incompleteMessage} and confirmation must be given by ticking the checkbox." + @logThis(question, "validateRequiredConfirmQuestion", errorMessage) + @addErrorMessage(question, errorMessage) validateMatchQuestion: (question) -> q = question.find(".match") @@ -229,12 +249,14 @@ window.FormValidation = diff = @compareDateInDays(questionDate, expDate) if not questionYear or not questionMonth or not questionDay - @logThis(question, "validateMaxDate", "This field is required") - @addErrorMessage(question, "Question #{questionRef} is incomplete. It is required and must be filled in. Use the format DD/MM/YYYY.") + errorMessage = "Question #{questionRef} is incomplete. It is required and must be filled in. Use the format DD/MM/YYYY." + @logThis(question, "validateMaxDate", errorMessage) + @addErrorMessage(question, errorMessage) return if not @toDate(questionDate).isValid() - @logThis(question, "validateMaxDate", "Not a valid date") - @addErrorMessage(question, "Question #{questionRef} is incomplete. The date entered is not valid. Use the format DD/MM/YYYY.") + errorMessage = "Question #{questionRef} is incomplete. The date entered is not valid. Use the format DD/MM/YYYY." + @logThis(question, "validateMaxDate", errorMessage) + @addErrorMessage(question, errorMessage) return if diff > 0 @@ -470,7 +492,7 @@ window.FormValidation = inputCellsCounter += 1 label = @extractText(subq.attr('id')) if not subq.val() and question.hasClass("question-required") - @logThis(question, "validateNumberByYears", "This field is required") + @logThis(question, "validateNumberByYears", "Question #{questionRef} is incomplete. #{label} is required and must be filled in.") @appendMessage(errContainer, "Question #{questionRef} is incomplete. #{label} is required and must be filled in.") @addErrorClass(question) continue @@ -553,16 +575,18 @@ window.FormValidation = if question.hasClass("question-required") && errorsContainer.length < 1 if !(day and month and year) - @logThis(question, "validateDateByYears", "This field is required") - @appendMessage(qParent, "Question #{questionRef} is incomplete. #{label} is required and must be filled in. Use the format DD/MM/YYYY.") + errorMessage = "Question #{questionRef} is incomplete. #{label} is required and must be filled in. Use the format DD/MM/YYYY." + @logThis(question, "validateDateByYears", errorMessage) + @appendMessage(qParent, errorMessage) @addErrorClass(question) else complexDateString = day + "/" + month + "/" + year date = @toDate(complexDateString) if not date.isValid() - @logThis(question, "validateDateByYears", "Not a valid date") - @appendMessage(qParent, "The date entered for question #{questionRef} #{label} is not valid. Use the format DD/MM/YYYY.") + errorMessage = "The date entered for question #{questionRef} #{label} is not valid. Use the format DD/MM/YYYY." + @logThis(question, errorMessage) + @appendMessage(qParent, errorMessage) @addErrorClass(question) validateInnovationFinancialDate: (question) -> @@ -573,10 +597,13 @@ window.FormValidation = questionDate = "#{questionDay}/#{questionMonth}/#{moment().format('Y')}" if not (questionDay and questionMonth) - @addErrorMessage(question, "Question #{questionRef} is incomplete. Year-end is required and must be filled in. Use the format DD/MM.") + errorMessage = "Question #{questionRef} is incomplete. Year-end is required and must be filled in. Use the format DD/MM." + @logThis(question, errorMessage) + @addErrorMessage(question, errorMessage) else if not @toDate(questionDate).isValid() - @logThis(question, "validateMaxDate", "Not a valid date") - @addErrorMessage(question, "The date entered for #{questionRef} is not valid. Use the format DD/MM.") + errorMessage = "The date entered for #{questionRef} is not valid. Use the format DD/MM." + @logThis(question, errorMessage) + @addErrorMessage(question, errorMessage) return validateDiffBetweenDates: (question) -> @@ -612,8 +639,9 @@ window.FormValidation = err = true if err - @logThis(question, "validateDiffBetweenDates", "There is an error because financial year cannot be longer than 18 months, please double check your year end dates") - @appendMessage(question, "There is an error because financial year cannot be longer than 18 months, please double check your year end dates") + errorMessage = "There is an error because financial year cannot be longer than 18 months, please double check your year end dates" + @logThis(question, "validateDiffBetweenDates", errorMessage) + @appendMessage(question, errorMessage) @addErrorClass(question) validateDateStartEnd: (question) -> @@ -699,8 +727,9 @@ window.FormValidation = else missingOverseasTradeValue = true if totalOverseasTradePercentage.toFixed(2) != (100).toFixed(2) - @logThis(question, "validateGoodsServicesPercentage", "% of your total overseas trade should add up to 100") - @appendMessage(question, "% of your total overseas trade should add up to 100") + errorMessage = "% of your total overseas trade should add up to 100" + @logThis(question, "validateGoodsServicesPercentage", errorMessage) + @appendMessage(question, errorMessage) @addErrorClass(question) validateSelectionLimit: (question) -> @@ -717,7 +746,7 @@ window.FormValidation = logThis: (question, validator, message) -> stepData = question.closest(".js-step-condition").data("step") stepTitle = $.trim($("a.js-step-link[data-step='#{stepData}']").text()) - qRef = $.trim(question.find("h2 span.visuallyhidden").text()) + qRef = question.attr("data-question_ref") qTitle = $.trim(question.find("h2").first().text()) if typeof console != "undefined" diff --git a/app/assets/javascripts/frontend/password-strength-indicator.js b/app/assets/javascripts/frontend/password-strength-indicator.js index 71f7b0791..6448b0dfd 100644 --- a/app/assets/javascripts/frontend/password-strength-indicator.js +++ b/app/assets/javascripts/frontend/password-strength-indicator.js @@ -138,6 +138,7 @@ $(function() { if (someProblem) { $('#password-guidance').removeClass("govuk-!-display-none"); + $('#password-result-span').addClass("hide") } else { $('#password-guidance').addClass("govuk-!-display-none"); } @@ -147,8 +148,10 @@ $(function() { if ($passwordField.val().length == 0) { $passwordField.attr('aria-invalid', "true"); + $('#password-result-span').addClass("hide") } else if ($.inArray('good-password', guidance) >= 0) { $passwordField.attr('aria-invalid', "false"); + $('#password-result-span').removeClass("hide") } else { $passwordField.attr('aria-invalid', "true"); } @@ -160,13 +163,16 @@ $(function() { if ($passwordConfirmationField.val().length == 0) { $passwordConfirmationField.attr('aria-invalid', "true"); + $('#password-confirmation-result-span').addClass("hide") } else if ($.inArray('confirmation-not-matching', guidance) >= 0) { $passwordConfirmationField.attr('aria-invalid', "true"); indicator.parent().removeClass('confirmation-matching'); + $('#password-confirmation-result-span').addClass("hide") } else if ($.inArray('confirmation-matching', guidance) >= 0) { $passwordConfirmationField.attr('aria-invalid', "false"); if($.inArray('good-password', guidance) >= 0) { indicator.parent().addClass('confirmation-matching'); + $('#password-confirmation-result-span').removeClass("hide") } } } diff --git a/app/assets/stylesheets/admin/_pxToRem.scss b/app/assets/stylesheets/admin/_pxToRem.scss new file mode 100644 index 000000000..b5983bee0 --- /dev/null +++ b/app/assets/stylesheets/admin/_pxToRem.scss @@ -0,0 +1,3 @@ +@function pxToRem($value) { + @return $value * 0.0625rem; +} diff --git a/app/assets/stylesheets/admin/_toRem.scss b/app/assets/stylesheets/admin/_toRem.scss new file mode 100644 index 000000000..f648f1ab6 --- /dev/null +++ b/app/assets/stylesheets/admin/_toRem.scss @@ -0,0 +1,6 @@ +@use "sass:math"; + +@function toRem($value) { + $remValue: math.div($value, 16) * 1rem; + @return $remValue; +} diff --git a/app/assets/stylesheets/admin/_toggle.scss b/app/assets/stylesheets/admin/_toggle.scss index 4dfcd1812..8cc3bf517 100644 --- a/app/assets/stylesheets/admin/_toggle.scss +++ b/app/assets/stylesheets/admin/_toggle.scss @@ -1,3 +1,5 @@ +@import "pxToRem"; + .hide { display: none !important; } @@ -17,12 +19,12 @@ .rm { border: 0; clip: rect(0, 0, 0, 0); - height: 1px; - margin: -1px; + height: pxToRem(1); + margin: pxToRem(1); overflow: hidden; padding: 0; position: absolute; - width: 1px; + width: pxToRem(1); z-index: -1; } diff --git a/app/assets/stylesheets/admin/_variables.scss b/app/assets/stylesheets/admin/_variables.scss index 630ac5470..740857467 100644 --- a/app/assets/stylesheets/admin/_variables.scss +++ b/app/assets/stylesheets/admin/_variables.scss @@ -12,7 +12,7 @@ $grey-tab: #aaa; $grey-tab-hover: #666; $grey-panel: #dadad9; $white-true: #fff; -$green: #5C802D; +$green: #3E541D; $amber: #995C00; $red: #A0030E; $blue: #3276b1; @@ -30,3 +30,5 @@ $header-hover: #444; $blue-link-colour: #3264A3; $govuk-light-blue: #5694ca; +$govuk-highlight-yellow: #fd0; +$govuk-warning-red: #d4351c; diff --git a/app/assets/stylesheets/admin/base.scss b/app/assets/stylesheets/admin/base.scss index 1bbe32c8b..66dfdcd4b 100644 --- a/app/assets/stylesheets/admin/base.scss +++ b/app/assets/stylesheets/admin/base.scss @@ -1,14 +1,48 @@ @import "mixins"; +@import "pxToRem"; // Basic a { color: $blue-link-colour; + text-decoration: underline; &:active, - &:focus { + &:focus:not(.btn-default, [role=tab]) { + outline: none !important; + text-decoration: none !important; + border-bottom-color: $black !important; + border-bottom-width: pxToRem(2) !important; + border-bottom-style: solid !important; + } +} + +.btn, button, a, [role=menuitem], .button_to { + &:focus, &:focus-within { + background-color: $govuk-highlight-yellow !important; + border-color: $govuk-highlight-yellow; + color: $black !important; outline: none !important; } } +.search-submit, input[type=search], +input[type=text], input[type=tel], +input[type=email], input[type=password], +input[type=checkbox], select { + &:focus { + outline: pxToRem(3) solid #ffdd00 !important; + outline-offset: 0 !important; + box-shadow: inset 0 0 0 pxToRem(2) !important; + } +} + +.btn-primary, .button_to { + &:focus, &:focus-within { + border-bottom-color: $black !important; + border-bottom-width: pxToRem(2) !important; + border-bottom-style: solid !important; + } +} + span { &.visible-lg { @include screen-lg-min { @@ -45,11 +79,11 @@ span { select, select.form-control { - padding-right: 20px; + padding-right: pxToRem(20); background-image: image-url("icon-arrow-select.png"); background-repeat: no-repeat; background-position: right center; - background-size: 13px 14px; + background-size: pxToRem(13) pxToRem(14); -moz-appearance: none; -webkit-appearance: none; @@ -94,7 +128,7 @@ h4 { } table .ellipsis { - max-width: 175px; + max-width: pxToRem(175); } .admin-page-heading { @@ -111,6 +145,7 @@ table .ellipsis { .btn { border-radius: 0; + text-decoration: none; .icon-view, .glyphicon, @@ -128,7 +163,7 @@ table .ellipsis { } .glyphicon:first-child { - margin-right: 5px; + margin-right: pxToRem(5); } } @@ -182,28 +217,28 @@ table .ellipsis { } .btn-confirm-submit { - font-size: 17px; + font-size: pxToRem(17); } .alert { - padding-top: 10px; - padding-bottom: 10px; + padding-top: pxToRem(10); + padding-bottom: pxToRem(10); border-radius: 0; } .alert-glyphicon { position: relative; - padding-left: 40px; + padding-left: pxToRem(40); .glyphicon { position: absolute; - top: 15px; - left: 15px; + top: pxToRem(15); + left: pxToRem(15); } } .panel-body .alert { - margin-bottom: 30px; + margin-bottom: pxToRem(30); } .page-header { @@ -212,7 +247,7 @@ table .ellipsis { h1 { margin-top: 0; - font-size: 30px; + font-size: pxToRem(30); } } @@ -226,7 +261,7 @@ table .ellipsis { } .pull-right a { - margin-left: 20px; + margin-left: pxToRem(20); } .apps-navigation { @@ -245,21 +280,21 @@ table .ellipsis { .table { width: 99.5%; - margin-left: 1px; + margin-left: pxToRem(1); } .not-eligible { - border-right: 2px solid $black; - width: 75px; + border-right: pxToRem(2) solid $black; + width: pxToRem(75); } .table-total { - width: 80px; + width: pxToRem(80); } .apps-in-proggress { td { - width: 70px; + width: pxToRem(70); } } } @@ -267,7 +302,7 @@ table .ellipsis { .table { &.table-striped tbody { .lte-ie8 & { - border: 2px solid $black-true; + border: pxToRem(2) solid $black-true; border-collapse: initial !important; } } @@ -296,7 +331,7 @@ table .ellipsis { } td { - padding-top: 10px; + padding-top: pxToRem(10); } .td-assessment-submitted { @@ -318,10 +353,10 @@ table .ellipsis { } .ellipsis { - max-width: 350px; + max-width: pxToRem(350); @include screen-md-max { - max-width: 250px; + max-width: pxToRem(250); } } } @@ -343,7 +378,7 @@ table .ellipsis { } .question-group h2, h3 { - font-size: 18px; + font-size: pxToRem(18); font-weight: bold; line-height: 1.5; @@ -377,7 +412,7 @@ table .ellipsis { .field-with-errors { .error { display: block; - margin-bottom: 5px; + margin-bottom: pxToRem(5); color: $error-colour; @include screen-md-min { @@ -391,14 +426,14 @@ table .ellipsis { } .feedback-holder { - margin-bottom: 15px; + margin-bottom: pxToRem(15); } } .govuk-form-group--error { .govuk-error-message { display: block; - margin-bottom: 5px; + margin-bottom: pxToRem(5); color: $error-colour; @include screen-md-min { @@ -412,7 +447,7 @@ table .ellipsis { } .feedback-holder { - margin-bottom: 15px; + margin-bottom: pxToRem(15); } } @@ -420,7 +455,7 @@ table .ellipsis { display: block; .form-group { - margin-right: 36px; + margin-right: pxToRem(36); } .form-control { @@ -430,7 +465,7 @@ table .ellipsis { &, &:first-child, &:last-child { - border-radius: 4px; + border-radius: pxToRem(4); } } @@ -439,16 +474,16 @@ table .ellipsis { position: absolute; right: 0; bottom: 0; - width: 40px; + width: pxToRem(40); height: 2.4em; - border-left: 1px solid $grey-light; + border-left: pxToRem(1) solid $grey-light; line-height: 1.5; z-index: 2; .lte-ie7 & { - top: 1px !important; + top: pxToRem(1) !important; padding: 0 !important; - padding-top: 6px !important; + padding-top: pxToRem(6) !important; } .glyphicon { @@ -480,14 +515,14 @@ table .ellipsis { } .form-actions { - margin-top: 30px; + margin-top: pxToRem(30); .show-sidebar & { margin-top: 0; } .btn { - margin-right: 15px; + margin-right: pxToRem(15); &:last-child { margin-right: 0; @@ -502,23 +537,23 @@ table .ellipsis { .selectable { display: block; position: relative; - margin-top: 10px; - margin-bottom: 10px; - padding: 15px 20px; + margin-top: pxToRem(10); + margin-bottom: pxToRem(10); + padding: pxToRem(15) pxToRem(20); float: none; clear: left; - border: 1px solid $selectable; - border-radius: 4px; + border: pxToRem(1) solid $selectable; + border-radius: pxToRem(4); background-color: $white-true; cursor: pointer; .checkbox & { - padding-left: 35px; + padding-left: pxToRem(35); } } label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { - margin-left: 10px; + margin-left: pxToRem(10); } // Fix boolean inputs @@ -529,12 +564,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } &.control-label { - padding-left: 20px; + padding-left: pxToRem(20); } input.boolean { position: absolute; - top: 14px; + top: pxToRem(14); left: 1em; } @@ -545,17 +580,17 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } &.field-with-errors .checkbox { - left: 20px; + left: pxToRem(20); } &.selectable-group { .selectable { - padding-left: 40px; + padding-left: pxToRem(40); } .checkbox { - top: 15px; - left: 15px; + top: pxToRem(15); + left: pxToRem(15); margin-top: 0; } } @@ -563,16 +598,16 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .hint { display: block; - margin-top: 10px; - margin-bottom: 10px; + margin-top: pxToRem(10); + margin-bottom: pxToRem(10); color: $grey-dark; - font-size: 14px; + font-size: pxToRem(14); } .password-meter { max-width: none; - margin-top: 5px; - margin-right: -80px; + margin-top: pxToRem(5); + margin-right: pxToRem(-80); @include screen-sm-max { margin-right: 0; @@ -602,11 +637,11 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon { - margin-right: 5px; + margin-right: pxToRem(5); } li { - margin-bottom: 10px; + margin-bottom: pxToRem(10); @extend .clearfix; &:last-child { @@ -615,13 +650,13 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .action-title { display: block; - margin-right: 110px; + margin-right: pxToRem(110); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; .lte-ie8 & { - max-width: 200px; + max-width: pxToRem(200); margin-right: 0; float: left; } @@ -640,7 +675,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { overflow: hidden; .lte-ie8 & { - max-height: 25px; + max-height: pxToRem(25); } input[type="file"] { @@ -651,7 +686,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { left: auto; width: 100%; cursor: pointer; - font-size: 200px; + font-size: pxToRem(200); opacity: 0; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter: alpha(opacity=0); @@ -665,7 +700,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { // Alerts .alert-container { - margin-bottom: 30px; + margin-bottom: pxToRem(30); z-index: 10001; @include screen-sm-min { @@ -673,18 +708,18 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } @include screen-md-min { - max-width: 970px; + max-width: pxToRem(970); } @include screen-lg-min { - max-width: 1170px; + max-width: pxToRem(1170); } .alert { margin: 0; .lte-ie7 & { - max-width: 1170px; + max-width: pxToRem(1170); text-align: left; } } @@ -700,8 +735,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .icon-unflagged, .icon-check { display: inline-block; - width: 30px; - height: 30px; + width: pxToRem(30); + height: pxToRem(30); text-align: left; text-indent: -9999px; background-repeat: no-repeat; @@ -714,12 +749,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .icon-comment { position: relative; - width: 25px; - height: 18px; - top: 3px; + width: pxToRem(25); + height: pxToRem(18); + top: pxToRem(3); background-image: image-url("icon-comment-sm.png"); background-position: left top; - background-size: 20px 18px; + background-size: pxToRem(20) pxToRem(18); @include is-retina { background-image: image-url("icon-comment-sm@2.png"); @@ -727,28 +762,28 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .comment-count { position: absolute; - top: -6px; - left: 23px; + top: pxToRem(-6); + left: pxToRem(23); color: $black; - font-size: 10px; + font-size: pxToRem(10); font-weight: bold; text-indent: 0; } } .icon-comment-wrapper + .icon-flagged { - margin-left: 10px; + margin-left: pxToRem(10); display: inline-block; } .icon-flagged { position: relative; - width: 25px; - height: 18px; - top: 3px; + width: pxToRem(25); + height: pxToRem(18); + top: pxToRem(3); background-image: image-url("icon-flag-sm.png"); background-position: left top; - background-size: 12px 15px; + background-size: pxToRem(12) pxToRem(15); @include is-retina { background-image: image-url("icon-flag-sm@2.png"); @@ -764,10 +799,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .flag-count { position: absolute; - top: -6px; - left: 13px; + top: pxToRem(-6); + left: pxToRem(13); color: $black; - font-size: 10px; + font-size: pxToRem(10); font-weight: bold; text-indent: 0; } @@ -776,7 +811,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .icon-unflagged { background-image: image-url("icon-flag-sm-grey.png"); background-position: left top; - background-size: 12px 15px; + background-size: pxToRem(12) pxToRem(15); @include is-retina { background-image: image-url("icon-flag-sm-grey@2.png"); @@ -785,12 +820,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .icon-check { position: relative; - width: 20px; - height: 20px; - top: -6px; + width: pxToRem(20); + height: pxToRem(20); + top: pxToRem(-6); background-image: image-url("icon-check.png"); background-position: center center; - background-size: 20px 14px; + background-size: pxToRem(20) pxToRem(14); vertical-align: -webkit-baseline-middle; @include is-retina { @@ -803,12 +838,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .icon-view { - width: 18px; - min-height: 16px; + width: pxToRem(18); + min-height: pxToRem(16); background-image: image-url("icon-computer-sm.png"); background-repeat: no-repeat; background-position: center center; - background-size: 18px 16px; + background-size: pxToRem(18) pxToRem(16); text-align: left; text-indent: -9999px; opacity: 0.8; @@ -829,12 +864,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .btn & { position: relative; - top: 2px; - width: 20px; - height: 16px; - margin-right: 5px; + top: pxToRem(2); + width: pxToRem(20); + height: pxToRem(16); + margin-right: pxToRem(5); background-image: image-url("icon-computer-sm.png"); - background-size: 18px 16px; + background-size: pxToRem(18) pxToRem(16); opacity: 1; @include is-retina { @@ -854,13 +889,13 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { position: relative !important; .lte-ie8 & { - width: 350px; + width: pxToRem(350); overflow: hidden; } .form_answers-index-page & { @include screen-sm-max { - margin-top: 15px; + margin-top: pxToRem(15); } } @@ -868,7 +903,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { position: relative; .lte-ie8 & { - width: 350px; + width: pxToRem(350); } .lte-ie7 & { @@ -878,8 +913,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { [type="search"], input.form-control { - padding-right: 40px; - font-size: 16px; + padding-right: pxToRem(40); + font-size: pxToRem(16); font-style: italic; position: relative; z-index: 10; @@ -890,7 +925,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { top: 0; right: 0; bottom: 0; - width: 30px; + width: pxToRem(30); margin: 0; padding: 0; border: none; @@ -898,7 +933,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { background-image: image-url("icon-search.png"); background-repeat: no-repeat; background-position: center center; - background-size: 15px 21px; + background-size: pxToRem(15) pxToRem(21); text-align: left !important; text-indent: -9999px; white-space: nowrap; @@ -921,10 +956,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { // Sortable Tables .sortable { a { - padding-right: 16px; + padding-right: pxToRem(16); background-repeat: no-repeat; background-position: right center; - background-size: 10px 6px; + background-size: pxToRem(10) pxToRem(6); color: $black; background-image: image-url("icon-arrow-sort-down-grey.png"); text-decoration: none; @@ -975,13 +1010,13 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { // Filterable Dropdowns .filter { .dropdown-toggle { - padding: 4px 10px; + padding: pxToRem(4) pxToRem(10); color: $grey-dark; font-size: inherit; .lte-ie7 & { position: relative; - border-bottom: 1px solid $grey-light; + border-bottom: pxToRem(1) solid $grey-light; zoom: 1; } @@ -995,7 +1030,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .caret { - margin-left: 5px; + margin-left: pxToRem(5); } } @@ -1004,11 +1039,11 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { margin: 0; &.divider { - margin: 5px 0; + margin: pxToRem(5) 0; .lte-ie7 & { width: 100%; - height: 1px; + height: pxToRem(1); background: $grey-light; } } @@ -1025,15 +1060,15 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } input { - margin-top: 11px; - margin-left: 10px; + margin-top: pxToRem(11); + margin-left: pxToRem(10); } .label-contents { display: block; position: relative; - padding: 7px 10px; - padding-left: 30px; + padding: pxToRem(7) pxToRem(10); + padding-left: pxToRem(30); z-index: 1; } @@ -1046,8 +1081,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { // Sidebar .show-main-content, .show-sidebar-container { - padding-right: 15px; - padding-left: 15px; + padding-right: pxToRem(15); + padding-left: pxToRem(15); float: left; } @@ -1069,12 +1104,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .show-sidebar { position: relative; - padding: 15px; + padding: pxToRem(15); background: $white-true; zoom: 1; @include screen-md-max { - margin-bottom: 30px; + margin-bottom: pxToRem(30); } h1, @@ -1085,45 +1120,45 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon { - margin-right: 5px; + margin-right: pxToRem(5); } } h1 { - font-size: 32px; + font-size: pxToRem(32); @include screen-md-max { - font-size: 24px; + font-size: pxToRem(24); } } h2 { - margin-bottom: 20px; - font-size: 16px; + margin-bottom: pxToRem(20); + font-size: pxToRem(16); font-weight: bold; text-transform: uppercase; @include screen-md-max { - font-size: 15px; + font-size: pxToRem(15); } .pull-right { @include screen-md-max { display: inline-block; width: 100%; - margin-top: 10px; + margin-top: pxToRem(10); float: none !important; } } } h3 { - margin-bottom: 20px; - font-size: 16px; + margin-bottom: pxToRem(20); + font-size: pxToRem(16); font-weight: bold; @include screen-md-max { - font-size: 15px; + font-size: pxToRem(15); } } @@ -1131,16 +1166,16 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { display: block; margin-top: 0.25em; color: $grey-dark; - font-size: 14px; + font-size: pxToRem(14); text-transform: none; } .well { margin: 0; - padding: 20px 0; + padding: pxToRem(20) 0; background: transparent; border: none; - border-bottom: 1px solid $grey; + border-bottom: pxToRem(1) solid $grey; border-radius: 0; box-shadow: none; @@ -1155,8 +1190,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .well { padding: 19px; - border: 1px solid $grey; - border-radius: 4px; + border: pxToRem(1) solid $grey; + border-radius: pxToRem(4); } } @@ -1166,12 +1201,12 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .form-cancel-link:last-child { - margin-right: 15px; + margin-right: pxToRem(15); } } .sidebar-section { - margin-bottom: 45px; + margin-bottom: pxToRem(45); &:last-child { margin-bottom: 0; @@ -1190,7 +1225,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .comment { position: relative; - margin-bottom: 15px; + margin-bottom: pxToRem(15); &:first-child { padding-top: 0; @@ -1199,11 +1234,11 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .comment-header { - margin-bottom: 5px; + margin-bottom: pxToRem(5); h3 { margin: 0; - font-size: 16px; + font-size: pxToRem(16); font-weight: bold; line-height: 1.5; } @@ -1211,22 +1246,22 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .comment-action { display: block; - margin-top: 10px; + margin-top: pxToRem(10); color: $grey-dark; - font-size: 14px; + font-size: pxToRem(14); .divider { - margin: 0 8px; + margin: 0 pxToRem(8); } } .link-flag-comment { - margin-left: 3px; - padding-left: 17px; + margin-left: pxToRem(3); + padding-left: pxToRem(17); background-image: image-url("icon-flag-sm-grey.png"); background-repeat: no-repeat; background-position: left center; - background-size: 12px 15px; + background-size: pxToRem(12) pxToRem(15); @include is-retina { background-image: image-url("icon-flag-sm-grey@2.png"); @@ -1276,8 +1311,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .link-delete-comment { display: inline-block; position: relative; - padding-right: 15px; - font-size: 14px; + padding-right: pxToRem(15); + font-size: pxToRem(14); font-weight: normal; .show-delete-comment & { @@ -1288,13 +1323,13 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { position: absolute; top: 50%; right: 0; - width: 10px; - height: 10px; - margin-top: -5px; + width: pxToRem(10); + height: pxToRem(10); + margin-top: pxToRem(-5); background-image: image-url("icon-close-comment.png"); background-repeat: no-repeat; background-position: right; - background-size: 10px 10px; + background-size: pxToRem(10) pxToRem(10); @include is-retina { background-image: image-url("icon-close-comment@2.png"); @@ -1330,20 +1365,20 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { width: 100%; top: 50%; left: 0; - margin-top: -36px; + margin-top: pxToRem(-36); } } .comment-insert { - margin-top: 15px; + margin-top: pxToRem(15); } .comment-actions { - margin-top: 10px; + margin-top: pxToRem(10); .link-flag-comment { display: inline-block; - margin-top: 5px; + margin-top: pxToRem(5); } input[type="checkbox"] { @@ -1374,10 +1409,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } tbody { - border: 2px solid $black; + border: pxToRem(2) solid $black; > tr > th { - border-right: 2px solid $black; + border-right: pxToRem(2) solid $black; } } @@ -1399,22 +1434,37 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { border: none; border-radius: 0; background-color: $grey-panel; + + &:focus-within { + background-color: $govuk-highlight-yellow; + border-bottom-color: $black !important; + border-bottom-width: pxToRem(2) !important; + border-bottom-style: solid !important; + } } .panel-title { font-weight: bold; text-transform: uppercase; - font-size: 16px; + font-size: pxToRem(16); line-height: 1.1 !important; > a { - padding-left: 25px; + padding-left: pxToRem(25); background-image: image-url("icon-arrow-collapse-down.png"); background-repeat: no-repeat; background-position: 0 0.4em; - background-size: 12px 8px; + background-size: pxToRem(12) pxToRem(8); text-decoration: none; + &:focus { + border-bottom: none !important; + + small { + color: $black !important; + } + } + @include is-retina { background-image: image-url("icon-arrow-collapse-down@2.png"); } @@ -1435,18 +1485,18 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { small { display: inline-block; width: 100%; - margin-top: 10px; - padding-left: 25px; + margin-top: pxToRem(10); + padding-left: pxToRem(25); color: $grey-dark; - font-size: 14px; + font-size: pxToRem(14); font-style: italic; text-transform: none; @include screen-md-max { display: inline-block; width: 100%; - margin-top: 10px; - padding-left: 25px; + margin-top: pxToRem(10); + padding-left: pxToRem(25); float: none !important; } @@ -1469,8 +1519,16 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { a { background-image: image-url("icon-arrow-collapse-down-white.png"); + &:focus { + background-image: image-url("icon-arrow-collapse-down.png") !important; + } + @include is-retina { background-image: image-url("icon-arrow-collapse-down-white@2.png"); + + &:focus { + background-image: image-url("icon-arrow-collapse-down@2.png") !important; + } } } } @@ -1478,8 +1536,16 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { &.expanded > .panel-title a { background-image: image-url("icon-arrow-collapse-up-white.png"); + &:focus { + background-image: image-url("icon-arrow-collapse-up.png") !important; + } + @include is-retina { background-image: image-url("icon-arrow-collapse-up-white@2.png"); + + &:focus { + background-image: image-url("icon-arrow-collapse-up@2.png") !important; + } } } } @@ -1493,8 +1559,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .form-label-rag { display: block; position: relative; - margin-top: 10px; - margin-bottom: 10px; + margin-top: pxToRem(10); + margin-bottom: pxToRem(10); @include screen-xs-max { margin-top: 0; @@ -1517,7 +1583,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .btn-rag { position: absolute; right: 0; - top: -5px; + top: pxToRem(-5); @include screen-xs-max { display: block; @@ -1541,23 +1607,23 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { position: absolute; top: 50%; right: 0; - width: 20px; - height: 20px; - margin-top: -10px; - background-size: 20px 20px; + width: pxToRem(20); + height: pxToRem(20); + margin-top: pxToRem(-10); + background-size: pxToRem(20) pxToRem(20); text-align: left; text-indent: -9999px; } &.rag-editable .icon-rag { - right: 15px; + right: pxToRem(15); } .caret { position: absolute; top: 50%; right: 0; - margin-top: -2px; + margin-top: pxToRem(-2); } .dropdown-toggle, @@ -1567,7 +1633,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .dropdown-toggle { - padding-right: 25px; + padding-right: pxToRem(25); padding-left: 0; @include screen-xs-max { @@ -1588,7 +1654,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } &.rag-editable .dropdown-toggle { - padding-right: 40px; + padding-right: pxToRem(40); } .dropdown-menu { @@ -1605,9 +1671,9 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } a { - padding-top: 6px; - padding-bottom: 6px; - padding-left: 37px; + padding-top: pxToRem(6); + padding-bottom: pxToRem(6); + padding-left: pxToRem(37); @include screen-xs-max { white-space: normal; @@ -1615,10 +1681,15 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .icon-rag { - left: 12px; + left: pxToRem(12); } } + .rag-error { + background-color: #f2dede; + color: #a94442 !important; + } + .rag-blank { &, a, @@ -1731,7 +1802,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .form-control { @include screen-sm-max { - margin-bottom: 10px; + margin-bottom: pxToRem(10); } } } @@ -1741,7 +1812,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { @include screen-sm-max { margin-top: 0; - margin-bottom: 10px; + margin-bottom: pxToRem(10); } } } @@ -1751,7 +1822,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { background: none; border: none; display: block; - padding: 3px 20px; + padding: pxToRem(3) pxToRem(20); clear: both; font-weight: normal; line-height: 1.42857143; @@ -1770,7 +1841,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .page-header-nav .btn-link.pull-left, .search-form h2 .btn-link { - margin-right: 30px; + margin-right: pxToRem(30); padding-right: 0; padding-left: 0; } @@ -1794,10 +1865,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-paperclip { - width: 16px; - height: 16px; + width: pxToRem(16); + height: pxToRem(16); background-image: image-url("icon-attach.png"); - background-size: 16px 16px; + background-size: pxToRem(16) pxToRem(16); @include is-retina { background-image: image-url("icon-attach@2.png"); @@ -1805,10 +1876,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-pencil { - width: 15px; - height: 13px; + width: pxToRem(15); + height: pxToRem(13); background-image: image-url("icon-edit-black.png"); - background-size: 15px 13px; + background-size: pxToRem(15) pxToRem(13); @include is-retina { background-image: image-url("icon-edit-black@2.png"); @@ -1816,10 +1887,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-file { - width: 12px; - height: 16px; + width: pxToRem(12); + height: pxToRem(16); background-image: image-url("icon-file.png"); - background-size: 12px 16px; + background-size: pxToRem(12) pxToRem(16); @include is-retina { background-image: image-url("icon-file@2.png"); @@ -1827,10 +1898,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-info-sign { - width: 15px; - height: 15px; + width: pxToRem(15); + height: pxToRem(15); background-image: image-url("icon-info.png"); - background-size: 15px 15px; + background-size: pxToRem(15) pxToRem(15); @include is-retina { background-image: image-url("icon-info@2.png"); @@ -1838,10 +1909,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-download-alt { - width: 15px; - height: 16px; + width: pxToRem(15); + height: pxToRem(16); background-image: image-url("icon-download.png"); - background-size: 15px 16px; + background-size: pxToRem(15) pxToRem(16); &.black { background-image: image-url("icon-download-black.png"); @@ -1857,10 +1928,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-user { - width: 14px; - height: 14px; + width: pxToRem(14); + height: pxToRem(14); background-image: image-url("icon-user.png"); - background-size: 14px 14px; + background-size: pxToRem(14) pxToRem(14); @include is-retina { background-image: image-url("icon-user@2.png"); @@ -1868,10 +1939,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-ok { - width: 15px; - height: 12px; + width: pxToRem(15); + height: pxToRem(12); background-image: image-url("icon-valid.png"); - background-size: 15px 12px; + background-size: pxToRem(15) pxToRem(12); @include is-retina { background-image: image-url("icon-valid@2.png"); @@ -1896,10 +1967,10 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .glyphicon-remove { - width: 11px; - height: 11px; + width: pxToRem(11); + height: pxToRem(11); background-image: image-url("icon-invalid.png"); - background-size: 11px 11px; + background-size: pxToRem(11) pxToRem(11); @include is-retina { background-image: image-url("icon-invalid@2.png"); @@ -1934,8 +2005,9 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .award-year-dropdown { - max-width: 142px; - margin-left: 120px; + max-width: pxToRem(142); + margin-left: pxToRem(120); + margin: 0 !important; } &.award-year--vertical { @@ -1958,9 +2030,9 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { right: 0; bottom: 0; left: auto; - padding-right: 10px; - padding-left: 10px; - border-left: 1px solid $less-black; + padding-right: pxToRem(10); + padding-left: pxToRem(10); + border-left: pxToRem(1) solid $less-black; background: $black-header; .caret { @@ -1982,8 +2054,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .btn:active .caret-container { outline: 0; background-image: none; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + -webkit-box-shadow: inset 0 pxToRem(3) pxToRem(5) rgba(0, 0, 0, 0.125); + box-shadow: inset 0 pxToRem(3) pxToRem(5) rgba(0, 0, 0, 0.125); } .dropdown-menu { @@ -1992,16 +2064,16 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { z-index: 2000; > li > a { - padding-left: 12px; + padding-left: pxToRem(12); } } } .glyphicon-star, .glyphicon-star-empty { - width: 16px; - height: 15px; - background-size: 16px 15px; + width: pxToRem(16); + height: pxToRem(15); + background-size: pxToRem(16) pxToRem(15); } .glyphicon-star { @@ -2028,7 +2100,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .char-space { display: inline-block; - width: 160px; + width: pxToRem(160); height: 1em; } @@ -2041,13 +2113,13 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .char-text-limit { - font-size: 14px; + font-size: pxToRem(14); margin-top: 0.5em; margin-bottom: 0.2em; text-align: right; } .char-text { - font-size: 14px; + font-size: pxToRem(14); margin-top: 0.2em; text-align: right; @@ -2057,13 +2129,17 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { } .nav-subnav { - margin-bottom: 15px; - border-bottom: 4px solid $black-header; + margin-bottom: pxToRem(15); + border-bottom: pxToRem(4) solid $black-header; ul { margin: 0; padding: 0; list-style: none; + display: flex; + @include screen-sm-max { + display: block; + } } li { @@ -2082,8 +2158,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { a { display: block; - padding: 10px 15px; - padding-bottom: 15px; + padding: pxToRem(10) pxToRem(15); + padding-bottom: pxToRem(15); float: left; color: $black; text-align: center; @@ -2091,8 +2167,8 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { text-transform: uppercase; @include screen-md-max { - padding: 8px 10px; - padding-bottom: 15px; + padding: pxToRem(8) pxToRem(10); + padding-bottom: pxToRem(15); } @include screen-sm-max { @@ -2103,7 +2179,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .active a { background: transparent image-url("icon-arrow-admin-subnav.png") no-repeat center 110%; - background-size: 24px 12px; + background-size: pxToRem(24) pxToRem(12); color: $blue-link-colour; @include is-retina { @@ -2139,3 +2215,7 @@ label.govuk-label.govuk-checkboxes__label.boolean.optional.govuk-label { .space-y-3 > :not([hidden]) ~ :not([hidden]) { margin-top: 1.5rem; } + +.alert button.close { + font-size: pxToRem(18); +} diff --git a/app/assets/stylesheets/admin/bootstrap-overrides.scss b/app/assets/stylesheets/admin/bootstrap-overrides.scss new file mode 100644 index 000000000..472b85c36 --- /dev/null +++ b/app/assets/stylesheets/admin/bootstrap-overrides.scss @@ -0,0 +1,18 @@ +html { font-size: 100% } +body { font-size: inherit !important; } + +.btn { + font-size: 1rem; +} + +.form-control { + font-size: 1rem +} + +.dropdown-menu { + font-size: 1rem; +} + +.navbar-nav { + display: flex; +} diff --git a/app/assets/stylesheets/admin/date-time-picker.scss b/app/assets/stylesheets/admin/date-time-picker.scss index b21e5dc51..81b86709f 100644 --- a/app/assets/stylesheets/admin/date-time-picker.scss +++ b/app/assets/stylesheets/admin/date-time-picker.scss @@ -1,15 +1,16 @@ +@import "pxToRem"; // Date/time Picker Inputs .datepicker, .timepicker { display: inline-block; - padding-left: 35px; + padding-left: pxToRem(35); background-repeat: no-repeat; - background-position: 7px 7px; - background-size: 18px 18px; + background-position: pxToRem(7) pxToRem(7); + background-size: pxToRem(18) pxToRem(18); } .datepicker { - width: 135px; + width: pxToRem(135); background-image: image-url("icon-date-picker.png"); @include is-retina { @@ -21,13 +22,13 @@ } .lte-ie7 & { - width: 80px !important; + width: pxToRem(80) !important; } } .timepicker { - width: 95px; - margin-left: 10px; + width: pxToRem(95); + margin-left: pxToRem(10); background-image: image-url("icon-time-picker.png"); @include is-retina { @@ -36,7 +37,7 @@ @include screen-sm-max { width: 100%; - margin-top: 10px; + margin-top: pxToRem(10); margin-left: 0; } @@ -49,26 +50,26 @@ .time-picker { display: none; position: absolute; - min-width: 95px; - max-height: 200px; - margin: 2px 0 0; - padding: 5px 0; + min-width: pxToRem(95); + max-height: pxToRem(200); + margin: pxToRem(2) 0 0; + padding: pxToRem(5) 0; float: left; - border: 1px solid $grey-light; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; + border: pxToRem(1) solid $grey-light; + border: pxToRem(1) solid rgba(0, 0, 0, 0.15); + border-radius: pxToRem(4); background-color: $white-true; - font-size: 14px; + font-size: pxToRem(14); list-style: none; text-align: left; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 pxToRem(6) pxToRem(12) rgba(0, 0, 0, 0.175); overflow: auto; z-index: 99; background-clip: padding-box; li { display: block; - padding: 3px 20px; + padding: pxToRem(3) pxToRem(20); clear: both; cursor: pointer; color: $black; @@ -99,18 +100,18 @@ body .ui-datepicker { top: 100%; left: 0; width: auto; - min-width: 160px; - margin: 2px 0 0; - padding: 4px; + min-width: pxToRem(160); + margin: pxToRem(2) 0 0; + padding: pxToRem(4); float: left; - border: 1px solid $grey-light; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; + border: pxToRem(1) solid $grey-light; + border: pxToRem(1) solid rgba(0, 0, 0, 0.15); + border-radius: pxToRem(4); background-color: $white-true; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; + font-size: pxToRem(14); list-style: none; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 pxToRem(6) pxToRem(12) rgba(0, 0, 0, 0.175); z-index: 1000; background-clip: padding-box; @@ -121,39 +122,39 @@ body .ui-datepicker { .ui-datepicker-prev, .ui-datepicker-next { - width: 30px; - height: 30px; + width: pxToRem(30); + height: pxToRem(30); border: none; - border-radius: 4px; + border-radius: pxToRem(4); text-align: center; &:hover { - top: 2px; + top: pxToRem(2); border: none; background: $grey-lightest; } } .ui-datepicker-prev:hover { - left: 2px; + left: pxToRem(2); } .ui-datepicker-next:hover { - right: 2px; + right: pxToRem(2); } .ui-datepicker-title { - width: 145px; - height: 30px; - margin: 0 35px; + width: pxToRem(145); + height: pxToRem(30); + margin: 0 pxToRem(35); border: none; - border-radius: 4px; + border-radius: pxToRem(4); text-align: center; } .ui-datepicker-calendar { .lte-ie7 & { - width: 200px !important; + width: pxToRem(200) !important; } } @@ -163,11 +164,11 @@ body .ui-datepicker { th, td a { - width: 30px; - height: 30px; - padding: 5px; + width: pxToRem(30); + height: pxToRem(30); + padding: pxToRem(5); border: none; - border-radius: 4px; + border-radius: pxToRem(4); text-align: center; } diff --git a/app/assets/stylesheets/admin/forms.scss b/app/assets/stylesheets/admin/forms.scss index cdc503797..1f12f10b9 100644 --- a/app/assets/stylesheets/admin/forms.scss +++ b/app/assets/stylesheets/admin/forms.scss @@ -1,15 +1,27 @@ +@import "pxToRem"; + .selectable-group { input[type='checkbox'], input[type='radio'] { margin-left: 0; - top: 15px; - left: 15px; + top: pxToRem(15); + left: pxToRem(15); z-index: 999; } } .has-royal-family-connections-wrapper { - padding-left: 20px; + padding-left: pxToRem(20); +} + +input.form-control, select.form-control, +.input-group-addon, textarea, +.btn-default { + border-color: #5C5C5C !important; +} + +.input-group-addon { + height: 2.42em !important; } input.form-control, select.form-control, diff --git a/app/assets/stylesheets/admin/header-footer.scss b/app/assets/stylesheets/admin/header-footer.scss index 227771b72..1f5c0ee7a 100644 --- a/app/assets/stylesheets/admin/header-footer.scss +++ b/app/assets/stylesheets/admin/header-footer.scss @@ -1,4 +1,5 @@ @import "mixins"; +@import "pxToRem"; #site-header { -webkit-font-smoothing: antialiased; @@ -6,39 +7,38 @@ *z-index: 1000; @include screen-lg-max { - font-size: 14px; + font-size: pxToRem(14); } @include screen-md-max { - font-size: 13px; + font-size: pxToRem(13); } @include screen-sm-max { - font-size: 14px; + font-size: pxToRem(14); } .lte-ie8 & { background: $black-header; clear: both; - height: 70px; - margin-bottom: 40px !important; + height: pxToRem(70); + margin-bottom: pxToRem(40) !important; } .caret { - margin-left: 5px; - border-top-width: 7px; - border-right-width: 6px; - border-left-width: 6px; + margin-left: pxToRem(5); + border-top-width: pxToRem(7); + border-right-width: pxToRem(6); + border-left-width: pxToRem(6); @include screen-md-max { - margin-left: 3px; - border-top-width: 5px; - border-right-width: 4px; - border-left-width: 4px; + margin-left: pxToRem(3); + border-top-width: pxToRem(5); + border-right-width: pxToRem(4); + border-left-width: pxToRem(4); } } - .container { .lte-ie8 & { width: 90% !important; @@ -53,8 +53,8 @@ } .navbar-toggle { - margin-top: 16px; - margin-bottom: 16px; + margin-top: pxToRem(16); + margin-bottom: pxToRem(16); .icon-bar { background-color: $white-true; @@ -68,7 +68,7 @@ .navbar-collapse { .lte-ie8 & { display: block !important; - margin-left: 50px; + margin-left: pxToRem(50); } &.collapse { @@ -80,13 +80,13 @@ } .navbar { - margin-bottom: 40px; + margin-bottom: pxToRem(40); border: none; border-radius: 0; background: $black-header; - + .lte-ie7 & { - height: 81px !important; + height: pxToRem(81) !important; z-index: 100001 !important; } @@ -99,20 +99,27 @@ } } - .navbar-nav > li > a { - padding-top: 24px; - padding-bottom: 23px; + .navbar-nav > li > a, + .navbar-nav > li > details { + padding-top: pxToRem(24); + padding-bottom: pxToRem(23); color: $white-true; text-transform: uppercase; background: transparent; + text-decoration: none; @include screen-md-max { - padding-right: 8px; - padding-left: 8px; + padding-right: pxToRem(8); + padding-left: pxToRem(8); } @include screen-sm-max { - padding: 20px 15px; + padding: pxToRem(20) pxToRem(15); + } + + > .dropdown-menu { + display: block; + text-transform: none; } } } @@ -121,7 +128,7 @@ .lte-ie8 & { float: left !important; position: relative; - top: -10px; + top: pxToRem(-10); } &.navbar-right { @@ -150,22 +157,23 @@ margin-right: 0; } - > a { + > a, > details { .lte-ie8 & { display: block !important; - padding-top: 26px !important; - padding-bottom: 26px !important; + padding-top: pxToRem(26) !important; + padding-bottom: pxToRem(26) !important; color: $white-true !important; } .lte-ie7 & { - padding-top: 30px !important; - padding-bottom: 30px !important; + padding-top: pxToRem(30) !important; + padding-bottom: pxToRem(30) !important; } &:hover, &:focus { background-color: $header-hover; + cursor: pointer; @include is-ios { background-color: transparent; @@ -179,9 +187,26 @@ } } + > details { + padding: 0 pxToRem(10); + + &:focus-within { + background-color: $govuk-highlight-yellow !important; + color: $black !important; + outline: none !important; + border-bottom-color: $black !important; + border-bottom-width: pxToRem(2) !important; + border-bottom-style: solid !important; + + summary { + outline: none !important; + } + } + } + &.active > a { background: transparent image-url("icon-arrow-admin-nav.png") no-repeat center bottom; - background-size: 24px 12px; + background-size: pxToRem(24) pxToRem(12); @include is-retina { background-image: image-url("icon-arrow-admin-nav@2.png"); @@ -206,32 +231,40 @@ z-index: 999999 !important; } - > li > span { + > li > span, + > li > .button_to { display: block; - padding: 3px 20px; + padding: pxToRem(3) pxToRem(20); clear: both; font-weight: normal; line-height: 1.42857143; color: #333333; white-space: nowrap; + + @include screen-sm-max { + padding: 0; + } } > li > a, - > li > span { + > li > span, + > li > .button_to > input[type="submit"] { @include screen-sm-max { - padding: 15px; - padding-left: 35px; + padding: pxToRem(15); + padding-left: pxToRem(35); color: $white-true; text-transform: uppercase; + text-align: left; } .lte-ie8 & { - padding: 7px 10px !important; + padding: pxToRem(7) pxToRem(10) !important; color: $black !important; } } - > li > a { + > li > a, + > li > .button_to { &:hover, &:focus { @include screen-sm-max { @@ -243,6 +276,26 @@ } } } + + .button_to { + display: inline; + + input[type="submit"] { + background: none; + padding: 0; + border: none; + color: black; + text-decoration: underline; + + .lte-ie7 & { + font-size: pxToRem(16); + } + } + + &:hover { + background-color: $grey-lightest; + } + } } .open .dropdown-toggle { @@ -250,15 +303,14 @@ background-color: $header-hover !important; } } - + // moving down here seems to make it work .navbar-brand { - padding-top: 14px; - padding-bottom: 0; - text-align: left; + padding: pxToRem(14); + height: 100%; img { - width: 28px; - height: 33px; + width: pxToRem(28); + height: pxToRem(33); } } } diff --git a/app/assets/stylesheets/admin/layout.scss b/app/assets/stylesheets/admin/layout.scss index b492a89f2..b6c00a614 100644 --- a/app/assets/stylesheets/admin/layout.scss +++ b/app/assets/stylesheets/admin/layout.scss @@ -1,6 +1,8 @@ @import "mixins"; -$footer-height: 60px; -$footer-height-sm: 30px; +@import "pxToRem"; + +$footer-height: pxToRem(60); +$footer-height-sm: pxToRem(30); html, body, @@ -56,7 +58,7 @@ body { font-weight: bold; color: #fff; position: relative; - top: -40px; + top: pxToRem(-40); } .dev-banner { @@ -69,7 +71,7 @@ body { .admin-assessor-js-status-banner { position: relative; - top: -40px; + top: pxToRem(-40); padding: 0.5em; text-align: center; background-color: #fff2d3; @@ -78,15 +80,15 @@ body { .container { .lte-ie8 & { - max-width: 1170px; + max-width: pxToRem(1170); } } .row { .lte-ie8 & { display: block; - margin-right: -15px; - margin-left: -15px; + margin-right: pxToRem(-15); + margin-left: pxToRem(-15); clear: both; } @@ -111,9 +113,9 @@ body { .lte-ie8 & { float: left; position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; + min-height: pxToRem(1); + padding-right: pxToRem(15); + padding-left: pxToRem(15); } .lte-ie7 & { diff --git a/app/assets/stylesheets/admin/page-applications.scss b/app/assets/stylesheets/admin/page-applications.scss index 4b2fb6d42..1553e5ac5 100644 --- a/app/assets/stylesheets/admin/page-applications.scss +++ b/app/assets/stylesheets/admin/page-applications.scss @@ -1,17 +1,18 @@ @import "mixins"; +@import "pxToRem"; .panel-subtitle-small { - margin-bottom: 5px; + margin-bottom: pxToRem(5); } .applications-filter { - margin-bottom: 20px; + margin-bottom: pxToRem(20); .applications-filter__label, .search-input__label { display: block; text-transform: uppercase; - padding-right: 10px; - padding-top: 7px; + padding-right: pxToRem(10); + padding-top: pxToRem(7); @include screen-md-max { text-align: left; @@ -19,14 +20,14 @@ @include screen-sm-max { min-width: 0; - padding-right: 20px; + padding-right: pxToRem(20); } } .dropdown { position: relative; display: inline-block; - padding-right: 28px; + padding-right: pxToRem(28); width: 100%; vertical-align: bottom; @@ -35,7 +36,7 @@ overflow: hidden; max-width: 100%; text-overflow: ellipsis; - line-height: 19px; + line-height: pxToRem(19); } .caret-container { @@ -44,9 +45,9 @@ right: 0; bottom: 0; left: auto; - padding-right: 10px; - padding-left: 10px; - border-left: 1px solid #262626; + padding-right: pxToRem(10); + padding-left: pxToRem(10); + border-left: pxToRem(1) solid #262626; background: #1c1c1b; .caret { @@ -56,26 +57,22 @@ } .dropdown-menu { - min-width: 250px; + min-width: pxToRem(250); border-color: #666666; li.checkbox, li.apply { - padding: 0 10px; - - .btn:focus { - border-color: #5C5C5C; - } + padding: 0 pxToRem(10); } } } } .search-text { - margin-bottom: 30px; - padding: 10px 15px; + margin-bottom: pxToRem(30); + padding: pxToRem(10) pxToRem(15); background-color: $grey-lighter; - font-size: 18px; + font-size: pxToRem(18); font-style: italic; p { @@ -90,8 +87,8 @@ .applications-table, .admin-table { > tbody > tr > td { - padding-top: 13px; - padding-bottom: 12px; + padding-top: pxToRem(13); + padding-bottom: pxToRem(12); a:not(.link-edit-user) { color: $blue-link-colour; } @@ -103,7 +100,7 @@ } > tbody > tr:first-child > td { - padding-top: 16px; + padding-top: pxToRem(16); } } @@ -128,9 +125,9 @@ } .nav-tabs.submitted-tabs { - margin-bottom: 20px; + margin-bottom: pxToRem(20); border-bottom: none; - font-size: 11px; + font-size: pxToRem(11); .no-js & { display: none; @@ -139,18 +136,18 @@ > li { > a { margin: 0; - margin-left: 5px; + margin-left: pxToRem(5); &, &:focus { - padding: 3px 10px; - border: 2px solid $grey-tab; - border-radius: 100px; + padding: pxToRem(3) pxToRem(10); + border: pxToRem(2) solid $grey-tab; + border-radius: pxToRem(100); color: $grey-tab; } &:hover { - border: 2px solid $grey-tab-hover; + border: pxToRem(2) solid $grey-tab-hover; background: transparent; color: $grey-tab-hover; } @@ -161,12 +158,12 @@ position: absolute; bottom: 0; left: 50%; - width: 8px; - height: 4px; - margin-bottom: -6px; - margin-left: -4px; + width: pxToRem(8); + height: pxToRem(4); + margin-bottom: pxToRem(-6); + margin-left: pxToRem(-4); background-image: image-url("icon-arrow-tab.png"); - background-size: 8px 4px; + background-size: pxToRem(8) pxToRem(4); @include is-retina { background-image: image-url("icon-arrow-tab@2.png"); @@ -179,7 +176,7 @@ &, &:hover, &:focus { - border: 2px solid #666; + border: pxToRem(2) solid #666; color: #666; } } @@ -192,7 +189,7 @@ .no-js .tab-content > .tab-pane { display: block; - margin-bottom: 30px; + margin-bottom: pxToRem(30); visibility: visible; &:last-child { @@ -202,7 +199,7 @@ .tab-header { display: none; - margin-bottom: 15px; + margin-bottom: pxToRem(15); .no-js & { display: block; @@ -211,12 +208,12 @@ .label-small { display: block; - font-size: 14px; + font-size: pxToRem(14); text-align: left; } .good-services-list li { - margin-bottom: 20px; + margin-bottom: pxToRem(20); &:last-child { margin-bottom: 0; @@ -227,19 +224,19 @@ display: none; color: gray; font-style: italic; - font-size: 14px; - margin-bottom: 10px; + font-size: pxToRem(14); + margin-bottom: pxToRem(10); } .inline-form-view { - margin-top: 15px; + margin-top: pxToRem(15); .form-control { - margin-bottom: 5px; + margin-bottom: pxToRem(5); } .form-group .row { - margin-bottom: 10px; + margin-bottom: pxToRem(10); } .no-js & { @@ -260,7 +257,7 @@ .control-label { display: block; - font-size: 14px; + font-size: pxToRem(14); text-align: left; } @@ -268,17 +265,17 @@ .form-fields, .char-count { display: none; - min-width: 100px; + min-width: pxToRem(100); } .form-control-day, .form-control-month, .form-control-year { - margin-right: 10px; + margin-right: pxToRem(10); float: left; .form-control { - width: 50px; + width: pxToRem(50); text-align: center; } } @@ -286,13 +283,13 @@ .form-control-day, .form-control-month { .form-control { - min-width: 50px; + min-width: pxToRem(50); } } .form-control-year { .form-control { - min-width: 70px; + min-width: pxToRem(70); } } @@ -329,7 +326,7 @@ } .form-container { - padding: 10px; + padding: pxToRem(10); background: $grey-lighter; .char-count.char-max-shift { @@ -338,7 +335,7 @@ } .form-group-multiple-parent { - margin-bottom: 15px; + margin-bottom: pxToRem(15); &:last-child { margin-bottom: 0; @@ -366,25 +363,25 @@ } .form-edit-link { - margin-top: 10px; + margin-top: pxToRem(10); color: $black; - font-size: 16px; + font-size: pxToRem(16); .glyphicon { - margin-right: 5px; - font-size: 12px; + margin-right: pxToRem(5); + font-size: pxToRem(12); } } .show-sidebar { .form-save-button, .form-save-link { - margin-top: 10px; + margin-top: pxToRem(10); } ul.other-applications-list { li { - margin-bottom: 20px; + margin-bottom: pxToRem(20); } } } @@ -397,22 +394,22 @@ h2 { margin-top: 0; - margin-bottom: 10px; - font-size: 24px; + margin-bottom: pxToRem(10); + font-size: pxToRem(24); font-weight: normal; text-transform: none; } p { - font-size: 18px; + font-size: pxToRem(18); @include screen-md-max { - font-size: 16px; + font-size: pxToRem(16); } } .btn { - margin-top: 20px; + margin-top: pxToRem(20); } } @@ -420,35 +417,35 @@ h2, p { - margin-bottom: 5px; + margin-bottom: pxToRem(5); } p { - font-size: 15px; + font-size: pxToRem(15); &.p-lg { - font-size: 20px; + font-size: pxToRem(20); } } .btn-group { display: block; - margin-bottom: 5px; + margin-bottom: pxToRem(5); } .dropdown-toggle { display: inline-block; padding: 0; - padding-right: 20px; + padding-right: pxToRem(20); float: none; color: $black; - font-size: 20px; + font-size: pxToRem(20); text-align: left; text-decoration: none; background-image: image-url("icon-arrow-admin-dropdown-down.png"); background-repeat: no-repeat; background-position: right center; - background-size: 12px 9px; + background-size: pxToRem(12) pxToRem(9); @include is-retina { background-image: image-url("icon-arrow-admin-dropdown-down@2.png"); @@ -464,12 +461,11 @@ } .state-container { - position: relative; + display: flex; + justify-content: space-between; h2 { - position: absolute; - top: 0.4em; - left: 0; + margin-top: 0.35rem; @include screen-lg-max { position: static; @@ -501,7 +497,7 @@ li { position: relative; - margin-bottom: 20px; + margin-bottom: pxToRem(20); clear: both; &:last-child { @@ -511,7 +507,7 @@ label { display: block; - font-size: 14px; + font-size: pxToRem(14); font-weight: bold; text-transform: uppercase; } @@ -526,7 +522,7 @@ @include screen-md-max { width: auto; - margin-left: 120px; + margin-left: pxToRem(120); } } @@ -543,15 +539,15 @@ top: 0; right: 0; margin-top: 0; - font-size: 14px; + font-size: pxToRem(14); .lte-ie7 & { - margin-top: -20px; + margin-top: pxToRem(-20); } } .form-save-button { - margin-bottom: 10px; + margin-bottom: pxToRem(10); float: right; clear: both; } @@ -561,10 +557,17 @@ display: inline; } } + + .alert ~ form > .form-edit-link { + display: flex; + float: right; + margin-top: pxToRem(-54); + position: relative; + } } .centered { - padding-left: 25px; + padding-left: pxToRem(25); } .audit-cert-no-change-val, @@ -589,13 +592,13 @@ } .form-group .list-attendees li .row { - margin-bottom: 20px; + margin-bottom: pxToRem(20); } .bulk-assign-assessors-link { position: relative; - left: -9999px; - margin-top: 10px; + left: pxToRem(-9999); + margin-top: pxToRem(10); background-color: $blue-link-colour; color: white; @@ -603,6 +606,12 @@ left: 0; } + &:hover { + color: #fff; + background-color: $govuk-light-blue; + border-color: #204d74 + } + .show-bulk-assign & { .js & { display: none; @@ -611,9 +620,9 @@ } .bulk-assign-assessors-form { - max-width: 755px; - margin-top: 25px; - margin-bottom: 10px; + max-width: pxToRem(755); + margin-top: pxToRem(25); + margin-bottom: pxToRem(10); .js & { display: none; @@ -626,7 +635,7 @@ } h2 { - font-size: 18px; + font-size: pxToRem(18); font-weight: bold; line-height: 1.5; } @@ -640,7 +649,7 @@ } .btn { - margin-left: 10px; + margin-left: pxToRem(10); } .well { @@ -670,7 +679,7 @@ zoom: 1; .form-group { - margin-top: 15px; + margin-top: pxToRem(15); .form-control { display: block; diff --git a/app/assets/stylesheets/admin/page-custom-emails.scss b/app/assets/stylesheets/admin/page-custom-emails.scss index 0e534cae6..20363b445 100644 --- a/app/assets/stylesheets/admin/page-custom-emails.scss +++ b/app/assets/stylesheets/admin/page-custom-emails.scss @@ -1,3 +1,5 @@ +@import "pxToRem"; + .email-send-to, .email-send-subject { h3 { @@ -6,11 +8,11 @@ } .email-send-to { - margin-top: 40px; + margin-top: pxToRem(40); } .email-send-subject { - margin-top: 20px; + margin-top: pxToRem(20); } .email-send-to-help { diff --git a/app/assets/stylesheets/admin/page-dashboard.scss b/app/assets/stylesheets/admin/page-dashboard.scss index bab4e9feb..3c6a65a86 100644 --- a/app/assets/stylesheets/admin/page-dashboard.scss +++ b/app/assets/stylesheets/admin/page-dashboard.scss @@ -1,6 +1,4 @@ -.dashboard { - -} +@import "pxToRem"; .download-link { display: inline-block; @@ -11,8 +9,8 @@ &:before { content: " "; display: block; - width: 18px; - height: 16px; + width: pxToRem(18); + height: pxToRem(16); background-image: image-url("icon_download.svg"); background-repeat: no-repeat; background-size: 100% auto; @@ -20,7 +18,7 @@ position: absolute; left: 0; - top: 2px; + top: pxToRem(2); } } @@ -32,15 +30,11 @@ .award-year-label { text-transform: none !important; display: block; - margin-bottom: 10px; + margin-bottom: pxToRem(10); position: static; font-weight: normal; margin-top: 0; - font-size: 18px; - } - - .award-year-dropdown { - margin: 0 !important; + font-size: pxToRem(18); } } @@ -48,7 +42,7 @@ margin-top: 2em; color: $blue-link-colour; - @media (min-width: 992px) { + @media (min-width: pxToRem(992)) { margin-top: 0; position: absolute !important; top: 1.5em; @@ -75,7 +69,7 @@ .dashboard__section { padding: 2em 0; - border-top: 2px solid #ccc; + border-top: pxToRem(2) solid #ccc; position: relative; } @@ -89,8 +83,8 @@ content: " "; display: block; position: absolute; - top: 9px; - left: 10px; + top: pxToRem(9); + left: pxToRem(10); background-size: 1em auto; background-repeat: no-repeat; background-position: center; @@ -98,7 +92,7 @@ height: 1em; } - @media (min-width: 768px) { + @media (min-width: pxToRem(768)) { position: absolute; right: 0; top: 3em; @@ -108,26 +102,44 @@ opacity: 0.7; } - &.btn--load:before { - background-image: image-url("icon_load.svg"); + &.btn--load { + + &:before { + background-image: image-url("icon_load.svg"); + } + + &:focus:before { + background-image: image-url("icon_load_black.svg"); + } } - &.btn--reload:before { - background-image: image-url("icon_update.svg"); + &.btn--reload { + &:before { + background-image: image-url("icon_update.svg"); + } + + &:focus:before { + background-image: image-url("icon_update_black.svg"); + } } - &.btn--loading:before { - background-image: image-url("icon_loading.svg"); - background-size: 1.15em auto; - width: 1.2em; - height: 1.5em; - top: 7px; + &.btn--loading { + &:before { + background-image: image-url("icon_loading.svg"); + background-size: 1.15em auto; + width: 1.2em; + height: 1.5em; + top: pxToRem(7); + } + + &:focus:before { + background-image: image-url("icon_loading_black.svg"); + } } } } .dashboard-table { - table-layout: fixed; overflow-x: scroll; border-collapse: collapse; @@ -152,22 +164,22 @@ position: relative; font-weight: bold; - @media (min-width: 768px) { - padding-right: 200px; + @media (min-width: pxToRem(768)) { + padding-right: pxToRem(200); } &:before { content: " "; display: block; - width: 23px; - height: 23px; + width: pxToRem(23); + height: pxToRem(23); background-image: image-url("icon_warning.svg"); background-size: 100% auto; background-position: center; background-repeat: no-repeat; position: absolute; left: 0; - top: -1px; + top: pxToRem(-1); } } } @@ -184,7 +196,7 @@ } &.border-right { - border-right: 2px solid black; + border-right: pxToRem(2) solid black; } } @@ -203,7 +215,7 @@ tbody { th { padding: 0.5em 1em; - border-right: 2px solid black; + border-right: pxToRem(2) solid black; font-weight: normal; } td { @@ -219,11 +231,11 @@ } .dashboard-table--small { - font-size: 14px; + font-size: pxToRem(14); th { - font-size: 12px; - padding: 1em 3px !important; + font-size: pxToRem(12); + padding: 1em pxToRem(3) !important; } td { @@ -231,12 +243,12 @@ } thead tr:first-of-type th { - padding-top: 2px !important; + padding-top: pxToRem(2) !important; } } .dashboard-table__wrapper { - @media (max-width: 1199px) { + @media (max-width: pxToRem(1199)) { overflow-x: scroll; } } @@ -249,7 +261,7 @@ .downloads-page__section { h2 { - font-size: 24px; + font-size: pxToRem(24); font-weight: bold; color: black; } @@ -263,13 +275,13 @@ li { padding: 1.2em 0 1.2em 1.5em; position: relative; - border-bottom: 1px solid #ccc; + border-bottom: pxToRem(1) solid #ccc; &:before { content: " "; display: block; - width: 11px; - height: 20px; + width: pxToRem(11); + height: pxToRem(20); background-image: image-url("icon_file.svg"); background-repeat: no-repeat; background-size: 100% auto; @@ -285,7 +297,7 @@ margin-top: 1.5em; color: $blue-link-colour; - @media (min-width: 992px) { + @media (min-width: pxToRem(992)) { margin-top: 0; position: absolute; top: 1.2em; @@ -297,19 +309,19 @@ color: black; font-size: 1.11111em; - @media (min-width: 992px) { - margin-right: 200px; + @media (min-width: pxToRem(992)) { + margin-right: pxToRem(200); } } .download-item__description { font-size: 1.11111em; - max-width: 700px; + max-width: pxToRem(700); margin-bottom: 0; color: $grey-panel-dark; - @media (min-width: 992px) { - margin-right: 200px; + @media (min-width: pxToRem(992)) { + margin-right: pxToRem(200); } } } diff --git a/app/assets/stylesheets/admin/page-feedback.scss b/app/assets/stylesheets/admin/page-feedback.scss index ef510af40..e53e687d8 100644 --- a/app/assets/stylesheets/admin/page-feedback.scss +++ b/app/assets/stylesheets/admin/page-feedback.scss @@ -1,10 +1,12 @@ +@import "pxToRem"; + .feedback-stars { white-space: nowrap; } .feedback-description, .feedback-table .read-more-container { - font-size: 14px; + font-size: pxToRem(14); } .read-more-container { diff --git a/app/assets/stylesheets/admin/page-settings.scss b/app/assets/stylesheets/admin/page-settings.scss index da82ee915..12781189f 100644 --- a/app/assets/stylesheets/admin/page-settings.scss +++ b/app/assets/stylesheets/admin/page-settings.scss @@ -1,10 +1,12 @@ +@import "pxToRem"; + .settings-page { .panel-body ul { margin: 0; padding-left: 1.25em; li { - margin-bottom: 10px; + margin-bottom: pxToRem(10); &:last-child { margin-bottom: 0; @@ -13,8 +15,8 @@ } .panel-section { - padding: 15px 0; - border-bottom: 1px solid $grey; + padding: pxToRem(15) 0; + border-bottom: pxToRem(1) solid $grey; &:first-child { padding-top: 0; @@ -33,7 +35,7 @@ @include screen-sm-max { display: inline; width: auto; - margin-right: 10px; + margin-right: pxToRem(10); } } @@ -42,13 +44,13 @@ } .edit-deadline { - margin-right: 10px; + margin-right: pxToRem(10); color: $blue-link-colour; text-decoration: underline; } .email-notification-help { - margin-left: 10px; + margin-left: pxToRem(10); } .deadline-help, .email-notification-help { @@ -58,10 +60,10 @@ .help-message { position: absolute; - top: -13px; + top: pxToRem(-13); left: 0; - width: 15px; - height: 15px; + width: pxToRem(15); + height: pxToRem(15); display: inline-block; &:hover .help-message-text { @@ -71,21 +73,21 @@ .help-message-text { position: absolute; z-index: 1; - top: -5px; + top: pxToRem(-5); left: 105%; visibility: hidden; background-color: #fff; text-align: center; - padding: 5px; - width: 300px; - border: 1px solid #ccc; - border-radius: 6px; + padding: pxToRem(5); + width: pxToRem(300); + border: pxToRem(1) solid #ccc; + border-radius: pxToRem(6); } } } .btn-add-schedule { - margin: 20px 0; + margin: pxToRem(20) 0; } .link-email-example { @@ -95,8 +97,8 @@ .well { display: block; - max-width: 440px; - margin: 20px 0; + max-width: pxToRem(440); + margin: pxToRem(20) 0; background-color: $white-true; box-shadow: none; @@ -108,6 +110,7 @@ max-width: none; a { text-decoration: underline; + overflow-wrap: break-word; } } @@ -125,7 +128,7 @@ } .control-date { - margin-right: 20px; + margin-right: pxToRem(20); @include screen-sm-max { margin-right: 0; @@ -137,7 +140,7 @@ } .control-hint { - margin-bottom: 10px; + margin-bottom: pxToRem(10); display: inline-block; } @@ -155,7 +158,7 @@ } &:first-child { - margin-right: 10px; + margin-right: pxToRem(10); @include screen-sm-max { margin-right: 0; @@ -165,14 +168,14 @@ .btn-submit { @include screen-sm-max { - margin-bottom: 45px; + margin-bottom: pxToRem(45); } } .btn-cancel { @include screen-sm-max { position: absolute; - top: 45px; + top: pxToRem(45); } } } @@ -203,14 +206,14 @@ border: none; .lte-ie7 & { - font-size: 16px; + font-size: pxToRem(16); } } } } .deadline-form-wrapper { - @media (min-width: 768px) { + @media (min-width: pxToRem(768)) { &:after { clear: both; content: " "; diff --git a/app/assets/stylesheets/admin/page-users.scss b/app/assets/stylesheets/admin/page-users.scss index cac748aa0..12da9e6c8 100644 --- a/app/assets/stylesheets/admin/page-users.scss +++ b/app/assets/stylesheets/admin/page-users.scss @@ -1,3 +1,5 @@ +@import "pxToRem"; + .form-collaborator { .well { display: none; @@ -34,11 +36,11 @@ } .js-admin-search-collaborators-error-box { - margin-bottom: 10px; + margin-bottom: pxToRem(10); } .js-admin-search-collaborators-results-box { - margin-bottom: 20px; + margin-bottom: pxToRem(20); } .cant_add_to_collaborators_message { @@ -46,26 +48,26 @@ } .admin-user-debounce-api-check-block { - padding-left: 25px; - font-size: 14px; + padding-left: pxToRem(25); + font-size: pxToRem(14); .heading-message { - margin-bottom: 5px; + margin-bottom: pxToRem(5); display: inline-block; } img { - width: 20px; - height: 20px; - margin-right: 10px; + width: pxToRem(20); + height: pxToRem(20); + margin-right: pxToRem(10); } .next-run-info { - margin-top: 7px; + margin-top: pxToRem(7); display: inline-block; } a { - margin-top: 7px; + margin-top: pxToRem(7); } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/application-admin.scss b/app/assets/stylesheets/application-admin.scss index 1a4466fc7..557769c28 100644 --- a/app/assets/stylesheets/application-admin.scss +++ b/app/assets/stylesheets/application-admin.scss @@ -15,6 +15,7 @@ $button-colour: #00823b; @import "frontend/password_guidance"; @import "frontend/status"; +@import "admin/bootstrap-overrides"; @import "admin/variables"; @import "admin/toggle"; @import "admin/layout"; @@ -59,6 +60,5 @@ $todo-black: #333; &:focus { top: 20px; left: 20px; - color: white !important; } } diff --git a/app/assets/stylesheets/frontend/layout.scss b/app/assets/stylesheets/frontend/layout.scss index 34ad63f11..769a92fa3 100644 --- a/app/assets/stylesheets/frontend/layout.scss +++ b/app/assets/stylesheets/frontend/layout.scss @@ -628,6 +628,7 @@ header.page-header aside .inner { &.sidebar-helpline { a { text-decoration: underline; + overflow-wrap: break-word; } } } diff --git a/app/controllers/admin/admins_controller.rb b/app/controllers/admin/admins_controller.rb index 65753238b..d33e05489 100644 --- a/app/controllers/admin/admins_controller.rb +++ b/app/controllers/admin/admins_controller.rb @@ -20,7 +20,7 @@ def create @resource.save - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) location = @resource.persisted? ? admin_admins_path : nil respond_with :admin, @resource, location: location @@ -35,7 +35,7 @@ def update @resource.update_without_password(resource_params) end - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_admins_path end @@ -44,7 +44,7 @@ def destroy authorize @resource, :destroy? @resource.soft_delete! - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_admins_path end diff --git a/app/controllers/admin/assessors_controller.rb b/app/controllers/admin/assessors_controller.rb index f41faa68b..7408e8cb0 100644 --- a/app/controllers/admin/assessors_controller.rb +++ b/app/controllers/admin/assessors_controller.rb @@ -21,7 +21,7 @@ def create @resource.save location = @resource.persisted? ? admin_assessors_path : nil - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: location end @@ -35,7 +35,7 @@ def update @resource.update_without_password(resource_params) end - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_assessors_path end @@ -44,7 +44,7 @@ def destroy authorize @resource, :destroy? @resource.soft_delete! - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_assessors_path end diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 8ea7ef5bc..43d10a6c4 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -22,7 +22,7 @@ def current_subject def render_flash_message_for(resource, message: nil) if resource.errors.any? - flash[:error] = message || "An unknown error has occurred, please try again." + flash.now[:error] = message || "An unknown error has occurred, please try again." else flash[:notice] = message || "Success!" end diff --git a/app/controllers/admin/custom_emails_controller.rb b/app/controllers/admin/custom_emails_controller.rb index 438461a96..49e1be858 100644 --- a/app/controllers/admin/custom_emails_controller.rb +++ b/app/controllers/admin/custom_emails_controller.rb @@ -14,7 +14,7 @@ def create @form = CustomEmailForm.new(custom_email_form_attributes) if @form.valid? - CustomEmailWorker.perform_async(custom_email_form_attributes) + CustomEmailForm.new(custom_email_form_attributes.permit!.to_h.with_indifferent_access).send! render_flash_message_for(@form) redirect_to admin_custom_email_path, notice: "Email was successfully scheduled" else diff --git a/app/controllers/admin/dashboard_reports_controller.rb b/app/controllers/admin/dashboard_reports_controller.rb index 432a3fcf0..8e0133b1d 100644 --- a/app/controllers/admin/dashboard_reports_controller.rb +++ b/app/controllers/admin/dashboard_reports_controller.rb @@ -10,44 +10,56 @@ class Admin::DashboardReportsController < Admin::BaseController # that represents the type of the application def all_applications - authorize :dashboard, :reports? - - @report = Reports::Dashboard::ApplicationsReport.new(kind: params[:kind]) - render partial: "admin/dashboard/totals_#{params[:kind]}/table_body" + render_or_download_report nil, params[:kind], params[:format] end def international_trade - authorize :dashboard, :reports? - - @report = Reports::Dashboard::ApplicationsReport.new(kind: params[:kind], award_type: :trade) - render partial: "admin/dashboard/totals_#{params[:kind]}/table_body" + render_or_download_report :trade, params[:kind], params[:format] end def innovation - authorize :dashboard, :reports? - - @report = Reports::Dashboard::ApplicationsReport.new(kind: params[:kind], award_type: :innovation) - render partial: "admin/dashboard/totals_#{params[:kind]}/table_body" + render_or_download_report :innovation, params[:kind], params[:format] end def social_mobility - authorize :dashboard, :reports? - - @report = Reports::Dashboard::ApplicationsReport.new(kind: params[:kind], award_type: :mobility) - render partial: "admin/dashboard/totals_#{params[:kind]}/table_body" + render_or_download_report :mobility, params[:kind], params[:format] end def sustainable_development - authorize :dashboard, :reports? - - @report = Reports::Dashboard::ApplicationsReport.new(kind: params[:kind], award_type: :development) - render partial: "admin/dashboard/totals_#{params[:kind]}/table_body" + render_or_download_report :development, params[:kind], params[:format] end def account_registrations authorize :dashboard, :reports? @report = Reports::Dashboard::UsersReport.new(kind: params[:kind]) - render partial: "admin/dashboard/totals_#{params[:kind]}/users_table_body" + + respond_to do |format| + format.csv do + send_data @report.as_csv, filename: @report.csv_filename + end + + format.html do + render partial: "admin/dashboard/totals_#{params[:kind]}/users_table_body" + end + end + end + + private + + def render_or_download_report award_type, kind, format + authorize :dashboard, :reports? + + @report = Reports::Dashboard::ApplicationsReport.new(kind: kind, award_type: award_type) + + respond_to do |format| + format.csv do + send_data @report.as_csv, filename: @report.csv_filename + end + + format.html do + render partial: "admin/dashboard/totals_#{kind}/table_body" + end + end end end diff --git a/app/controllers/admin/judges_controller.rb b/app/controllers/admin/judges_controller.rb index 5ee3060f8..3f174d28f 100644 --- a/app/controllers/admin/judges_controller.rb +++ b/app/controllers/admin/judges_controller.rb @@ -21,7 +21,7 @@ def create @resource.save location = @resource.persisted? ? admin_judges_path : nil - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: location end @@ -35,7 +35,7 @@ def update @resource.update_without_password(resource_params) end - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_judges_path end @@ -44,7 +44,7 @@ def destroy authorize @resource, :destroy? @resource.soft_delete! - render_flash_message_for(@resource) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_judges_path end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 3c32cae33..ac21e9a4a 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -30,7 +30,7 @@ def show type: "application/pdf", disposition: 'attachment' else - redirect_to pdf.data + redirect_to pdf.data, allow_other_host: true end end end diff --git a/app/controllers/admin/session_checks_controller.rb b/app/controllers/admin/session_checks_controller.rb index 0a0510a67..5ba9cc867 100644 --- a/app/controllers/admin/session_checks_controller.rb +++ b/app/controllers/admin/session_checks_controller.rb @@ -1,6 +1,18 @@ class Admin::SessionChecksController < ActionController::Base include SessionStatusCheckMixin + def show + if session_is_valid? + render json: { elapsed: elapsed }, status: :ok + else + head :unauthorized + end + end + + def extend + super + end + private def namespace diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index b42612ab4..da90ff36a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -28,7 +28,7 @@ def create @resource.save location = @resource.persisted? ? admin_users_path : nil - render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.full_messages.join(", ")) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: location end @@ -42,7 +42,7 @@ def update @resource.update_without_password(resource_params) end - render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.full_messages.join(", ")) + render_flash_message_for(@resource, message: @resource.errors.none? ? nil : @resource.errors.messages.values.flatten.uniq.join("
")) respond_with :admin, @resource, location: admin_users_path end diff --git a/app/controllers/assessor/base_controller.rb b/app/controllers/assessor/base_controller.rb index c1bfb1560..1fd0b17de 100644 --- a/app/controllers/assessor/base_controller.rb +++ b/app/controllers/assessor/base_controller.rb @@ -21,7 +21,7 @@ def current_subject def render_flash_message_for(resource, message: nil) if resource.errors.any? - flash[:error] = message || "An unknown error has occurred, please try again." + flash.now[:error] = message || "An unknown error has occurred, please try again." else flash[:notice] = message || "Success!" end diff --git a/app/controllers/assessor/reports_controller.rb b/app/controllers/assessor/reports_controller.rb index d5cd7fa90..3b68cae27 100644 --- a/app/controllers/assessor/reports_controller.rb +++ b/app/controllers/assessor/reports_controller.rb @@ -24,7 +24,7 @@ def show type: "application/pdf", disposition: 'attachment' else - redirect_to pdf.data + redirect_to pdf.data, allow_other_host: true end end end diff --git a/app/controllers/assessor/session_checks_controller.rb b/app/controllers/assessor/session_checks_controller.rb index fe2f0f382..9f56f9fc4 100644 --- a/app/controllers/assessor/session_checks_controller.rb +++ b/app/controllers/assessor/session_checks_controller.rb @@ -1,6 +1,18 @@ class Assessor::SessionChecksController < ActionController::Base include SessionStatusCheckMixin + def show + if session_is_valid? + render json: { elapsed: elapsed }, status: :ok + else + head :unauthorized + end + end + + def extend + super + end + private def namespace diff --git a/app/controllers/concerns/admin_shortlisted_docs_submission_context.rb b/app/controllers/concerns/admin_shortlisted_docs_submission_context.rb index e3ddf4706..bd0bc7d84 100644 --- a/app/controllers/concerns/admin_shortlisted_docs_submission_context.rb +++ b/app/controllers/concerns/admin_shortlisted_docs_submission_context.rb @@ -2,7 +2,17 @@ module AdminShortlistedDocsSubmissionContext def create authorize resource, :submit? - resource.submitted? ? resource.uncomplete : resource.complete + if resource.submitted? + resource.uncomplete + else + if resource.complete + if form_answer.assessors.primary.present? + Assessors::GeneralMailer.vat_returns_submitted(form_answer.id).deliver_later! + end + + Users::CommercialFiguresMailer.notify(form_answer.id, form_answer.account.owner_id).deliver_later! + end + end respond_to do |format| format.html do @@ -29,7 +39,7 @@ def resource @resource ||= (form_answer.shortlisted_documents_wrapper || form_answer.build_shortlisted_documents_wrapper) return @resource if @resource.persisted? - + @resource.save! @resource end diff --git a/app/controllers/concerns/assessment_submission_mixin.rb b/app/controllers/concerns/assessment_submission_mixin.rb index 86b97f58e..3f4bf3380 100644 --- a/app/controllers/concerns/assessment_submission_mixin.rb +++ b/app/controllers/concerns/assessment_submission_mixin.rb @@ -19,6 +19,7 @@ def unlock resource.update_column(:locked_at, nil) log_event + render_flash_message_for(resource, message: flash_message) redirect_to [namespace_name, resource.form_answer] end @@ -36,7 +37,7 @@ def form_answer def flash_message return nil if resource.errors.none? - resource.errors.full_messages.join(", ") + resource.errors.full_messages.join("
") end def json_response diff --git a/app/controllers/concerns/audit_certificate_context.rb b/app/controllers/concerns/audit_certificate_context.rb index ab858e206..0166b5071 100644 --- a/app/controllers/concerns/audit_certificate_context.rb +++ b/app/controllers/concerns/audit_certificate_context.rb @@ -1,7 +1,7 @@ module AuditCertificateContext def show authorize form_answer, :download_audit_certificate_pdf? - redirect_to resource.attachment_url + redirect_to resource.attachment_url, allow_other_host: true end def create diff --git a/app/controllers/concerns/palace_attendees_mixin.rb b/app/controllers/concerns/palace_attendees_mixin.rb index 3d2ffafdd..7ed8d9fe4 100644 --- a/app/controllers/concerns/palace_attendees_mixin.rb +++ b/app/controllers/concerns/palace_attendees_mixin.rb @@ -3,13 +3,15 @@ def new invite = PalaceInvite.find(params[:palace_invite_id]) @enable_edition = true @form_answer = invite.form_answer - authorize @form_answer, :update? + authorize invite, :update? palace_attendee = invite.palace_attendees.build render_attendee_form(palace_attendee, invite) end def create - authorize form_answer, :update? + form_answer + + authorize palace_invite, :update? limit = palace_invite.attendees_limit if palace_invite.palace_attendees.count < limit palace_attendee = palace_invite.palace_attendees.create(create_params) @@ -21,7 +23,9 @@ def create end def update - authorize form_answer, :update? + form_answer + + authorize palace_invite, :update? palace_attendee = palace_invite.palace_attendees.find(params[:id]) log_event if palace_attendee.update(create_params) @@ -29,7 +33,7 @@ def update end def destroy - authorize form_answer, :update? + authorize palace_invite, :update? palace_attendee = palace_invite.palace_attendees.find(params[:id]) log_event if palace_attendee.destroy respond_to do |format| diff --git a/app/controllers/concerns/palace_invites_mixin.rb b/app/controllers/concerns/palace_invites_mixin.rb index acd1d614f..e46264924 100644 --- a/app/controllers/concerns/palace_invites_mixin.rb +++ b/app/controllers/concerns/palace_invites_mixin.rb @@ -1,7 +1,7 @@ module PalaceInvitesMixin def submit @invite = PalaceInvite.find(params[:id]) - authorize @invite.form_answer, :update? + authorize @invite, :update? @invite.submit! diff --git a/app/controllers/concerns/session_status_check_mixin.rb b/app/controllers/concerns/session_status_check_mixin.rb index 88ed65014..b0c63e07c 100644 --- a/app/controllers/concerns/session_status_check_mixin.rb +++ b/app/controllers/concerns/session_status_check_mixin.rb @@ -19,6 +19,10 @@ def show end end + def extend + render json: { elapsed: elapsed }, status: :ok + end + private def skip_timeout @@ -34,4 +38,19 @@ def session_is_valid? judge_signed_in? end end + + def now + Time.now.in_time_zone("UTC") + end + + def elapsed + session = if namespace == ADMIN_NAMESPACE + admin_session + elsif namespace == ASSESSOR_NAMESPACE + assessor_session + elsif namespace == JUDGE_NAMESPACE + judge_session + end + (now - (session["last_request_at"] || params['__t'].to_i)).to_i / 1.minutes + end end diff --git a/app/controllers/judge/case_summaries_controller.rb b/app/controllers/judge/case_summaries_controller.rb index 336b40158..c8debdf5d 100644 --- a/app/controllers/judge/case_summaries_controller.rb +++ b/app/controllers/judge/case_summaries_controller.rb @@ -16,7 +16,7 @@ def download type: "application/pdf", disposition: 'attachment' else - redirect_to pdf.data + redirect_to pdf.data, allow_other_host: true end end end diff --git a/app/controllers/judge/session_checks_controller.rb b/app/controllers/judge/session_checks_controller.rb index 946c19527..ad9a5b962 100644 --- a/app/controllers/judge/session_checks_controller.rb +++ b/app/controllers/judge/session_checks_controller.rb @@ -1,6 +1,18 @@ class Judge::SessionChecksController < ActionController::Base include SessionStatusCheckMixin + def show + if session_is_valid? + render json: { elapsed: elapsed }, status: :ok + else + head :unauthorized + end + end + + def extend + super + end + private def namespace diff --git a/app/controllers/users/form_answers_controller.rb b/app/controllers/users/form_answers_controller.rb index 1a704f9d9..8825a9b41 100644 --- a/app/controllers/users/form_answers_controller.rb +++ b/app/controllers/users/form_answers_controller.rb @@ -71,7 +71,7 @@ def can_render_pdf_on_fly? def render_hard_copy_pdf if form_answer.pdf_version.present? - redirect_to form_answer.pdf_version.url + redirect_to form_answer.pdf_version.url, allow_other_host: true else if !admin_in_read_only_mode? redirect_to dashboard_path, diff --git a/app/helpers/form_answer_helper.rb b/app/helpers/form_answer_helper.rb index c5195cccf..7bbfe8b12 100644 --- a/app/helpers/form_answer_helper.rb +++ b/app/helpers/form_answer_helper.rb @@ -37,7 +37,8 @@ def application_flags(fa, subject = nil) if comments_count > 0 content_tag :span, class: "icon-flagged #{flag_type}" do - content_tag :span, class: "flag-count" do + "#{current_user_class} flags: ".html_safe + + content_tag(:span, class: "flag-count") do comments_count.to_s end end diff --git a/app/helpers/winners_awards_helper.rb b/app/helpers/winners_awards_helper.rb index 4c1bd031e..1baad0e05 100644 --- a/app/helpers/winners_awards_helper.rb +++ b/app/helpers/winners_awards_helper.rb @@ -1,7 +1,8 @@ module WinnersAwardsHelper def static_s3_asset_path(year, file_name) file_path = "winners assets #{year}/#{file_name}" - URI.encode("https://s3-eu-west-1.amazonaws.com/qae-winners-assets/#{file_path}") + + "https://s3-eu-west-1.amazonaws.com/qae-winners-assets/#{CGI.escape(file_path)}" end end diff --git a/app/javascript/controllers/appraisal_form_controller.js b/app/javascript/controllers/appraisal_form_controller.js new file mode 100644 index 000000000..82082b63a --- /dev/null +++ b/app/javascript/controllers/appraisal_form_controller.js @@ -0,0 +1,211 @@ +import { scrollToElement } from '../helpers'; + +export default class extends ApplicationController { + static values = { + success: String, + }; + + connect() {} + + success(event) { + return this.handleSuccess(event); + } + + error(event) { + return this.handleError(event); + } + + handleSuccess = ({ target }) => { + const panel = target.closest('.panel-body'); + + this.resetErrors(panel); + + const message = this.hasSuccessValue ? this.successValue : 'Success!'; + const [alert, identifier] = this.createAlert('success', message); + + panel.insertAdjacentHTML('afterbegin', alert); + + const template = this.element.querySelector('[data-role=template]'); + + if ('content' in template) { + const replacement = template.content.cloneNode(true); + target.replaceWith(replacement.firstChild); + } else { + target.remove(); + } + }; + + handleError = (event) => { + const { + target, + detail: { responseJSON: errors }, + } = event; + const panel = target.closest('.panel-body'); + let form; + + this.resetErrors(panel); + + Object.entries(errors).forEach(([key, values]) => { + const field = panel.querySelector(`[name*='[${key}]']`); + if (!form) { + form = field.closest('form'); + } + const container = field.closest('.form-container'); + + if (field && this.shouldValidateField(field)) { + this.showFieldErrors(field, values); + } + + if (key && key.endsWith('_rate')) { + const button = container.querySelector('label button'); + if (button) button.classList.add('rag-error'); + } else { + if (!this.visible(field)) { + const target = container.querySelector('.form-edit-link'); + if (target) $(target).trigger('click'); + } + } + }); + + const firstInvalidField = Array.from(form.elements).find((field) => field.ariaInvalid == 'true'); + + if (this.visible(firstInvalidField)) { + scrollToElement(firstInvalidField); + firstInvalidField.focus(); + } else { + const altTarget = firstInvalidField.closest('.form-container').querySelector('label button'); + if (altTarget) { + scrollToElement(altTarget); + altTarget.focus(); + } + } + }; + + stash = (event) => { + event.preventDefault(); + event.stopPropagation(); + + const { target: button } = event; + const wrapper = button.closest('.form-group'); + const form = wrapper.closest('form'); + const textarea = Array.from(wrapper.querySelectorAll('textarea')).filter(this.visible); + + if (textarea.length > 1) { + for (let idx = 0; idx < textarea.length; idx++) { + LS.removeItem(textarea[idx].dataset.autosaveKey); + } + } else { + LS.removeItem(textarea[0].dataset.autosaveKey); + } + + wrapper.classList.remove('form-edit'); + + if (Array.from(wrapper.querySelectorAll('.form-value p[data-for]')).length > 0) { + console.log(1); + Array.from(wrapper.querySelectorAll('.form-value p[data-for]')).forEach((el) => { + let identifier = el.dataset.for; + let element = document.querySelector(`#${identifier}`); + + if (element) el.innerHTML = element.value; + }); + + return $(form).submit(); + } else if (textarea.length === 1) { + let _textarea = textarea[0]; + + if (_textarea.value.length) { + const panel = event.target.closest('.panel-body'); + const value = _textarea.value.replace(/\n/g, '
'); + const updatedValue = button.dataset.updatedSection; + + wrapper.querySelector('.form-value p').innerHTML = value; + + panel.querySelector('.field-with-errors')?.classList?.remove('field-with-errors'); + panel.querySelector('.feedback-holder')?.classList?.remove('error'); + panel.querySelector('.btn-rag > button')?.classList?.remove('rag-error'); + + Array.from(wrapper.querySelectorAll('textarea')).forEach((el) => { + if (el.value && el.value.length) { + return el.closest('.input')?.classList?.remove('field-with-errors'); + } + }); + + if (updatedValue) { + const input = form.querySelector("input[name='updated_section']"); + + if (input) { + input.value = updatedValue; + } + } + + return $(form).submit(); + } + } else { + if (textarea[0] && textarea[0].value.length) { + const value = textarea[0].value.replace(/\n/g, '
'); + Array.from(wrapper.querySelectorAll('.form-value p'))[0].innerHTML = value; + } + + if (textarea[-1] && textarea[-1].value.length) { + const value = textarea[-1].value.replace(/\n/g, '
'); + Array.from(wrapper.querySelectorAll('.form-value p'))[-1].innerHTML = value; + } + + return $(form).submit(); + } + }; + + resetErrors(element) { + element.querySelectorAll('.field-with-errors').forEach((el) => el.classList.remove('field-with-errors')); + element.querySelectorAll('.rag-error').forEach((el) => el.classList.remove('rag-error')); + element.querySelectorAll('[aria-errormessage]').forEach((el) => el.removeAttribute('aria-errormessage')); + element.querySelectorAll('[aria-invalid]').forEach((el) => el.removeAttribute('aria-invalid')); + element.querySelectorAll('.alert').forEach((el) => el.remove()); + } + + shouldValidateField(field) { + return !field.disabled && field.type !== void 0 && !['file', 'reset', 'submit', 'button'].includes(field.type); + } + + showFieldErrors(field, errors, selector) { + if (selector == null) { + selector = '.form-container'; + } + + const container = field.closest(selector); + const group = field.closest('.govuk-form-group'); + + if (group) { + group.classList.add('field-with-errors'); + } + + if (container) { + return errors.forEach((message) => { + const [alert, identifier] = this.createAlert('danger', message); + field.setAttribute('aria-errormessage', identifier); + field.setAttribute('aria-invalid', true); + return container.insertAdjacentHTML('afterbegin', alert); + }); + } + } + + createAlert = (type, message) => { + const id = 'alert__' + String(Math.random()).slice(2, -1); + const element = ` + + `; + + return [element, id]; + }; + + visible(field) { + return ( + !field.hidden && (!field.type || field.type != 'hidden') && (field.offsetWidth > 0 || field.offsetHeight > 0) + ); + } +} diff --git a/app/javascript/controllers/element_scroll_controller.js b/app/javascript/controllers/element_scroll_controller.js index d905ccc57..d79c3ef83 100644 --- a/app/javascript/controllers/element_scroll_controller.js +++ b/app/javascript/controllers/element_scroll_controller.js @@ -5,14 +5,14 @@ export default class extends ApplicationController { connect() { if (this.firstInvalidField) { - if (!this.visible(this.firstInvalidField)) { - const collapsible = this.accordionTargets.find((target) => { - return target.contains(this.firstInvalidField); - }); - if (collapsible) $(collapsible).collapse('show'); - } - setTimeout(() => { + if (!this.visible(this.firstInvalidField)) { + const collapsible = this.accordionTargets.find((target) => { + return target.contains(this.firstInvalidField); + }); + if (collapsible) $(collapsible).collapse('show'); + } + this.firstInvalidField.focus(); scrollToElement(this.firstInvalidField); }, 300); diff --git a/app/javascript/controllers/inline_flash_controller.js b/app/javascript/controllers/inline_flash_controller.js index 481c3316e..127dd8f40 100644 --- a/app/javascript/controllers/inline_flash_controller.js +++ b/app/javascript/controllers/inline_flash_controller.js @@ -32,14 +32,14 @@ export default class extends ApplicationController { const msg = this.hasSuccessValue ? this.successValue : 'Success!'; const [alert, identifier] = this.createAlert('success', msg); - setTimeout(() => this.showAlert(alert, identifier), 1); + setTimeout(() => this.showAlert(alert, identifier), 10); }; onError = (event) => { const msg = this.hasErrorValue ? this.errorValue : 'An unknown error has occurred, please try again.'; const [alert, identifier] = this.createAlert('danger', msg); - setTimeout(() => this.showAlert(alert, identifier), 1); + setTimeout(() => this.showAlert(alert, identifier), 10); }; createAlert = (type, message) => { @@ -47,7 +47,7 @@ export default class extends ApplicationController { const element = ` @@ -57,8 +57,8 @@ export default class extends ApplicationController { }; showAlert = (alert, identifier) => { - const existing = this.element.querySelector('[id*=alert__]') - if (existing) existing.remove() + const existing = this.element.querySelector('[id*=alert__]'); + if (existing) existing.remove(); this.element.insertAdjacentHTML('afterbegin', alert); diff --git a/app/models/account.rb b/app/models/account.rb index 8e3efbc74..f97d35e10 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -10,7 +10,7 @@ class Account < ApplicationRecord has_many :mobility_eligibilities, class_name: 'Eligibility::Mobility' has_many :promotion_eligibilities, class_name: 'Eligibility::Promotion' - belongs_to :owner, class_name: 'User', autosave: false, inverse_of: :owned_account + belongs_to :owner, class_name: 'User', autosave: false, inverse_of: :owned_account, optional: true validates :owner, presence: true def basic_eligibility diff --git a/app/models/aggregated_award_year_pdf.rb b/app/models/aggregated_award_year_pdf.rb index f8495bf82..6cb3e49c9 100644 --- a/app/models/aggregated_award_year_pdf.rb +++ b/app/models/aggregated_award_year_pdf.rb @@ -2,7 +2,7 @@ class AggregatedAwardYearPdf < ApplicationRecord TYPES = %w(case_summary feedback) - belongs_to :award_year + belongs_to :award_year, optional: true validates :award_year_id, uniqueness: { scope: [:award_category, :type_of_report, :sub_type] }, presence: true diff --git a/app/models/assessor_assignment.rb b/app/models/assessor_assignment.rb index 5952da3a9..185c384ca 100644 --- a/app/models/assessor_assignment.rb +++ b/app/models/assessor_assignment.rb @@ -31,10 +31,10 @@ class AssessorAssignment < ApplicationRecord end begin :associations - belongs_to :assessor - belongs_to :form_answer - belongs_to :editable, polymorphic: true - belongs_to :award_year + belongs_to :assessor, optional: true + belongs_to :form_answer, optional: true + belongs_to :editable, polymorphic: true, optional: true + belongs_to :award_year, optional: true end begin :scopes @@ -152,7 +152,13 @@ def not_submitted_or_not_locked? def award_specific_attributes struct.diff(form_answer, moderated?).each do |att| if public_send(att).present? - errors.add(att, "cannot be present for this Award Type") + message = if att.ends_with?("_rate") + "RAG rating for '#{section_name(att)}' cannot be present for this Award Type" + else + "An appraisal comment for '#{section_name(att)}' cannot be present for this Award Type" + end + + errors.add(att, message: message) end end end @@ -162,7 +168,13 @@ def mandatory_fields_for_submitted struct.meths_for_award_type(form_answer, moderated?).each do |meth| if public_send(meth).blank? - errors.add(meth, "cannot be blank for submitted assessment") + message = if meth.ends_with?("_rate") + "RAG rating is required for '#{section_name(meth)}'. Select an option from the dropdown list." + else + "An appraisal comment is required for '#{section_name(meth)}' and must be filled in." + end + + errors.add(meth, message: message) end end end @@ -174,7 +186,8 @@ def validate_rate(rate_type) c = "#{rate_type.upcase}_ALLOWED_VALUES" if val && !struct.const_get(c).include?(val) sect_name = struct.rate(section) - errors.add(sect_name, "#{rate_type} field has not permitted value") + message = "#{rate_type} field for '#{section_name(section)}' has not permitted value." + errors.add(sect_name, message: message) end end end @@ -183,6 +196,14 @@ def section_rate(section) public_send(struct.rate(section)) end + def section_name(key) + @_sections ||= struct.struct(form_answer).to_h + section_key = key.to_s.gsub(/_desc$/, "").gsub(/_rate$/, "") + if section = @_sections.dig(section_key.to_sym) + section[:label].gsub(/:$/, "") + end + end + def struct AppraisalForm end diff --git a/app/models/audit_log.rb b/app/models/audit_log.rb index 8d4da014a..5874ff467 100644 --- a/app/models/audit_log.rb +++ b/app/models/audit_log.rb @@ -3,8 +3,8 @@ class AuditLog < ApplicationRecord validates :subject_id, presence: true validates :action_type, presence: true - belongs_to :subject, polymorphic: true - belongs_to :auditable, polymorphic: true + belongs_to :subject, polymorphic: true, optional: true + belongs_to :auditable, polymorphic: true, optional: true scope :data_export, -> { where(auditable_type: nil).or(where(action_type: "download_form_answer")) } scope :data_update, -> { where(auditable_type: "FormAnswer").where("action_type != 'download_form_answer'") } diff --git a/app/models/award_year.rb b/app/models/award_year.rb index 98c1a974b..2fbc868ef 100644 --- a/app/models/award_year.rb +++ b/app/models/award_year.rb @@ -256,8 +256,8 @@ def current_year_deadline(title) res end - def buckingham_palace_reception_deadline - current_year_deadline("buckingham_palace_attendees_invite") + def buckingham_palace_reception_deadline(award_year) + award_year.settings.deadlines.where(kind: "buckingham_palace_attendees_invite").first end def buckingham_palace_reception_date diff --git a/app/models/case_summary_hard_copy_pdf.rb b/app/models/case_summary_hard_copy_pdf.rb index 9bdaae6aa..9f9e3c03c 100644 --- a/app/models/case_summary_hard_copy_pdf.rb +++ b/app/models/case_summary_hard_copy_pdf.rb @@ -1,5 +1,5 @@ class CaseSummaryHardCopyPdf < ApplicationRecord - belongs_to :form_answer + belongs_to :form_answer, optional: true mount_uploader :file, FormAnswerPdfVersionUploader diff --git a/app/models/comment.rb b/app/models/comment.rb index 641f20a33..74bfbb6b4 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,6 +1,6 @@ class Comment < ApplicationRecord - belongs_to :commentable, polymorphic: true - belongs_to :authorable, polymorphic: true + belongs_to :commentable, polymorphic: true, optional: true + belongs_to :authorable, polymorphic: true, optional: true validates :body, presence: true diff --git a/app/models/commercial_figures_file.rb b/app/models/commercial_figures_file.rb index 641fe786e..255f3f0b4 100644 --- a/app/models/commercial_figures_file.rb +++ b/app/models/commercial_figures_file.rb @@ -10,5 +10,5 @@ class CommercialFiguresFile < ActiveRecord::Base validates :form_answer_id, uniqueness: true, presence: true - belongs_to :shortlisted_documents_wrapper + belongs_to :shortlisted_documents_wrapper, optional: true end diff --git a/app/models/concerns/reviewable.rb b/app/models/concerns/reviewable.rb index 2f444bc7f..0e4c81aeb 100644 --- a/app/models/concerns/reviewable.rb +++ b/app/models/concerns/reviewable.rb @@ -2,7 +2,7 @@ module Reviewable extend ActiveSupport::Concern included do - belongs_to :reviewable, polymorphic: true + belongs_to :reviewable, polymorphic: true, optional: true validates :reviewable_type, :reviewable_id, diff --git a/app/models/concerns/shortlisted_document.rb b/app/models/concerns/shortlisted_document.rb index 337294792..8b69b02e2 100644 --- a/app/models/concerns/shortlisted_document.rb +++ b/app/models/concerns/shortlisted_document.rb @@ -8,7 +8,7 @@ module ShortlistedDocument include ::InfectedFileCleaner clean_after_scan :attachment - belongs_to :form_answer + belongs_to :form_answer, optional: true end def original_filename diff --git a/app/models/deadline.rb b/app/models/deadline.rb index e5c8f8eef..c26e7c59d 100644 --- a/app/models/deadline.rb +++ b/app/models/deadline.rb @@ -6,7 +6,7 @@ class Deadline < ApplicationRecord date_time_for :trigger_at - belongs_to :settings + belongs_to :settings, optional: true AVAILABLE_DEADLINES = [ "award_year_switch", diff --git a/app/models/draft_note.rb b/app/models/draft_note.rb index 260443cfb..01594bb6d 100644 --- a/app/models/draft_note.rb +++ b/app/models/draft_note.rb @@ -5,8 +5,8 @@ class DraftNote < ApplicationRecord :authorable_id, presence: true - belongs_to :notable, polymorphic: true - belongs_to :authorable, polymorphic: true + belongs_to :notable, polymorphic: true, optional: true + belongs_to :authorable, polymorphic: true, optional: true before_save :update_content_updated_at diff --git a/app/models/eligibility.rb b/app/models/eligibility.rb index a353f8cae..85962561e 100644 --- a/app/models/eligibility.rb +++ b/app/models/eligibility.rb @@ -3,8 +3,8 @@ class Eligibility < ApplicationRecord AWARD_NAME = "" - belongs_to :account - belongs_to :form_answer + belongs_to :account, optional: true + belongs_to :form_answer, optional: true attr_accessor :current_step, :force_validate_now diff --git a/app/models/email_notification.rb b/app/models/email_notification.rb index 962ae406c..75088f18c 100644 --- a/app/models/email_notification.rb +++ b/app/models/email_notification.rb @@ -25,7 +25,7 @@ class EmailNotification < ApplicationRecord date_time_for :trigger_at - belongs_to :settings + belongs_to :settings, optional: true enumerize :kind, in: NOTIFICATION_KINDS diff --git a/app/models/feedback.rb b/app/models/feedback.rb index bed274f16..1b518f5f3 100644 --- a/app/models/feedback.rb +++ b/app/models/feedback.rb @@ -2,13 +2,13 @@ class Feedback < ApplicationRecord has_paper_trail unless: Proc.new { |t| Rails.env.test? } - belongs_to :form_answer + belongs_to :form_answer, optional: true store_accessor :document, FeedbackForm.fields scope :submitted, -> { where submitted: true } - belongs_to :authorable, polymorphic: true - belongs_to :award_year + belongs_to :authorable, polymorphic: true, optional: true + belongs_to :award_year, optional: true validates :form_answer_id, uniqueness: true diff --git a/app/models/feedback_hard_copy_pdf.rb b/app/models/feedback_hard_copy_pdf.rb index 6c5e55e2c..f3db0d1b7 100644 --- a/app/models/feedback_hard_copy_pdf.rb +++ b/app/models/feedback_hard_copy_pdf.rb @@ -1,5 +1,5 @@ class FeedbackHardCopyPdf < ApplicationRecord - belongs_to :form_answer + belongs_to :form_answer, optional: true mount_uploader :file, FormAnswerPdfVersionUploader diff --git a/app/models/form_answer.rb b/app/models/form_answer.rb index 61359773a..51d63765b 100644 --- a/app/models/form_answer.rb +++ b/app/models/form_answer.rb @@ -75,10 +75,10 @@ class FormAnswer < ApplicationRecord mount_uploader :pdf_version, FormAnswerPdfVersionUploader begin :associations - belongs_to :user - belongs_to :account - belongs_to :award_year - belongs_to :company_details_editable, polymorphic: true + belongs_to :user, optional: true + belongs_to :account, optional: true + belongs_to :award_year, optional: true + belongs_to :company_details_editable, polymorphic: true, optional: true has_one :form_basic_eligibility, class_name: 'Eligibility::Basic', dependent: :destroy has_one :trade_eligibility, class_name: 'Eligibility::Trade', dependent: :destroy @@ -99,8 +99,8 @@ class FormAnswer < ApplicationRecord has_one :case_summary_hard_copy_pdf, dependent: :destroy has_one :feedback_hard_copy_pdf, dependent: :destroy - belongs_to :primary_assessor, class_name: "Assessor", foreign_key: :primary_assessor_id - belongs_to :secondary_assessor, class_name: "Assessor", foreign_key: :secondary_assessor_id + belongs_to :primary_assessor, optional: true, class_name: "Assessor", foreign_key: :primary_assessor_id + belongs_to :secondary_assessor, optional: true, class_name: "Assessor", foreign_key: :secondary_assessor_id has_many :form_answer_attachments, dependent: :destroy has_many :support_letter_attachments, dependent: :destroy has_many :commercial_figures_files, dependent: :destroy diff --git a/app/models/form_answer_attachment.rb b/app/models/form_answer_attachment.rb index fd4c4eb3c..6fdc27ef2 100644 --- a/app/models/form_answer_attachment.rb +++ b/app/models/form_answer_attachment.rb @@ -1,6 +1,6 @@ class FormAnswerAttachment < ApplicationRecord - belongs_to :form_answer - belongs_to :attachable, polymorphic: true + belongs_to :form_answer, optional: true + belongs_to :attachable, polymorphic: true, optional: true mount_uploader :file, FormAnswerAttachmentUploader scan_file :file diff --git a/app/models/form_answer_progress.rb b/app/models/form_answer_progress.rb index 7d2d0b592..0327009eb 100644 --- a/app/models/form_answer_progress.rb +++ b/app/models/form_answer_progress.rb @@ -1,5 +1,5 @@ class FormAnswerProgress < ApplicationRecord - belongs_to :form_answer + belongs_to :form_answer, optional: true validates :form_answer_id, presence: true diff --git a/app/models/form_answer_transition.rb b/app/models/form_answer_transition.rb index c62212972..f08891410 100644 --- a/app/models/form_answer_transition.rb +++ b/app/models/form_answer_transition.rb @@ -1,7 +1,7 @@ class FormAnswerTransition < ApplicationRecord include Statesman::Adapters::ActiveRecordTransition - belongs_to :form_answer, inverse_of: :form_answer_transitions + belongs_to :form_answer, optional: true, inverse_of: :form_answer_transitions def transitable t_type = metadata["transitable_type"] diff --git a/app/models/palace_attendee.rb b/app/models/palace_attendee.rb index d30a36d0c..9f2f170b3 100644 --- a/app/models/palace_attendee.rb +++ b/app/models/palace_attendee.rb @@ -1,5 +1,5 @@ class PalaceAttendee < ApplicationRecord - belongs_to :palace_invite + belongs_to :palace_invite, optional: true validates :palace_invite, :title, diff --git a/app/models/palace_invite.rb b/app/models/palace_invite.rb index ed426eb90..5304ef1aa 100644 --- a/app/models/palace_invite.rb +++ b/app/models/palace_invite.rb @@ -1,5 +1,5 @@ class PalaceInvite < ApplicationRecord - belongs_to :form_answer + belongs_to :form_answer, optional: true has_many :palace_attendees, dependent: :destroy, autosave: true diff --git a/app/models/press_summary.rb b/app/models/press_summary.rb index 1a7ddbd21..896d127b6 100644 --- a/app/models/press_summary.rb +++ b/app/models/press_summary.rb @@ -1,5 +1,5 @@ class PressSummary < ApplicationRecord - belongs_to :form_answer + belongs_to :form_answer, optional: true validates :form_answer, :token, presence: true validates :body, presence: true, unless: :contact_details_update? @@ -15,7 +15,7 @@ class PressSummary < ApplicationRecord before_validation :set_token, on: :create - belongs_to :authorable, polymorphic: true + belongs_to :authorable, optional: true, polymorphic: true attr_accessor :contact_details_update, :body_update diff --git a/app/models/reports/dashboard/applications_report.rb b/app/models/reports/dashboard/applications_report.rb index cf125bfc1..da9419fac 100644 --- a/app/models/reports/dashboard/applications_report.rb +++ b/app/models/reports/dashboard/applications_report.rb @@ -80,4 +80,33 @@ def generate_content(form_answers, date) [" ".html_safe, " ".html_safe] end end + + def as_csv + CSV.generate do |csv| + headers = ["Year"] + case kind + when "by_month" + columns = ["By end of April", "By end of May", "By end of June", "By end of July", "By end of August", "Totals on deadline"] + when "by_week" + columns = ["6 weeks before deadline", "5 weeks before deadline", "4 weeks before deadline", "3 weeks before deadline", "2 weeks before deadline", "1 week before deadline", "Totals on deadline"] + when "by_day" + columns = ["6 days before deadline", "5 days before deadline", "4 days before deadline", "3 days before deadline", "2 days before deadline", "1 day before deadline", "Totals on deadline"] + end + csv << (headers + columns.map{|c| [c] << nil}).flatten + subheaders =[""] + columns.each{subheaders << ["In progress", "Submitted"]} + csv << subheaders.flatten + + stats.each do |row| + content = [] + content << row.label + row.content.each{|c| content << (c == " " ? nil : c)} + csv << content + end + end + end + + def csv_filename + "#{award_type || "all"}_applications_report_#{kind}.csv" + end end diff --git a/app/models/reports/dashboard/users_report.rb b/app/models/reports/dashboard/users_report.rb index ff116776e..a732a26db 100644 --- a/app/models/reports/dashboard/users_report.rb +++ b/app/models/reports/dashboard/users_report.rb @@ -69,4 +69,30 @@ def generate_content(users, date) " ".html_safe end end + + def as_csv + CSV.generate do |csv| + headers = ["Year"] + case kind + when "by_month" + columns = ["By end of April", "By end of May", "By end of June", "By end of July", "By end of August", "Totals on deadline"] + when "by_week" + columns = ["6 weeks before deadline", "5 weeks before deadline", "4 weeks before deadline", "3 weeks before deadline", "2 weeks before deadline", "1 week before deadline", "Totals on deadline"] + when "by_day" + columns = ["6 days before deadline", "5 days before deadline", "4 days before deadline", "3 days before deadline", "2 days before deadline", "1 day before deadline", "Totals on deadline"] + end + csv << (headers + columns).flatten + + stats.each do |row| + content = [] + content << row.label + row.content.each{|c| content << (c == " " ? nil : c)} + csv << content + end + end + end + + def csv_filename + "account_registrations_#{kind}.csv" + end end diff --git a/app/models/reports/discrepancies_between_primary_and_secondary_appraisals.rb b/app/models/reports/discrepancies_between_primary_and_secondary_appraisals.rb index 3686ec361..0fe35c319 100644 --- a/app/models/reports/discrepancies_between_primary_and_secondary_appraisals.rb +++ b/app/models/reports/discrepancies_between_primary_and_secondary_appraisals.rb @@ -45,22 +45,22 @@ def initialize(year, award_type, current_subject=nil) raise "Access Denied!" end - end - def stream - scoped = @year.form_answers.order(:id) - .joins(%Q{ + @scope = @year.form_answers.order(:id) + .joins(%Q{ JOIN assessor_assignments primary_assignments ON primary_assignments.form_answer_id = form_answers.id JOIN assessor_assignments secondary_assignments ON secondary_assignments.form_answer_id = form_answers.id }) - .where(primary_assignments: { position: AssessorAssignment.positions[:primary] }) - .where.not(primary_assignments: { position: nil }) - .where(secondary_assignments: { position: AssessorAssignment.positions[:secondary] }) - .where.not(secondary_assignments: { position: nil }) - .primary_and_secondary_appraisals_are_not_match - .where(award_type: @award_type) + .where(primary_assignments: { position: AssessorAssignment.positions[:primary] }) + .where.not(primary_assignments: { position: nil }) + .where(secondary_assignments: { position: AssessorAssignment.positions[:secondary] }) + .where.not(secondary_assignments: { position: nil }) + .primary_and_secondary_appraisals_are_not_match + .where(award_type: @award_type) + end - prepare_stream(scoped) + def stream + prepare_stream(@scope) end private diff --git a/app/models/settings.rb b/app/models/settings.rb index e5d6d8ae0..fa0a5c8a8 100644 --- a/app/models/settings.rb +++ b/app/models/settings.rb @@ -6,7 +6,7 @@ class Settings < ApplicationRecord has_many :deadlines, dependent: :destroy has_many :email_notifications, dependent: :destroy - belongs_to :award_year, inverse_of: :settings + belongs_to :award_year, optional: true, inverse_of: :settings validates :award_year, presence: true diff --git a/app/models/shortlisted_documents_wrapper.rb b/app/models/shortlisted_documents_wrapper.rb index dfa451c06..89cee9aee 100644 --- a/app/models/shortlisted_documents_wrapper.rb +++ b/app/models/shortlisted_documents_wrapper.rb @@ -3,7 +3,7 @@ class ShortlistedDocumentsWrapper < ActiveRecord::Base has_one :commercial_figures_file has_many :vat_returns_files - belongs_to :form_answer + belongs_to :form_answer, optional: true validate :valid_for_submission? diff --git a/app/models/support_letter.rb b/app/models/support_letter.rb index 8c7e2bc24..b0d78d59e 100644 --- a/app/models/support_letter.rb +++ b/app/models/support_letter.rb @@ -1,8 +1,8 @@ class SupportLetter < ApplicationRecord begin :associations - belongs_to :supporter - belongs_to :form_answer - belongs_to :user + belongs_to :supporter, optional: true + belongs_to :form_answer, optional: true + belongs_to :user, optional: true has_one :support_letter_attachment, autosave: true, dependent: :destroy end diff --git a/app/models/support_letter_attachment.rb b/app/models/support_letter_attachment.rb index c3640ba45..81c6f2eb1 100644 --- a/app/models/support_letter_attachment.rb +++ b/app/models/support_letter_attachment.rb @@ -6,9 +6,9 @@ class SupportLetterAttachment < ApplicationRecord clean_after_scan :attachment begin :associations - belongs_to :user - belongs_to :form_answer - belongs_to :support_letter + belongs_to :user, optional: true + belongs_to :form_answer, optional: true + belongs_to :support_letter, optional: true end begin :validations diff --git a/app/models/supporter.rb b/app/models/supporter.rb index c8984b14a..068289c0e 100644 --- a/app/models/supporter.rb +++ b/app/models/supporter.rb @@ -2,8 +2,8 @@ class Supporter < ApplicationRecord begin :associations - belongs_to :form_answer - belongs_to :user + belongs_to :form_answer, optional: true + belongs_to :user, optional: true has_one :support_letter, dependent: :destroy end diff --git a/app/models/user.rb b/app/models/user.rb index ef27ff22a..b117a164e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,7 +44,7 @@ class User < ApplicationRecord source: :feedback has_one :owned_account, foreign_key: :owner_id, class_name: 'Account' - belongs_to :account + belongs_to :account, optional: true has_many :form_answer_attachments, as: :attachable has_many :support_letter_attachments, dependent: :destroy has_many :supporters, dependent: :destroy @@ -161,7 +161,7 @@ def new_member? end def timeout_in - 24.hours + ENV.fetch("SESSION_TIMEOUT", 24).to_i.hours end def check_email_on_bounces! diff --git a/app/models/vat_returns_file.rb b/app/models/vat_returns_file.rb index 27f81a131..dc7570503 100644 --- a/app/models/vat_returns_file.rb +++ b/app/models/vat_returns_file.rb @@ -7,7 +7,7 @@ class VatReturnsFile < ActiveRecord::Base maximum: 5.megabytes.to_i } - belongs_to :shortlisted_documents_wrapper + belongs_to :shortlisted_documents_wrapper, optional: true after_destroy :unsubmit_if_needed diff --git a/app/pdf_generators/case_summary_pdfs/base.rb b/app/pdf_generators/case_summary_pdfs/base.rb index 290ee8b01..ca160e1bc 100644 --- a/app/pdf_generators/case_summary_pdfs/base.rb +++ b/app/pdf_generators/case_summary_pdfs/base.rb @@ -24,10 +24,10 @@ def add_page_numbers end def all_mode - set_form_answers + @_form_answers = set_form_answers - if form_answers.present? - form_answers.each_with_index do |form_answer, index| + if @_form_answers.present? + @_form_answers.each_with_index do |form_answer, index| start_new_page if index.to_i != 0 render_item(form_answer) end @@ -54,7 +54,11 @@ def set_form_answers scope = scope.where("form_answers.document #>> '{trade_commercial_success}' = '#{years_mode}'") end - @form_answers = scope + puts "*"*100 + puts scope.count + puts "*"*100 + + scope end def render_item(form_answer) diff --git a/app/pdf_generators/hard_copy_generators/base.rb b/app/pdf_generators/hard_copy_generators/base.rb index 2081bd33c..af700a72a 100644 --- a/app/pdf_generators/hard_copy_generators/base.rb +++ b/app/pdf_generators/hard_copy_generators/base.rb @@ -18,19 +18,21 @@ def initialize(form_answer, use_latest_version=false) def run # Create a tempfile @tmpfile = Tempfile.new([tempfile_name, '.pdf']) - - # set to binary mode to avoid UTF-8 conversion errors - tmpfile.binmode - - # Use render to write the file contents - tmpfile.write pdf.render - - # Upload the tempfile with your Carrierwave uploader - attach_generated_file! - - # Close the tempfile and delete it - tmpfile.close - tmpfile.unlink + + begin + # set to binary mode to avoid UTF-8 conversion errors + tmpfile.binmode + + # Use render to write the file contents + tmpfile.write pdf.render + + # Upload the tempfile with your Carrierwave uploader + attach_generated_file! + ensure + # Close the tempfile and delete it + tmpfile.close + tmpfile.unlink if tmpfile + end end private diff --git a/app/policies/palace_invite_policy.rb b/app/policies/palace_invite_policy.rb new file mode 100644 index 000000000..5846e93ff --- /dev/null +++ b/app/policies/palace_invite_policy.rb @@ -0,0 +1,6 @@ +class PalaceInvitePolicy < ApplicationPolicy + def update? + admin? || + subject.lead_or_assigned?(record.form_answer) + end +end diff --git a/app/services/assessment_submission_service.rb b/app/services/assessment_submission_service.rb index 76b3f61f7..c2dd1b691 100644 --- a/app/services/assessment_submission_service.rb +++ b/app/services/assessment_submission_service.rb @@ -156,11 +156,11 @@ def check_if_there_are_any_discrepancies_between_primary_and_secondary_appraisal res = { discrepancies: discrepancies, - primary_assessor_name: primary_assessor.full_name, - primary_assessor_email: primary_assessor.email, + primary_assessor_name: primary_assessor&.full_name, + primary_assessor_email: primary_assessor&.email, primary_assessor_submitted_at: format_date(primary_assessment.submitted_at), - secondary_assessor_name: secondary_assessor.full_name, - secondary_assessor_email: secondary_assessor.email, + secondary_assessor_name: secondary_assessor&.full_name, + secondary_assessor_email: secondary_assessor&.email, secondary_assessor_submitted_at: format_date(secondary_assessment.submitted_at) } diff --git a/app/validators/advanced_email_validator.rb b/app/validators/advanced_email_validator.rb index fef46dec3..eda403dc9 100644 --- a/app/validators/advanced_email_validator.rb +++ b/app/validators/advanced_email_validator.rb @@ -24,12 +24,12 @@ def has_mx_records(domain) end def set_error(record) - record.errors.add(:email, "is not a valid address") + record.errors.add(:email, :invalid) end def maybe_set_error(record, message) yield.tap do |value| - record.errors.add(:email, message) if value + record.errors.add(:email, message: message) if value end end diff --git a/app/validators/company_registration_number_validator.rb b/app/validators/company_registration_number_validator.rb index 21c582083..1f155421e 100644 --- a/app/validators/company_registration_number_validator.rb +++ b/app/validators/company_registration_number_validator.rb @@ -18,7 +18,7 @@ def validate_each(record, attribute, value) return if options[:allow_blank] && value.blank? return if options[:allow_empty] && value == "" unless self.class.valid?(value) - record.errors[attribute] << (options[:message] || "is not a valid company registration number") + record.errors.add(attribute, message: options[:message] || "is not a valid company registration number") end end end diff --git a/app/validators/file_size_validator.rb b/app/validators/file_size_validator.rb index a2f729339..fad3ec6d3 100644 --- a/app/validators/file_size_validator.rb +++ b/app/validators/file_size_validator.rb @@ -56,7 +56,7 @@ def validate_each(record, attribute, value) default_message = options[MESSAGES[key]] errors_options[:message] ||= default_message if default_message - record.errors.add(attribute, MESSAGES[key], errors_options) + record.errors.add(attribute, MESSAGES[key], **errors_options) end end diff --git a/app/views/admin/admins/_form.html.slim b/app/views/admin/admins/_form.html.slim index 15ec8cd34..d714f5004 100644 --- a/app/views/admin/admins/_form.html.slim +++ b/app/views/admin/admins/_form.html.slim @@ -1,6 +1,6 @@ .js-admin-strict-password-form -= simple_form_for [:admin, resource], html: { class: 'qae-form' } do |f| += simple_form_for [:admin, resource], html: { class: 'qae-form', data: { type: "json", controller: "inline-flash element-scroll", inline_flash_target: "form" } } do |f| .row.question-group .col-lg-2.col-md-3.col-sm-4 h3 = f.label :first_name, class: "form-label" @@ -26,6 +26,14 @@ .question-group#password-change-panel #password-control-group h3 = f.label :password, class: "form-label" + .guidance-panel.if-no-js-hide + .govuk-form-group--error#password-guidance + p.govuk-error-message.text-underline Please improve your password + p.govuk-error-message#password-too-short + ' It must be at least 10 characters. + p.govuk-error-message#parts-of-email It shouldn't include part or all of your email address. + p.govuk-error-message#password-entropy + ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. .row .col-md-4.col-sm-6 .input-group @@ -33,23 +41,15 @@ wrapper_html: { class: 'form-group' }, input_html: { class: 'form-control', "data-min-password-length" => "10" }, label: false - span#password-result-span.input-group-addon + span#password-result-span.input-group-addon.hide i#password-result.glyphicon.glyphicon-ok .clear - .guidance-panel.if-no-js-hide - #password-guidance - br - .alert.alert-warning - p.text-underline Please improve your password - p#password-too-short - ' It must be at least 10 characters. - p#parts-of-email It shouldn't include part or all of your email address. - p#password-entropy - ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. - #password-confirmation-control-group h3 = f.label :password_confirmation, label: "Retype password", class: "form-label" + .if-no-js-hide + .govuk-form-group--error#password-confirmation-guidance + p.govuk-error-message#password-confirmation-match The confirmation must match the password .row .col-md-4.col-sm-6 .input-group @@ -57,16 +57,10 @@ wrapper_html: { class: 'form-group' }, input_html: { class: 'form-control' }, label: false - span#password-confirmation-result-span.input-group-addon + span#password-confirmation-result-span.input-group-addon.hide i#password-confirmation-result.glyphicon.glyphicon-ok .clear - .if-no-js-hide - #password-confirmation-guidance - br - .alert.alert-warning - p#password-confirmation-match The confirmation must match the password - br .row .col-md-4.col-sm-6 diff --git a/app/views/admin/assessors/_form.html.slim b/app/views/admin/assessors/_form.html.slim index 2a1b556d3..4af2e46ce 100644 --- a/app/views/admin/assessors/_form.html.slim +++ b/app/views/admin/assessors/_form.html.slim @@ -1,4 +1,4 @@ -= simple_form_for [:admin, resource], as: :assessor, url: resource.persisted? ? admin_assessor_path(resource) : admin_assessors_path, html: { class: 'qae-form' } do |f| += simple_form_for [:admin, resource], as: :assessor, url: resource.persisted? ? admin_assessor_path(resource) : admin_assessors_path, html: { class: 'qae-form', data: { controller: "inline-flash element-scroll", inline_flash_target: "form" } } do |f| .row.question-group .col-lg-2.col-md-3.col-sm-4 h2 = f.label :first_name, class: "form-label" @@ -42,6 +42,14 @@ .question-group#password-change-panel #password-control-group h2 = f.label :password, class: "form-label" + .guidance-panel.if-no-js-hide + .govuk-form-group--error#password-guidance + p.govuk-error-message.text-underline Please improve your password + p.govuk-error-message#password-too-short + ' It must be at least 10 characters. + p.govuk-error-message#parts-of-email It shouldn't include part or all of your email address. + p.govuk-error-message#password-entropy + ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. .row .col-md-4.col-sm-6 .input-group @@ -49,22 +57,15 @@ wrapper_html: { class: 'form-group' }, input_html: { class: 'form-control' }, label: false - span#password-result-span.input-group-addon + span#password-result-span.input-group-addon.hide i#password-result.glyphicon.glyphicon-ok .clear - .guidance-panel.if-no-js-hide - #password-guidance - br - .alert.alert-warning - p.text-underline Please improve your password - p#password-too-short - ' It must be at least 10 characters. - p#parts-of-email It shouldn't include part or all of your email address. - p#password-entropy - ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. #password-confirmation-control-group h2 = f.label :password_confirmation, label: "Retype password", class: "form-label" + .if-no-js-hide + .govuk-form-group--error#password-confirmation-guidance + p.govuk-error-message#password-confirmation-match The confirmation must match the password .row .col-md-4.col-sm-6 .input-group @@ -72,16 +73,10 @@ wrapper_html: { class: 'form-group' }, input_html: { class: 'form-control' }, label: false - span#password-confirmation-result-span.input-group-addon + span#password-confirmation-result-span.input-group-addon.hide i#password-confirmation-result.glyphicon.glyphicon-ok .clear - .if-no-js-hide - #password-confirmation-guidance - br - .alert.alert-warning - p#password-confirmation-match The confirmation must match the password - br .row diff --git a/app/views/admin/audit_certificate/_form.html.slim b/app/views/admin/audit_certificate/_form.html.slim index 943f44582..dbb14c45c 100644 --- a/app/views/admin/audit_certificate/_form.html.slim +++ b/app/views/admin/audit_certificate/_form.html.slim @@ -3,15 +3,15 @@ / whether file was saved or not .span = hidden_field_tag "valid", "false", id: "form-audit_certificate-valid" -= form_for [namespace_name, form_answer, audit_certificate], html: { multipart: true, data: { inline_flash_target: "form" } } do |form| += form_for [namespace_name, form_answer, audit_certificate], html: { multipart: true, data: { inline_flash_target: "form", action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error" } } do |form| .well.js-attachment-form h3 Attach document - if form.object.errors.any? .errors - .alert.alert-danger[data-controller="element-removal" role="alert"'] + .alert.alert-danger[data-controller="element-removal" role="alert"] = form.object.errors.full_messages.join(", ") - button[type="button" class="close" data-action="click->element-removal#remove" aria-label="Close" style="font-size: 18px;"] + button[type="button" class="close" data-action="click->element-removal#remove" aria-label="Close"] span[aria-hidden="true"] × .attachment-link.if-js-hide diff --git a/app/views/admin/comments/_form.html.slim b/app/views/admin/comments/_form.html.slim index a2c28adab..8019516df 100644 --- a/app/views/admin/comments/_form.html.slim +++ b/app/views/admin/comments/_form.html.slim @@ -2,7 +2,7 @@ label ' Add a comment = f.hidden_field :section, id: "#{f.object.section}_section_hidden_field" - = f.text_area :body, id: "#{f.object.section}_comment_body", autofocus: true, class: 'form-control', rows: 4, "data-behavior" => "autosave", "data-autosave-key" => "#{@form_answer.id}-#{f.object.section}-new-comment", required: true + = f.text_area :body, id: "#{f.object.section}_comment_body", class: 'form-control', rows: 4, "data-behavior" => "autosave", "data-autosave-key" => "#{@form_answer.id}-#{f.object.section}-new-comment", required: true .comment-actions = f.check_box :flagged, id: "#{f.object.section}_flagged_hidden_checkbox" = link_to "#flag-comment", class: "link-flag-comment js-link-flag-comment" diff --git a/app/views/admin/dashboard/_applications_report.html.slim b/app/views/admin/dashboard/_applications_report.html.slim index 44f48f55a..9b2592d2e 100644 --- a/app/views/admin/dashboard/_applications_report.html.slim +++ b/app/views/admin/dashboard/_applications_report.html.slim @@ -1,9 +1,12 @@ .dashboard-table__wrapper.dashboard-report - button.btn.btn-primary.btn--load.update-report href=url aria-label="Load #{title} data" + button.btn.btn-primary.btn--load.update-report.if-no-js-hide href=url aria-label="Load #{title} data" | Load data + = link_to "Download data", url, class: "btn btn-primary btn--load update-report if-js-hide", aria: { label: "Download #{title} data" } button.btn.btn-primary.btn--loading.updating-data.hidden aria-label="Updating #{title} data" | Updating data + span.sr-only role="status" aria-live="polite" aria-relevant="all" + table.dashboard-table.dashboard-table--small width="1000" caption = title diff --git a/app/views/admin/dashboard/totals_by_day.html.slim b/app/views/admin/dashboard/totals_by_day.html.slim index ece3fcb8f..0fabfcde6 100644 --- a/app/views/admin/dashboard/totals_by_day.html.slim +++ b/app/views/admin/dashboard/totals_by_day.html.slim @@ -9,15 +9,25 @@ p.dashboard__subheading | Running totals compared to previous years - .dashboard__section + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: all_applications_admin_dashboard_reports_path(kind: "by_day"), title: "All applications", kind: "by_day" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: all_applications_admin_dashboard_reports_path(kind: "by_day", format: :csv), title: "All applications", kind: "by_day" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: international_trade_admin_dashboard_reports_path(kind: "by_day"), title: "International trade", kind: "by_day" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: international_trade_admin_dashboard_reports_path(kind: "by_day", format: :csv), title: "International trade", kind: "by_day" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: innovation_admin_dashboard_reports_path(kind: "by_day"), title: "Innovation", kind: "by_day" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: innovation_admin_dashboard_reports_path(kind: "by_day", format: :csv), title: "Innovation", kind: "by_day" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: social_mobility_admin_dashboard_reports_path(kind: "by_day"), title: "Social mobility", kind: "by_day" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: social_mobility_admin_dashboard_reports_path(kind: "by_day", format: :csv), title: "Social mobility", kind: "by_day" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: sustainable_development_admin_dashboard_reports_path(kind: "by_day"), title: "Sustainable development", kind: "by_day" + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: sustainable_development_admin_dashboard_reports_path(kind: "by_day", format: :csv), title: "Sustainable development", kind: "by_day" .dashboard__section = render "admin/dashboard/totals_by_day/registrations" diff --git a/app/views/admin/dashboard/totals_by_day/_registrations.html.slim b/app/views/admin/dashboard/totals_by_day/_registrations.html.slim index 3040fc893..b69b2e951 100644 --- a/app/views/admin/dashboard/totals_by_day/_registrations.html.slim +++ b/app/views/admin/dashboard/totals_by_day/_registrations.html.slim @@ -1,9 +1,13 @@ .dashboard-table__wrapper.dashboard-report - button.btn.btn-primary.btn--load.update-report href=account_registrations_admin_dashboard_reports_path(kind: "by_day") aria-label="Load data for new account registrations table" + button.btn.btn-primary.btn--load.update-report.if-no-js-hide href=account_registrations_admin_dashboard_reports_path(kind: "by_day") aria-label="Load data for new account registrations table" | Load data + = link_to "Download data", account_registrations_admin_dashboard_reports_path(kind: "by_day", format: :csv), + class: "btn btn-primary btn--load update-report if-js-hide", aria: { label: "Download registration totals by day" } button.btn.btn-primary.btn--loading.updating-data.hidden | Updating data + span.sr-only role="status" aria-live="polite" aria-relevant="all" + table.dashboard-table.dashboard-table--small width="1000" caption | New account registrations diff --git a/app/views/admin/dashboard/totals_by_month.html.slim b/app/views/admin/dashboard/totals_by_month.html.slim index f2d2e509a..5d7ac67ef 100644 --- a/app/views/admin/dashboard/totals_by_month.html.slim +++ b/app/views/admin/dashboard/totals_by_month.html.slim @@ -9,15 +9,25 @@ p.dashboard__subheading | Running totals compared to previous years - .dashboard__section + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: all_applications_admin_dashboard_reports_path(kind: "by_month"), title: "All applications", kind: "by_month" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: all_applications_admin_dashboard_reports_path(kind: "by_month", format: :csv), title: "All applications", kind: "by_month" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: international_trade_admin_dashboard_reports_path(kind: "by_month"), title: "International trade", kind: "by_month" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: international_trade_admin_dashboard_reports_path(kind: "by_month", format: :csv), title: "International trade", kind: "by_month" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: innovation_admin_dashboard_reports_path(kind: "by_month"), title: "Innovation", kind: "by_month" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: innovation_admin_dashboard_reports_path(kind: "by_month", format: :csv), title: "Innovation", kind: "by_month" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: social_mobility_admin_dashboard_reports_path(kind: "by_month"), title: "Social mobility", kind: "by_month" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: social_mobility_admin_dashboard_reports_path(kind: "by_month", format: :csv), title: "Social mobility", kind: "by_month" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: sustainable_development_admin_dashboard_reports_path(kind: "by_month"), title: "Sustainable development", kind: "by_month" + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: sustainable_development_admin_dashboard_reports_path(kind: "by_month", format: :csv), title: "Sustainable development", kind: "by_month" .dashboard__section = render "admin/dashboard/totals_by_month/registrations" diff --git a/app/views/admin/dashboard/totals_by_month/_registrations.html.slim b/app/views/admin/dashboard/totals_by_month/_registrations.html.slim index ed6d8d4da..8aec25538 100644 --- a/app/views/admin/dashboard/totals_by_month/_registrations.html.slim +++ b/app/views/admin/dashboard/totals_by_month/_registrations.html.slim @@ -1,6 +1,8 @@ .dashboard-table__wrapper.dashboard-report button.btn.btn-primary.btn--load.update-report href=account_registrations_admin_dashboard_reports_path(kind: "by_month") aria-label="Load data for new account registrations table" | Load data + = link_to "Download data", account_registrations_admin_dashboard_reports_path(kind: "by_month", format: :csv), + class: "btn btn-primary btn--load update-report if-js-hide", aria: { label: "Download registration totals by month" } button.btn.btn-primary.btn--loading.updating-data.hidden | Updating data diff --git a/app/views/admin/dashboard/totals_by_week.html.slim b/app/views/admin/dashboard/totals_by_week.html.slim index 565b8ba4d..e8cbdc598 100644 --- a/app/views/admin/dashboard/totals_by_week.html.slim +++ b/app/views/admin/dashboard/totals_by_week.html.slim @@ -9,15 +9,25 @@ p.dashboard__subheading | Running totals compared to previous years - .dashboard__section + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: all_applications_admin_dashboard_reports_path(kind: "by_week"), title: "All applications", kind: "by_week" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: all_applications_admin_dashboard_reports_path(kind: "by_week", format: :csv), title: "All applications", kind: "by_week" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: international_trade_admin_dashboard_reports_path(kind: "by_week"), title: "International trade", kind: "by_week" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: international_trade_admin_dashboard_reports_path(kind: "by_week", format: :csv), title: "International trade", kind: "by_week" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: innovation_admin_dashboard_reports_path(kind: "by_week"), title: "Innovation", kind: "by_week" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: innovation_admin_dashboard_reports_path(kind: "by_week", format: :csv), title: "Innovation", kind: "by_week" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: social_mobility_admin_dashboard_reports_path(kind: "by_week"), title: "Social mobility", kind: "by_week" - .dashboard__section + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: social_mobility_admin_dashboard_reports_path(kind: "by_week", format: :csv), title: "Social mobility", kind: "by_week" + .dashboard__section.if-no-js-hide = render "admin/dashboard/applications_report", url: sustainable_development_admin_dashboard_reports_path(kind: "by_week"), title: "Sustainable development", kind: "by_week" + .dashboard__section.if-js-hide + = render "admin/dashboard/applications_report", url: sustainable_development_admin_dashboard_reports_path(kind: "by_week", format: :csv), title: "Sustainable development", kind: "by_week" .dashboard__section = render "admin/dashboard/totals_by_week/registrations" diff --git a/app/views/admin/dashboard/totals_by_week/_registrations.html.slim b/app/views/admin/dashboard/totals_by_week/_registrations.html.slim index cbb416c7a..985c7edae 100644 --- a/app/views/admin/dashboard/totals_by_week/_registrations.html.slim +++ b/app/views/admin/dashboard/totals_by_week/_registrations.html.slim @@ -1,6 +1,8 @@ .dashboard-table__wrapper.dashboard-report - button.btn.btn-primary.btn--load.update-report href=account_registrations_admin_dashboard_reports_path(kind: "by_week") aria-label="Load data for new account registrations table" + button.btn.btn-primary.btn--load.update-report.if-no-js-hide href=account_registrations_admin_dashboard_reports_path(kind: "by_week") aria-label="Load data for new account registrations table" | Load data + = link_to "Download data", account_registrations_admin_dashboard_reports_path(kind: "by_week", format: :csv), + class: "btn btn-primary btn--load update-report if-js-hide", aria: { label: "Download registration totals by week" } button.btn.btn-primary.btn--loading.updating-data.hidden | Updating data diff --git a/app/views/admin/email_notifications/destroy.js.erb b/app/views/admin/email_notifications/destroy.js.erb index b1cf4effd..bef895013 100644 --- a/app/views/admin/email_notifications/destroy.js.erb +++ b/app/views/admin/email_notifications/destroy.js.erb @@ -1,2 +1,3 @@ wrapper = $("#notification-" + "<%= @email_notification.id %>"); +window.fire($(".edit_email_notification", wrapper)[0], 'ajax:x:success', null) wrapper.remove() diff --git a/app/views/admin/figures_and_vat_returns/_actual_figures.html.slim b/app/views/admin/figures_and_vat_returns/_actual_figures.html.slim index 016c534b5..ac1058961 100644 --- a/app/views/admin/figures_and_vat_returns/_actual_figures.html.slim +++ b/app/views/admin/figures_and_vat_returns/_actual_figures.html.slim @@ -1,13 +1,14 @@ label | Variance explanation document -.document-list.space-y-3 - p.p-empty class="#{'visuallyhidden' if figures_form.commercial_figures_file.present?}" - ' No document has been attached. - ul.list-unstyled.list-actions - - if figures_form.commercial_figures_file.present? - = render(partial: "admin/figures_and_vat_returns/file", collection: Array.wrap(figures_form.commercial_figures_file), as: :attachment) - .clear +div[data-controller="inline-flash"] + .document-list.space-y-3 + p.p-empty class="#{'visuallyhidden' if figures_form.commercial_figures_file.present?}" + ' No document has been attached. + ul.list-unstyled.list-actions + - if figures_form.commercial_figures_file.present? + = render(partial: "admin/figures_and_vat_returns/file", collection: Array.wrap(figures_form.commercial_figures_file), as: :attachment) + .clear -#commercial-figures-attachment-form - = render("admin/figures_and_vat_returns/form", form_answer: form_answer, attachment: figures_form.commercial_figures_file || figures_form.build_commercial_figures_file) + #commercial-figures-attachment-form + = render("admin/figures_and_vat_returns/form", form_answer: form_answer, attachment: figures_form.commercial_figures_file || figures_form.build_commercial_figures_file) diff --git a/app/views/admin/figures_and_vat_returns/_file.html.slim b/app/views/admin/figures_and_vat_returns/_file.html.slim index 573d9f6d8..ddf1b1a8f 100644 --- a/app/views/admin/figures_and_vat_returns/_file.html.slim +++ b/app/views/admin/figures_and_vat_returns/_file.html.slim @@ -3,7 +3,7 @@ - if show_shortlisted_document?(attachment) small.pull-right = form_for([namespace_name, attachment.form_answer, attachment], - html: { class: "#{attachment.model_name.param_key.dasherize}-destroyer", method: :delete, style: "display:inline-block;" }) do |f| + html: { class: "#{attachment.model_name.param_key.dasherize}-destroyer", method: :delete, style: "display:inline-block;", data: { inline_flash_target: "form", action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error" } }) do |f| = f.submit 'Remove', class: 'if-js-hide' diff --git a/app/views/admin/figures_and_vat_returns/_form.html.slim b/app/views/admin/figures_and_vat_returns/_form.html.slim index 7b2be8c2f..8a5f591c9 100644 --- a/app/views/admin/figures_and_vat_returns/_form.html.slim +++ b/app/views/admin/figures_and_vat_returns/_form.html.slim @@ -11,7 +11,7 @@ .errors .alert.alert-danger[data-controller="element-removal" role="alert"] = form.object.errors.full_messages.join(", ") - button[type="button" class="close" data-action="click->element-removal#remove" aria-label="Close" style="font-size: 18px;"] + button[type="button" class="close" data-action="click->element-removal#remove" aria-label="Close"] span[aria-hidden="true"] × .attachment-link.if-js-hide diff --git a/app/views/admin/figures_and_vat_returns/_vat_returns.html.slim b/app/views/admin/figures_and_vat_returns/_vat_returns.html.slim index 8d9aeb453..9d2e27bc5 100644 --- a/app/views/admin/figures_and_vat_returns/_vat_returns.html.slim +++ b/app/views/admin/figures_and_vat_returns/_vat_returns.html.slim @@ -1,13 +1,14 @@ label | Latest financial statements and VAT returns -.document-list.space-y-3 - p.p-empty class="#{'visuallyhidden' if figures_form.vat_returns_files.any?}" - ' No documents have been attached. - ul.list-unstyled.list-actions - - if figures_form.vat_returns_files.any? - = render(partial: "admin/figures_and_vat_returns/file", collection: figures_form.vat_returns_files, as: :attachment) - .clear +div[data-controller="inline-flash"] + .document-list.space-y-3 + p.p-empty class="#{'visuallyhidden' if figures_form.vat_returns_files.any?}" + ' No documents have been attached. + ul.list-unstyled.list-actions + - if figures_form.vat_returns_files.any? + = render(partial: "admin/figures_and_vat_returns/file", collection: figures_form.vat_returns_files, as: :attachment) + .clear -#vat-returns-attachment-form - = render("admin/figures_and_vat_returns/form", form_answer: form_answer, attachment: figures_form.vat_returns_files.build) + #vat-returns-attachment-form + = render("admin/figures_and_vat_returns/form", form_answer: form_answer, attachment: figures_form.vat_returns_files.build) diff --git a/app/views/admin/form_answer_attachments/_form.html.slim b/app/views/admin/form_answer_attachments/_form.html.slim index 14127c8da..04b8c1908 100644 --- a/app/views/admin/form_answer_attachments/_form.html.slim +++ b/app/views/admin/form_answer_attachments/_form.html.slim @@ -3,14 +3,18 @@ / whether file was saved or not .span = hidden_field_tag "valid", "false", id: "form-answer-attachment-valid" -= form_for [namespace_name, form_answer, form_answer_attachment], html: { multipart: true } do |form| += form_for [namespace_name, form_answer, form_answer_attachment], html: { multipart: true, data: { inline_flash_target: "form", action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error" } } do |form| .well.js-attachment-form h3 Attach document - if form.object.errors.any? - .errors = form.object.errors.full_messages.join(", ") + .alert.alert-danger[data-controller="element-removal" role="alert"] + = form.object.errors.full_messages.join(", ") + button[type="button" class="close" data-action="click->element-removal#remove" aria-label="Close"] + span[aria-hidden="true"] × .attachment-link.if-js-hide + = form.label :file, "Attach document", class: "visuallyhidden" = form.file_field :file .form-group diff --git a/app/views/admin/form_answer_attachments/_form_answer_attachment.html.slim b/app/views/admin/form_answer_attachments/_form_answer_attachment.html.slim index 7a2d2bfe2..99356008b 100644 --- a/app/views/admin/form_answer_attachments/_form_answer_attachment.html.slim +++ b/app/views/admin/form_answer_attachments/_form_answer_attachment.html.slim @@ -4,7 +4,7 @@ - if show_remove_form_answer_attachment?(form_answer_attachment) small.pull-right = form_for([namespace_name, form_answer_attachment.form_answer, form_answer_attachment], - html: { data: { inline_flash_target: "form" }, method: :delete, style: "display: inline-block;" }) do |f| + html: { data: { inline_flash_target: "form", action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error" }, method: :delete, style: "display: inline-block;" }) do |f| = f.submit 'Remove', class: 'if-js-hide' diff --git a/app/views/admin/form_answers/_comment.html.slim b/app/views/admin/form_answers/_comment.html.slim index d5ab863bb..1549cab69 100644 --- a/app/views/admin/form_answers/_comment.html.slim +++ b/app/views/admin/form_answers/_comment.html.slim @@ -6,7 +6,7 @@ p Delete this comment? .btn-actions = form_for [namespace_name, @form_answer, comment], - html: { method: :delete, class: "destroy-comment"} do |f| + html: { method: :delete, class: "destroy-comment", id: "overlay-destroy-comment", data: { inline_flash_target: "form", action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error" } } do |f| = f.submit 'Yes', class: "btn btn-danger link-delete-comment-confirm" '   @@ -25,7 +25,7 @@ - if comment.author?(current_subject) = form_for [namespace_name, @form_answer, comment], - html: { method: :delete, class: "destroy-comment"} do |f| + html: { method: :delete, class: "destroy-comment", id: "destroy-comment", data: { inline_flash_target: "form", action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error" } } do |f| = f.submit 'Delete', class: "btn btn-danger link-delete-comment-confirm" @@ -37,7 +37,7 @@ authenticity_token: true) do |f| label.if-js-hide Flag - = f.check_box :flagged, class: "flag-comment-checkbox if-js-hide" + = f.check_box :flagged, class: "flag-comment-checkbox if-js-hide", id: "flag-comment-#{comment.id}" .comment-action = l comment.created_at, format: :date_at_time span.divider • diff --git a/app/views/admin/form_answers/_financials_review.html.slim b/app/views/admin/form_answers/_financials_review.html.slim index 082ed2b1a..af85daba9 100644 --- a/app/views/admin/form_answers/_financials_review.html.slim +++ b/app/views/admin/form_answers/_financials_review.html.slim @@ -7,7 +7,7 @@ = simple_form_for([namespace_name, review_audit_certificate], remote: true, authenticity_token: true, - html: { controller: "inline-flash", inline_flash_target: "form" }) do |f| + html: { data: { controller: "inline-flash", inline_flash_target: "form" } }) do |f| = f.input :form_answer_id, as: :hidden diff --git a/app/views/admin/form_answers/_previous_applications_section.html.slim b/app/views/admin/form_answers/_previous_applications_section.html.slim index 997af30f8..172ab2fc2 100644 --- a/app/views/admin/form_answers/_previous_applications_section.html.slim +++ b/app/views/admin/form_answers/_previous_applications_section.html.slim @@ -10,4 +10,5 @@ - else = application.award_type_and_year - else - ' None + li + ' None diff --git a/app/views/admin/form_answers/_section_appraisal_form_moderated.html.slim b/app/views/admin/form_answers/_section_appraisal_form_moderated.html.slim index 4e34590b7..00d8ad505 100644 --- a/app/views/admin/form_answers/_section_appraisal_form_moderated.html.slim +++ b/app/views/admin/form_answers/_section_appraisal_form_moderated.html.slim @@ -1,18 +1,18 @@ -.panel.panel-default +.panel.panel-default[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#appraisal-form-moderated-heading h3.panel-title - a data-toggle="collapse" data-parent="#panel-assessment" href="#section-appraisal-form-moderated" aria-expanded="true" aria-controls="section-appraisal-form-moderated" + a data-toggle="collapse" data-parent="#panel-assessment" href="#section-appraisal-form-moderated" aria-expanded="true" aria-controls="section-appraisal-form-moderated" data-element-focus-target="reveal" ' Appraisal Form (Moderated) - if moderated_assessment.last_editor_info.present? small = moderated_assessment.last_editor_info - #section-appraisal-form-moderated.section-appraisal-form.section-appraisal-form-moderated.panel-collapse.collapse[aria-labelledby="appraisal-form-moderated-heading"] + #section-appraisal-form-moderated.section-appraisal-form.section-appraisal-form-moderated.panel-collapse.collapse[aria-labelledby="appraisal-form-moderated-heading" data-controller="appraisal-form" data-appraisal-form-success-value="Appraisal form (moderated) has been submitted"] .panel-body[data-controller="inline-flash"] = simple_form_for([namespace_name, moderated_assessment], remote: true, authenticity_token: true, - html: { data: { type: "json", inline_flash_target: "form", controller: "html5-form-validation", html5_form_validation_selectors_value: ["textarea"] } }) do |f| + html: { data: { type: "json", inline_flash_target: "form" } }) do |f| = render_section(resource, f) = hidden_field_tag :updated_section diff --git a/app/views/admin/form_answers/_section_appraisal_form_primary.html.slim b/app/views/admin/form_answers/_section_appraisal_form_primary.html.slim index 12430e2f2..b6708f1dc 100644 --- a/app/views/admin/form_answers/_section_appraisal_form_primary.html.slim +++ b/app/views/admin/form_answers/_section_appraisal_form_primary.html.slim @@ -1,17 +1,17 @@ -.panel.panel-default +.panel.panel-default[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#appraisal-form-primary-heading h3.panel-title - a data-toggle="collapse" data-parent="#panel-assessment" href="#section-appraisal-form-primary" aria-expanded="true" aria-controls="section-appraisal-form-primary" + a data-toggle="collapse" data-parent="#panel-assessment" href="#section-appraisal-form-primary" aria-expanded="true" aria-controls="section-appraisal-form-primary" data-element-focus-target="reveal" ' Appraisal Form (Primary) - if primary_assessment.last_editor_info.present? small = primary_assessment.last_editor_info - #section-appraisal-form-primary.section-appraisal-form.section-appraisal-form-primary.panel-collapse.collapse aria-labelledby="appraisal-form-primary-heading" + #section-appraisal-form-primary.section-appraisal-form.section-appraisal-form-primary.panel-collapse.collapse[aria-labelledby="appraisal-form-primary-heading" data-controller="appraisal-form" data-appraisal-form-success-value="Appraisal form (primary) has been submitted"] .panel-body[data-controller="inline-flash"] = simple_form_for([namespace_name, primary_assessment], remote: true, authenticity_token: true, - html: { data: { type: "json", inline_flash_target: "form", html5_form_validation_selectors_value: ["textarea"], controller: "html5-form-validation" }, id: "primary_appraisal_form" }) do |f| + html: { data: { type: "json", inline_flash_target: "form" }, id: "primary_appraisal_form" }) do |f| = hidden_field_tag :updated_section, nil, id: "primary_updated_section_hidden_field" = render_section(resource, f) diff --git a/app/views/admin/form_answers/_section_appraisal_form_secondary.html.slim b/app/views/admin/form_answers/_section_appraisal_form_secondary.html.slim index 27e6acd61..c76ed0d29 100644 --- a/app/views/admin/form_answers/_section_appraisal_form_secondary.html.slim +++ b/app/views/admin/form_answers/_section_appraisal_form_secondary.html.slim @@ -1,17 +1,17 @@ -.panel.panel-default +.panel.panel-default[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#appraisal-form-secondary-heading h3.panel-title - a data-toggle="collapse" data-parent="#panel-assessment" href="#section-appraisal-form-secondary" aria-expanded="true" aria-controls="section-appraisal-form-secondary" + a data-toggle="collapse" data-parent="#panel-assessment" href="#section-appraisal-form-secondary" aria-expanded="true" aria-controls="section-appraisal-form-secondary" data-element-focus-target="reveal" ' Appraisal Form (Secondary) - if secondary_assessment.last_editor_info.present? small = secondary_assessment.last_editor_info - #section-appraisal-form-secondary.section-appraisal-form.section-appraisal-form-secondary.panel-collapse.collapse aria-labelledby="appraisal-form-secondary-heading" + #section-appraisal-form-secondary.section-appraisal-form.section-appraisal-form-secondary.panel-collapse.collapse[aria-labelledby="appraisal-form-secondary-heading" data-controller="appraisal-form" data-appraisal-form-success-value="Appraisal form (secondary) has been submitted"] .panel-body[data-controller="inline-flash"] = simple_form_for([namespace_name, secondary_assessment], remote: true, authenticity_token: true, - html: { data: { type: "json", inline_flash_target: "form", controller: "html5-form-validation", html5_form_validation_selectors_value: ["textarea"] }, id: "secondary_appraisal_form"}) do |f| + html: { data: { type: "json", inline_flash_target: "form" }, id: "secondary_appraisal_form"}) do |f| = hidden_field_tag :updated_section, nil, id: "secondary_updated_section_hidden_field" = render_section(resource, f) diff --git a/app/views/admin/form_answers/_section_case_summary.html.slim b/app/views/admin/form_answers/_section_case_summary.html.slim index 7bc5b8538..0077f5aaf 100644 --- a/app/views/admin/form_answers/_section_case_summary.html.slim +++ b/app/views/admin/form_answers/_section_case_summary.html.slim @@ -1,29 +1,20 @@ - if visible_case_summaries(current_subject, resource) - visible_case_summaries(current_subject, resource).each do |assessment_obj| - assessment = assessment_obj.assessment - .panel.panel-parent + .panel.panel-parent.panel-default[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading id="case-summary-heading-#{assessment.position}" h3.panel-title - a data-toggle="collapse" href="#section-case-summary-#{assessment.position}" aria-expanded="true" aria-controls="section-case-summary-#{assessment.position}" + a data-toggle="collapse" href="#section-case-summary-#{assessment.position}" aria-expanded="true" aria-controls="section-case-summary-#{assessment.position}" data-element-focus-target="reveal" ' Case Summary (for recommended or reserved applications only) small.panel-subtitle-small | This will be presented to the panel members and is used when making the final decision - if assessment.editable.present? small = "Updated by #{message_author_name(assessment.editable)} - #{format_date(assessment.updated_at)}" - .panel-collapse.collapse aria-labelledby="case-summary-heading-#{assessment.position}" id="section-case-summary-#{assessment.position}" class="section-case-summary-#{assessment.position}" + .panel-collapse.collapse[aria-labelledby="case-summary-heading-#{assessment.position}" id="section-case-summary-#{assessment.position}" class="section-case-summary-#{assessment.position}" data-controller="appraisal-form" data-appraisal-form-success-value="Case summary has been submitted"] .panel-body[data-controller="inline-flash"] - /.alert.alert-glyphicon.alert-info - span.glyphicon.glyphicon-info-sign - p.todo-placeholder - ' Some instructions on how to use this or what this section is about. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus. - - / Only show if Moderated Appraisal is submitted - / For Primary Assessor - / It becomes read-only for primary assessor after submission - - = simple_form_for([namespace_name, assessment], remote: true, authenticity_token: true, html: { data: { inline_flash_target: "form", controller: "html5-form-validation", html5_form_validation_selectors_value: ["textarea"] } }) do |f| + = simple_form_for([namespace_name, assessment], remote: true, authenticity_token: true, html: { data: { inline_flash_target: "form" } }) do |f| = render partial: "admin/form_answers/appraisal_form_components/application_background_section", locals: { f: f} = render_section(resource, f) diff --git a/app/views/admin/form_answers/_section_company_details.html.slim b/app/views/admin/form_answers/_section_company_details.html.slim index fc8ce6b9b..ba8074c09 100644 --- a/app/views/admin/form_answers/_section_company_details.html.slim +++ b/app/views/admin/form_answers/_section_company_details.html.slim @@ -1,4 +1,4 @@ -.panel.panel-default[data-controller="element-focus"] +.panel.panel-default[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#company-details-heading h3.panel-title a data-toggle="collapse" data-parent="#panel-application-info" href="#section-company-details" aria-expanded="true" aria-controls="section-company-details" data-element-focus-target="reveal" diff --git a/app/views/admin/form_answers/_section_documents.html.slim b/app/views/admin/form_answers/_section_documents.html.slim index 30017bb46..f25fbebc5 100644 --- a/app/views/admin/form_answers/_section_documents.html.slim +++ b/app/views/admin/form_answers/_section_documents.html.slim @@ -27,16 +27,17 @@ ' Post Shortlisting Docs - if @form_answer.requires_vocf? - .document-list.space-y-2 - = render(partial: "admin/form_answers/docs/status", locals: { form_answer: @form_answer }) - = render "admin/form_answers/docs/post_shortlisting_docs", resource: resource + div[data-controller="inline-flash"] + .document-list.space-y-2 + = render(partial: "admin/form_answers/docs/status", locals: { form_answer: @form_answer }) + = render "admin/form_answers/docs/post_shortlisting_docs", resource: resource - - if @form_answer.audit_certificate.blank? && Settings.after_shortlisting_stage? - .sidebar-section.no-margin-bottom.space-y-3 - - if @form_answer.requires_vocf? - #audit-certificate-form[data-controller="inline-flash"] - - audit_certificate = @form_answer.build_audit_certificate - = render "admin/audit_certificate/form", form_answer: @form_answer, audit_certificate: audit_certificate + - if @form_answer.audit_certificate.blank? && Settings.after_shortlisting_stage? + .sidebar-section.no-margin-bottom.space-y-3 + - if @form_answer.requires_vocf? + #audit-certificate-form[data-controller="inline-flash"] + - audit_certificate = @form_answer.build_audit_certificate + = render "admin/audit_certificate/form", form_answer: @form_answer, audit_certificate: audit_certificate - elsif @form_answer.provided_estimates? - figures_form = @form_answer.shortlisted_documents_wrapper || @form_answer.build_shortlisted_documents_wrapper @@ -57,16 +58,17 @@ .sidebar-section h2 ' Other Docs - .document-list - p.p-empty class="#{'visuallyhidden' if @form_answer.form_answer_attachments.any?}" - ' No documents have been attached. - ul.list-unstyled.list-actions - - if @form_answer.form_answer_attachments.uploaded_not_by_user.any? - = render(partial: "admin/form_answer_attachments/form_answer_attachment", collection: @form_answer.form_answer_attachments.uploaded_not_by_user.visible_for(current_subject)) - .clear - br - #application-attachment-form - = render "admin/form_answer_attachments/form", form_answer: @form_answer, form_answer_attachment: @form_answer.form_answer_attachments.build + div[data-controller="inline-flash"] + .document-list + p.p-empty class="#{'visuallyhidden' if @form_answer.form_answer_attachments.any?}" + ' No documents have been attached. + ul.list-unstyled.list-actions + - if @form_answer.form_answer_attachments.uploaded_not_by_user.any? + = render(partial: "admin/form_answer_attachments/form_answer_attachment", collection: @form_answer.form_answer_attachments.uploaded_not_by_user.visible_for(current_subject)) + .clear + br + #application-attachment-form + = render "admin/form_answer_attachments/form", form_answer: @form_answer, form_answer_attachment: @form_answer.form_answer_attachments.build #audit-certificate-buffer diff --git a/app/views/admin/form_answers/_submitted_view.html.slim b/app/views/admin/form_answers/_submitted_view.html.slim index f2f618d21..56fda0ce7 100644 --- a/app/views/admin/form_answers/_submitted_view.html.slim +++ b/app/views/admin/form_answers/_submitted_view.html.slim @@ -1,10 +1,10 @@ - title "Admin: Application #{@form_answer.urn.presence}" .panel-group#submitted-application-parent-parent - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#application-info-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-application-info" aria-expanded="true" aria-controls="section-application-info" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-application-info" aria-expanded="true" aria-controls="section-application-info" data-element-focus-target="reveal" ' Application info #section-application-info.section-application-info.panel-collapse.collapse.in aria-labelledby="application-info-heading" .panel-body @@ -13,10 +13,10 @@ = render "section_company_details" = render "section_financial_summary" - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#assessment-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-assessment" aria-expanded="true" aria-controls="section-assessment" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-assessment" aria-expanded="true" aria-controls="section-assessment" data-element-focus-target="reveal" ' Assessment #section-assessment.section-application-info.panel-collapse.collapse.in aria-labelledby="assessment-heading" .panel-body @@ -31,10 +31,10 @@ = render "section_case_summary" - if show_feedback_section? - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#feedback-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-feedback" aria-expanded="true" aria-controls="section-feedback" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-feedback" aria-expanded="true" aria-controls="section-feedback" data-element-focus-target="reveal" ' Feedback (for not recommended applications only) small.panel-subtitle-small | This will be sent to the applicants to help improve their business and/or future award applications @@ -48,10 +48,10 @@ = render "admin/feedbacks/section", form_answer: @form_answer - if show_winners_section? - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#winners-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-winners" aria-expanded="true" aria-controls="section-winners" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-winners" aria-expanded="true" aria-controls="section-winners" data-element-focus-target="reveal" ' Recipients #section-winners.section-application-info.panel-collapse.collapse.in aria-labelledby="winners-heading" .panel-body @@ -61,10 +61,10 @@ - if show_palace_attendees_subsection? = render "section_palace_attendees" - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#logs-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-logs" aria-expanded="true" aria-controls="section-logs" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-logs" aria-expanded="true" aria-controls="section-logs" data-element-focus-target="reveal" ' Application Audit Log #section-logs.section-application-info.panel-collapse.collapse aria-labelledby="logs-heading" .panel-body diff --git a/app/views/admin/form_answers/appraisal_form_components/_application_background_section.html.slim b/app/views/admin/form_answers/appraisal_form_components/_application_background_section.html.slim index 1b49d9e47..70ef64e0d 100644 --- a/app/views/admin/form_answers/appraisal_form_components/_application_background_section.html.slim +++ b/app/views/admin/form_answers/appraisal_form_components/_application_background_section.html.slim @@ -23,5 +23,5 @@ = link_to "#", class: "form-edit-link pull-right", data: { element_focus_target: "reveal" } span.glyphicon.glyphicon-pencil ' Edit - = link_to "Save", "#", class: "btn btn-primary form-save-link pull-right if-no-js-hide", data: { action: "click->html5-form-validation#validate" } + = link_to "Save", "#", class: "btn btn-primary form-save-link js-form-save-link pull-right if-no-js-hide", data: { action: "click->appraisal-form#stash" } .clear diff --git a/app/views/admin/form_answers/appraisal_form_components/_case_summary_submit_block.html.slim b/app/views/admin/form_answers/appraisal_form_components/_case_summary_submit_block.html.slim index 377907f0c..4952e8d9b 100644 --- a/app/views/admin/form_answers/appraisal_form_components/_case_summary_submit_block.html.slim +++ b/app/views/admin/form_answers/appraisal_form_components/_case_summary_submit_block.html.slim @@ -1,6 +1,6 @@ - if policy(assessment).can_be_submitted? || policy(assessment).can_be_re_submitted? - = form_tag(url_for([namespace_name, :assessment_submissions]), remote: true, authenticity_token: true, class: "submit-assessment", "data-type" => "json") + = form_tag(url_for([namespace_name, :assessment_submissions]), remote: true, authenticity_token: true, class: "submit-assessment", data: { action: "ajax:x:success->appraisal-form#success ajax:x:error->appraisal-form#error", type: "json" }) = hidden_field_tag :assessment_id, assessment.id .feedback-holder @@ -9,10 +9,21 @@ = submit_tag submit_case_summary_title(assessment), class: "btn btn-primary btn-confirm-submit" .clear -- elsif assessment.submitted? + template[data-role='template'] + - if policy(assessment).admin_or_lead? + - unlock_url = namespace_name == :admin ? unlock_admin_assessment_submission_url(assessment) : unlock_assessor_assessment_submission_url(assessment) - - if policy(assessment).can_unlock? + = form_tag unlock_url, method: :patch do + = hidden_field_tag :assessment_id, assessment.id + .feedback-holder.alert.alert-success + ' Case Summary Submitted + + .pull-right + = submit_tag "Unlock", class: "btn btn-primary" + .clear +- elsif assessment.submitted? + - if policy(assessment).can_unlock? - unlock_url = namespace_name == :admin ? unlock_admin_assessment_submission_url(assessment) : unlock_assessor_assessment_submission_url(assessment) = form_tag unlock_url, method: :patch do @@ -29,6 +40,5 @@ ' Case Summary Submitted - elsif !assessment.submitted? - .feedback-holder.alert.alert-info ' Case Summary is not submitted yet! diff --git a/app/views/admin/form_answers/appraisal_form_components/_non_rag_section.html.slim b/app/views/admin/form_answers/appraisal_form_components/_non_rag_section.html.slim index 3a1610933..b41eb85b8 100644 --- a/app/views/admin/form_answers/appraisal_form_components/_non_rag_section.html.slim +++ b/app/views/admin/form_answers/appraisal_form_components/_non_rag_section.html.slim @@ -26,6 +26,6 @@ span.glyphicon.glyphicon-pencil ' Edit .form-actions.text-right - = link_to "Cancel", "#", class: "btn btn-default form-cancel-link if-no-js-hide", data: { element_focus_target: "dismiss", action: "click->html5-form-validation#discard" } - = link_to "Save", "#", class: "btn btn-primary form-save-link pull-right if-no-js-hide", data: { action: "click->html5-form-validation#validate", updated_section: section.desc } + = link_to "Cancel", "#", class: "btn btn-default form-cancel-link if-no-js-hide", data: { element_focus_target: "dismiss" } + = link_to "Save", "#", class: "btn btn-primary form-save-link js-form-save-link pull-right if-no-js-hide", data: { updated_section: section.desc, action: "click->appraisal-form#stash" } .clear diff --git a/app/views/admin/form_answers/appraisal_form_components/_rag_section.html.slim b/app/views/admin/form_answers/appraisal_form_components/_rag_section.html.slim index 6747cf373..0af874821 100644 --- a/app/views/admin/form_answers/appraisal_form_components/_rag_section.html.slim +++ b/app/views/admin/form_answers/appraisal_form_components/_rag_section.html.slim @@ -46,6 +46,6 @@ span.glyphicon.glyphicon-pencil ' Edit .form-actions.text-right - = link_to "Cancel", "#", class: "btn btn-default form-cancel-link if-no-js-hide", data: { element_focus_target: "dismiss", action: "click->html5-form-validation#discard" } - = link_to "Save", "#", class: "btn btn-primary form-save-link pull-right if-no-js-hide", data: { action: "click->html5-form-validation#validate", updated_section: section.desc } + = link_to "Cancel", "#", class: "btn btn-default form-cancel-link if-no-js-hide", data: { element_focus_target: "dismiss" } + = link_to "Save", "#", class: "btn btn-primary form-save-link js-form-save-link pull-right if-no-js-hide", data: { updated_section: section.desc, action: "click->appraisal-form#stash" } .clear diff --git a/app/views/admin/form_answers/appraisal_form_components/_submit_appraisal_form.html.slim b/app/views/admin/form_answers/appraisal_form_components/_submit_appraisal_form.html.slim index ecf440071..afd2f9ee3 100644 --- a/app/views/admin/form_answers/appraisal_form_components/_submit_appraisal_form.html.slim +++ b/app/views/admin/form_answers/appraisal_form_components/_submit_appraisal_form.html.slim @@ -3,7 +3,7 @@ authenticity_token: true, remote: true, class: "submit-assessment", - data: { type: "json" }) + data: { action: "ajax:x:success->appraisal-form#success ajax:x:error->appraisal-form#error", type: "json" }) = hidden_field_tag :assessment_id, assessment.id, id: "#{assessment.position}_assessment_id_submit_appraisal_form_hidden_field" @@ -13,6 +13,19 @@ = submit_tag (assessment.submitted? ? "Re-submit appraisal" : "Submit appraisal"), class: "btn btn-primary btn-confirm-submit" .clear + template[data-role='template'] + - if policy(assessment).admin_or_lead? + - unlock_url = namespace_name == :admin ? unlock_admin_assessment_submission_url(assessment) : unlock_assessor_assessment_submission_url(assessment) + + = form_tag unlock_url, method: :patch do + = hidden_field_tag :assessment_id, assessment.id, id: "#{assessment.position}_assessment_id_unlock_appraisal_form_hidden_field" + .feedback-holder.alert.alert-success + ' Assessment submitted + + .pull-right + = submit_tag "Unsubmit", class: "btn btn-primary" + .clear + - elsif assessment.submitted? - if policy(assessment).can_unlock? diff --git a/app/views/admin/form_answers/appraisal_form_components/_verdict_section.html.slim b/app/views/admin/form_answers/appraisal_form_components/_verdict_section.html.slim index c0102babf..4eacdcbd2 100644 --- a/app/views/admin/form_answers/appraisal_form_components/_verdict_section.html.slim +++ b/app/views/admin/form_answers/appraisal_form_components/_verdict_section.html.slim @@ -44,6 +44,6 @@ span.glyphicon.glyphicon-pencil ' Edit .form-actions.text-right - = link_to "Cancel", "#", class: "btn btn-default form-cancel-link if-no-js-hide", data: { element_focus_target: "dismiss", action: "click->html5-form-validation#discard" } - = link_to "Save", "#", class: "btn btn-primary form-save-link pull-right if-no-js-hide", data: { action: "click->html5-form-validation#validate" } + = link_to "Cancel", "#", class: "btn btn-default form-cancel-link if-no-js-hide", data: { element_focus_target: "dismiss" } + = link_to "Save", "#", class: "btn btn-primary form-save-link pull-right if-no-js-hide" .clear diff --git a/app/views/admin/form_answers/company_details/_goods_services_form.html.slim b/app/views/admin/form_answers/company_details/_goods_services_form.html.slim index 620fce08e..1351b4703 100644 --- a/app/views/admin/form_answers/company_details/_goods_services_form.html.slim +++ b/app/views/admin/form_answers/company_details/_goods_services_form.html.slim @@ -18,7 +18,7 @@ = simple_form_for [namespace_name, resource], remote: true, authenticity_token: true, html: { data: { type: "html", inline_flash_target: "form" }, id: "goods_services_form_admin_appraisal" } do |f| = hidden_field_tag :section, "goods_services", id: "section_goods_services_hidden_field" .form-fields.form-block - = f.simple_fields_for(:data) do |f| + = f.simple_fields_for :data, include_id: false do |f| - if !@form_answer.trade? = f.input @form_answer.goods_and_services_key, as: :text, label: false, input_html: { class: "form-control js-char-count", rows: 3, "data-word-max" => 15 } - elsif @form_answer.document["trade_goods_and_services_explanations"].present? diff --git a/app/views/admin/form_answers/company_details/_organisation_head_form.html.slim b/app/views/admin/form_answers/company_details/_organisation_head_form.html.slim index 9772d1ea0..c8df35163 100644 --- a/app/views/admin/form_answers/company_details/_organisation_head_form.html.slim +++ b/app/views/admin/form_answers/company_details/_organisation_head_form.html.slim @@ -25,7 +25,7 @@ - if @form_answer.head_of_business_email.present? = @form_answer.head_of_business_email .form-fields.form-block - = f.simple_fields_for(:data) do |f| + = f.simple_fields_for :data, include_id: false do |f| .row .col-md-2 = f.input :head_of_business_title, diff --git a/app/views/admin/form_answers/company_details/_organisation_type_form.html.slim b/app/views/admin/form_answers/company_details/_organisation_type_form.html.slim index 0b08fde0d..8a98a5321 100644 --- a/app/views/admin/form_answers/company_details/_organisation_type_form.html.slim +++ b/app/views/admin/form_answers/company_details/_organisation_type_form.html.slim @@ -20,7 +20,7 @@ .form-fields.form-block .row .col-xs-6.col-md-6 - = f.simple_fields_for(:data) do |f| + = f.simple_fields_for :data, include_id: false do |f| = f.input :organisation_type, collection: AppraisalFormHelper::COMPANY_ORGANISATION_TYPES, label: false, diff --git a/app/views/admin/form_answers/company_details/_registration_number_form.html.slim b/app/views/admin/form_answers/company_details/_registration_number_form.html.slim index a4a84b6bd..2d28b1ac0 100644 --- a/app/views/admin/form_answers/company_details/_registration_number_form.html.slim +++ b/app/views/admin/form_answers/company_details/_registration_number_form.html.slim @@ -16,7 +16,7 @@ - else em.text-muted No company/charity registration number added yet. .form-fields.form-block - = f.simple_fields_for(:data) do |f| + = f.simple_fields_for :data, include_id: false do |f| = f.input :registration_number, as: :string, label: false, diff --git a/app/views/admin/form_answers/company_details/_website_form.html.slim b/app/views/admin/form_answers/company_details/_website_form.html.slim index 319abec8a..271dc91dd 100644 --- a/app/views/admin/form_answers/company_details/_website_form.html.slim +++ b/app/views/admin/form_answers/company_details/_website_form.html.slim @@ -16,7 +16,7 @@ - else em.text-muted No website added. .form-fields.form-block - = f.simple_fields_for(:data) do |f| + = f.simple_fields_for :data, include_id: false do |f| = f.input :website_url, as: :string, label: false, diff --git a/app/views/admin/form_answers/docs/_application_and_supporting_docs.html.slim b/app/views/admin/form_answers/docs/_application_and_supporting_docs.html.slim index 7699b7983..d7eb6c2b9 100644 --- a/app/views/admin/form_answers/docs/_application_and_supporting_docs.html.slim +++ b/app/views/admin/form_answers/docs/_application_and_supporting_docs.html.slim @@ -1,4 +1,4 @@ -.document-list +.document-list[data-controller="inline-flash"] - if @form_answer.form_answer_attachments.uploaded_by_user.any? || @form_answer.support_letters.any? ul.list-unstyled.list-actions diff --git a/app/views/admin/form_answers/winners_components/_palace_attendee.html.slim b/app/views/admin/form_answers/winners_components/_palace_attendee.html.slim index 24f07136b..df16ad612 100644 --- a/app/views/admin/form_answers/winners_components/_palace_attendee.html.slim +++ b/app/views/admin/form_answers/winners_components/_palace_attendee.html.slim @@ -1,4 +1,4 @@ -.form-group.palace-attendee-container[class=("form-edit" if pa.errors.present? || defined?(@enable_edition)) data-controller="element-focus"] +.form-group.palace-attendee-container[class=("form-edit" if pa.errors.present? || defined?(@enable_edition)) data-controller="inline-flash element-focus"] .form-value .empty-message class="#{'visuallyhidden' if palace_invite.palace_attendees.exists?}" p.p-empty No attendees confirmed. diff --git a/app/views/admin/form_answers/winners_components/_palace_attendee_form.html.slim b/app/views/admin/form_answers/winners_components/_palace_attendee_form.html.slim index d73d9814c..1bbf66cf4 100644 --- a/app/views/admin/form_answers/winners_components/_palace_attendee_form.html.slim +++ b/app/views/admin/form_answers/winners_components/_palace_attendee_form.html.slim @@ -2,12 +2,12 @@ ul.list-add.list-attendees class=('if-js-hide' if palace_attendee.new_record? && li.well .pull-right - unless palace_attendee.new_record? - = simple_form_for([namespace_name, palace_attendee], remote: true, authenticity_token: true, method: :delete, html: { class: "remove-palace-attendee-form" }) do |f| + = simple_form_for([namespace_name, palace_attendee], remote: true, authenticity_token: true, method: :delete, html: { class: "remove-palace-attendee-form", data: { inline_flash_target: "form" } }) do |f| = f.input :palace_invite_id, as: :hidden = f.submit "Remove", class: "if-js-hide" = link_to "Remove", "", class: "remove-palace-attendee if-no-js-hide", data: {confirm: "Are you sure?"} - = simple_form_for([namespace_name, palace_attendee], remote: true, authenticity_token: true, html: { class: "palace-attendee-form" }) do |f| + = simple_form_for([namespace_name, palace_attendee], remote: true, authenticity_token: true, html: { class: "palace-attendee-form", data: { inline_flash_target: "form" } }) do |f| = f.input :palace_invite_id, as: :hidden .row .col-md-2 diff --git a/app/views/admin/judges/_form.html.slim b/app/views/admin/judges/_form.html.slim index 63a29d781..e188f7d6b 100644 --- a/app/views/admin/judges/_form.html.slim +++ b/app/views/admin/judges/_form.html.slim @@ -1,4 +1,4 @@ -= simple_form_for [:admin, resource], as: :judge, url: resource.persisted? ? admin_judge_path(resource) : admin_judges_path, html: { class: 'qae-form' } do |f| += simple_form_for [:admin, resource], as: :judge, url: resource.persisted? ? admin_judge_path(resource) : admin_judges_path, html: { class: 'qae-form', data: { type: "json", controller: "inline-flash element-scroll", inline_flash_target: "form" } } do |f| .row.question-group .col-lg-2.col-md-3.col-sm-4 h3 = f.label :first_name, class: "form-label" @@ -26,6 +26,15 @@ .question-group#password-change-panel #password-control-group h3 = f.label :password, class: "form-label" + .guidance-panel.if-no-js-hide + .govuk-form-group--error#password-guidance + p.govuk-error-message.text-underline Please improve your password + p.govuk-error-message#password-too-short + ' It must be at least 10 characters. + p.govuk-error-message#parts-of-email It shouldn't include part or all of your email address. + p.govuk-error-message#password-entropy + ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. + .row .col-md-4.col-sm-6 .input-group @@ -33,19 +42,9 @@ wrapper_html: { class: 'form-group' }, input_html: { class: 'form-control' }, label: false - span#password-result-span.input-group-addon + span#password-result-span.input-group-addon.hide i#password-result.glyphicon.glyphicon-ok .clear - .guidance-panel.if-no-js-hide - #password-guidance - br - .alert.alert-warning - p.text-underline Please improve your password - p#password-too-short - ' It must be at least 10 characters. - p#parts-of-email It shouldn't include part or all of your email address. - p#password-entropy - ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. #password-confirmation-control-group h3 = f.label :password_confirmation, label: "Retype password", class: "form-label" @@ -56,15 +55,13 @@ wrapper_html: { class: 'form-group' }, input_html: { class: 'form-control' }, label: false - span#password-confirmation-result-span.input-group-addon + span#password-confirmation-result-span.input-group-addon.hide i#password-confirmation-result.glyphicon.glyphicon-ok .clear .if-no-js-hide - #password-confirmation-guidance - br - .alert.alert-warning - p#password-confirmation-match The confirmation must match the password + .govuk-form-group--error#password-confirmation-guidance + p.govuk-error-message#password-confirmation-match The confirmation must match the password br diff --git a/app/views/admin/settings/_email_notification.html.slim b/app/views/admin/settings/_email_notification.html.slim index 4fac1ba1b..3f74339f0 100644 --- a/app/views/admin/settings/_email_notification.html.slim +++ b/app/views/admin/settings/_email_notification.html.slim @@ -7,7 +7,7 @@ span.email-notification-help - if (help_message = t("email_notification_help_messages.#{kind}", default: "")).present? - span.glyphicon-info-sign.help-message + span.glyphicon-info-sign.help-message role="tooltip" aria-label=help_message span.help-message-text = help_message diff --git a/app/views/admin/settings/_notification.html.slim b/app/views/admin/settings/_notification.html.slim index b60ca7ba3..60ab8eb71 100644 --- a/app/views/admin/settings/_notification.html.slim +++ b/app/views/admin/settings/_notification.html.slim @@ -14,9 +14,9 @@ li id="notification-#{notification.id}" - if policy(notification).destroy? span.if-no-js-hide ' | - = button_to "Delete", admin_settings_email_notification_path(notification), { onclick: "return confirm('Are you sure?')", method: :delete, remote: true } + = button_to "Delete", admin_settings_email_notification_path(notification), { onclick: "return confirm('Are you sure?')", method: :delete, remote: true, data: { action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error", inline_flash_target: "form" } } .notification-edit-form.well - = simple_form_for notification, url: admin_settings_email_notification_path(notification, year: params[:year]), remote: true, authenticity_token: true, html: { data: { inline_flash_target: "form", controller: "html5-form-validation", html5_form_validation_selectors_value: ["input"] } } do |f| + = simple_form_for notification, url: admin_settings_email_notification_path(notification, year: params[:year]), remote: true, authenticity_token: true, html: { data: { action: "ajax:x:success->inline-flash#success ajax:x:error->inline-flash#error", inline_flash_target: "form", controller: "html5-form-validation", html5_form_validation_selectors_value: ["input"] } } do |f| .control-date label.control-label Edit schedule = f.input :trigger_at, diff --git a/app/views/admin/users/_fields_password.html.slim b/app/views/admin/users/_fields_password.html.slim index 02de52d9a..e9ce0f421 100644 --- a/app/views/admin/users/_fields_password.html.slim +++ b/app/views/admin/users/_fields_password.html.slim @@ -18,6 +18,14 @@ - else = f.label :password, label: "Password" - password_hint = "" + .guidance-panel.if-no-js-hide + .govuk-form-group--error#password-guidance + p.govuk-error-message.text-underline Please improve your password + p.govuk-error-message#password-too-short + ' It must be at least 10 characters. + p.govuk-error-message#parts-of-email It shouldn't include part or all of your email address. + p.govuk-error-message#password-entropy + ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. .row .col-md-4.col-sm-6 .input-group @@ -25,22 +33,15 @@ wrapper_html: { class: "form-group" }, input_html: { class: "form-control password-strength-meter" }, label_html: { class: "visuallyhidden" } - span#password-result-span.input-group-addon + span#password-result-span.input-group-addon.hide i#password-result.glyphicon.glyphicon-ok span.hint = password_hint - .guidance-panel.if-no-js-hide - #password-guidance - .alert.alert-warning - p.text-underline Please improve your password - p#password-too-short - ' It must be at least 10 characters. - p#parts-of-email It shouldn't include part or all of your email address. - p#password-entropy - ' It must be more complex. Consider using whole sentences (with spaces), lyrics or phrases to make it more memorable. - .question-group#password-confirmation-control-group h3 = f.label :password_confirmation, label: "Retype password" + .if-no-js-hide + .govuk-form-group--error#password-confirmation-guidance + p.govuk-error-message#password-confirmation-match The confirmation must match the password .row .col-md-4.col-sm-6 .input-group @@ -48,11 +49,5 @@ wrapper_html: { class: "form-group" }, input_html: { class: "form-control" }, label_html: { class: "visuallyhidden" } - span#password-confirmation-result-span.input-group-addon + span#password-confirmation-result-span.input-group-addon.hide i#password-confirmation-result.glyphicon.glyphicon-ok - - .if-no-js-hide - #password-confirmation-guidance - br - .alert.alert-warning - p#password-confirmation-match The confirmation must match the password diff --git a/app/views/assessor/form_answers/_bulk_assignment.html.slim b/app/views/assessor/form_answers/_bulk_assignment.html.slim index 78ffd2bee..8c1b852c3 100644 --- a/app/views/assessor/form_answers/_bulk_assignment.html.slim +++ b/app/views/assessor/form_answers/_bulk_assignment.html.slim @@ -1,6 +1,7 @@ - if show_bulk_assignment? .if-no-js-hide[data-controller="element-focus"] - .btn.bulk-assign-assessors-link[data-element-focus-target="reveal"] Bulk Assign Assessors + button.btn.bulk-assign-assessors-link[data-element-focus-target="reveal" type="button"] + | Bulk Assign Assessors .bulk-assign-assessors-form .well diff --git a/app/views/assessor/form_answers/_submitted_view.html.slim b/app/views/assessor/form_answers/_submitted_view.html.slim index aeab1be95..5558e7229 100644 --- a/app/views/assessor/form_answers/_submitted_view.html.slim +++ b/app/views/assessor/form_answers/_submitted_view.html.slim @@ -1,8 +1,8 @@ .panel-group#submitted-application-parent - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#application-info-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-application-info" aria-expanded="true" aria-controls="section-application-info" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-application-info" aria-expanded="true" aria-controls="section-application-info" data-element-focus-target="reveal" ' Application info #section-application-info.section-application-info.panel-collapse.collapse.in aria-labelledby="application-info-heading" .panel-body @@ -11,10 +11,10 @@ = render "admin/form_answers/section_company_details" = render "admin/form_answers/section_financial_summary" - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#assessment-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-assessment" aria-expanded="true" aria-controls="section-assessment" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-assessment" aria-expanded="true" aria-controls="section-assessment" data-element-focus-target="reveal" ' Assessment #section-assessment.section-application-info.panel-collapse.collapse.in aria-labelledby="assessment-heading" .panel-body @@ -32,10 +32,10 @@ = render "admin/form_answers/section_case_summary" - if show_feedback_section? - .panel.panel-default.panel-parent + .panel.panel-default.panel-parent[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] .panel-heading#feedback-heading h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-feedback" aria-expanded="true" aria-controls="section-feedback" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-feedback" aria-expanded="true" aria-controls="section-feedback" data-element-focus-target="reveal" ' Feedback (for not recommended applications only) small.panel-subtitle-small | This will be sent to the applicants to help improve their business and/or future award applications @@ -50,9 +50,9 @@ - if show_winners_section? .panel.panel-default.panel-parent - .panel-heading#winners-heading + .panel-heading#winners-heading[data-controller="element-focus" data-element-focus-selector-value="input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href='#']"] h2.panel-title - a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-winners" aria-expanded="true" aria-controls="section-winners" + a data-toggle="collapse" data-parent="#submitted-application-parent" href="#section-winners" aria-expanded="true" aria-controls="section-winners" data-element-focus-target="reveal" ' Recipients #section-winners.section-application-info.panel-collapse.collapse.in aria-labelledby="winners-heading" .panel-body diff --git a/app/views/assessor/form_answers/index.html.slim b/app/views/assessor/form_answers/index.html.slim index 99b88c377..80ef5bf70 100644 --- a/app/views/assessor/form_answers/index.html.slim +++ b/app/views/assessor/form_answers/index.html.slim @@ -34,25 +34,19 @@ h1.admin-page-heading = simple_form_for @search, url: assessor_form_answers_path, method: :get, as: :search, html: { class: 'search-form', id: 'application_table_search_form' } do |f| = hidden_field_tag :award_type, category_picker.current_award_type, id: "award_type_application_table_search_form" - label for="year" class="visuallyhidden" aria-hidden="true" - ' Award year - = text_field_tag :year, @award_year.year, class: "visuallyhidden", aria: { hidden: true } + = hidden_field_tag :year, @award_year.year - # status filters need to be here because the filtering is done in the other form above, so in order not to break filtering, we need to duplicate it here = f.simple_fields_for [:filters, @search.filters] do |h| - = h.label :status, class: "visuallyhidden", for: "status_application_table_search_form", aria: { hidden: true } = h.input :status, collection: FormAnswerStatus::AssessorFilter.options, label: false, - input_html: { multiple: true, class: 'visuallyhidden', id: 'status_application_table_search_form' }, - aria: { hidden: true } + input_html: { multiple: true, id: 'status_application_table_search_form', class: 'hide' } - = h.label :sub_status, class: "visuallyhidden", for: "sub_status_application_table_search_form", aria: { hidden: true } = h.input :sub_status, collection: FormAnswerStatus::AssessorFilter.sub_options(current_assessor), label: false, - input_html: { multiple: true, class: 'visuallyhidden', id: 'sub_status_application_table_search_form'}, - aria: { hidden: true } + input_html: { multiple: true, id: 'sub_status_application_table_search_form', class: 'hide'} .row .col-xs-12 @@ -61,7 +55,6 @@ h1.admin-page-heading tr - if current_subject.categories_as_lead.include?(category_picker.current_award_type) th - span.visuallyhidden Select for bulk action span.if-no-js-hide = check_box_tag :check_all, "Check all", false, aria: { label: "Select all applications for bulk action" } th.sortable width="250" diff --git a/app/views/judge/case_summaries/index.html.slim b/app/views/judge/case_summaries/index.html.slim index 35553156c..c0247edc8 100644 --- a/app/views/judge/case_summaries/index.html.slim +++ b/app/views/judge/case_summaries/index.html.slim @@ -5,7 +5,7 @@ .downloads-page .downloads-page__section - h3 + h2 ' Case Summaries ul.download-list - FormAnswer::AWARD_TYPE_FULL_NAMES.select { |key, value| current_judge.assigned_award_types.include?(key) }.each do |key, value| diff --git a/app/views/layouts/_vertical_admin_award_year.html.slim b/app/views/layouts/_vertical_admin_award_year.html.slim index 453df072e..c41a679eb 100644 --- a/app/views/layouts/_vertical_admin_award_year.html.slim +++ b/app/views/layouts/_vertical_admin_award_year.html.slim @@ -1,4 +1,4 @@ -.col-lg-2.col-md-3.col-sm-4.award-year-z-index.applications-filter +.col-md-3.col-sm-4.award-year-z-index.applications-filter label.applications-filter__label ' Award year .dropdown diff --git a/app/views/layouts/application-admin.html.slim b/app/views/layouts/application-admin.html.slim index 25b0cbe50..edcc389b8 100644 --- a/app/views/layouts/application-admin.html.slim +++ b/app/views/layouts/application-admin.html.slim @@ -79,8 +79,28 @@ html.no-js = link_to "Data Export Log", admin_audit_logs_path ul.nav.navbar-nav.navbar-right - li.dropdown - a.dropdown-toggle href="#" data-toggle="dropdown" role="button" aria-expanded="false" + li + details.if-js-hide.dropdown-toggle + summary + ' My Account + span.caret + ul.dropdown-menu + li + span + strong = current_subject.decorate.full_name + br + small = current_subject.email + li.divider + - unless current_admin.authy_enabled + li + = link_to "Enable 2FA", admin_enable_authy_path + li + = button_to "Sign out", + destroy_admin_session_path, + method: :delete, + class: "btn as-link" + + a.dropdown-toggle.if-no-js-hide href="#" data-toggle="dropdown" role="button" aria-expanded="false" ' My account span.caret ul.dropdown-menu @@ -99,6 +119,7 @@ html.no-js method: :delete = render "shared/dev_or_staging_banner" = render "shared/non_js_banner" + = render "shared/timeout_warning_popup" #main-container role="main" .container diff --git a/app/views/layouts/application-assessor.html.slim b/app/views/layouts/application-assessor.html.slim index 6323148a3..f90988523 100644 --- a/app/views/layouts/application-assessor.html.slim +++ b/app/views/layouts/application-assessor.html.slim @@ -60,8 +60,25 @@ html.no-js = link_to "Reports", assessor_reports_path(year: @award_year.year), role: "tab" ul.nav.navbar-nav.navbar-right - li.dropdown - a.dropdown-toggle href="#" data-toggle="dropdown" role="button" aria-expanded="false" + li + details.if-js-hide + summary + ' My Account + span.caret + ul.dropdown-menu + li + span + strong = current_subject.decorate.full_name + br + small = current_subject.email + li.divider + li + = button_to "Sign out", + destroy_assessor_session_path, + method: :delete, + class: "btn as-link" + + a.dropdown-toggle.if-no-js-hide href="#" data-toggle="dropdown" role="button" aria-expanded="false" ' My account span.caret ul.dropdown-menu @@ -77,6 +94,8 @@ html.no-js method: :delete = render "shared/dev_or_staging_banner" = render "shared/non_js_banner" + = render "shared/timeout_warning_popup" + #main-container role="main" .container @@ -88,6 +107,10 @@ html.no-js .alert-container .alert.alert-danger == alert + - unless flash[:error].blank? + .alert-container + .alert.alert-danger + == flash[:error] = yield diff --git a/app/views/layouts/application-judge.html.slim b/app/views/layouts/application-judge.html.slim index a1d45d573..f8c2e45e2 100644 --- a/app/views/layouts/application-judge.html.slim +++ b/app/views/layouts/application-judge.html.slim @@ -57,8 +57,25 @@ html.no-js = link_to "Downloads", judge_case_summaries_path ul.nav.navbar-nav.navbar-right role="navigation" - li.dropdown - a.dropdown-toggle href="#" data-toggle="dropdown" role="button" aria-expanded="false" + li + details.if-js-hide + summary + ' My Account + span.caret + ul.dropdown-menu + li + span + strong = current_subject.decorate.full_name + br + small = current_subject.email + li.divider + li + = button_to "Sign out", + destroy_judge_session_path, + method: :delete, + class: "btn as-link" + + a.dropdown-toggle.if-no-js-hide href="#" data-toggle="dropdown" role="button" aria-expanded="false" ' My account span.caret ul.dropdown-menu @@ -74,6 +91,7 @@ html.no-js method: :delete = render "shared/dev_or_staging_banner" = render "shared/non_js_banner" + = render "shared/timeout_warning_popup" #main-container role="main" .container @@ -85,6 +103,10 @@ html.no-js .alert-container .alert.alert-danger == alert + - unless flash[:error].blank? + .alert-container + .alert.alert-danger + == flash[:error] = yield diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index fa4520555..0c12654f2 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -1,4 +1,4 @@ -title = content_for?(:title) ? yield(:title) + " - King's Awards for Enterprise" : "King's Awards for Enterprise" +title = content_for?(:title) ? "#{'Appraisal view of ' if admin_in_read_only_mode?}" + yield(:title) + " - King's Awards for Enterprise" : "King's Awards for Enterprise" - content_for :head do = stylesheet_link_tag "application.css" diff --git a/app/views/layouts/govuk_template.html.erb b/app/views/layouts/govuk_template.html.erb index 903d250f9..2e35de968 100644 --- a/app/views/layouts/govuk_template.html.erb +++ b/app/views/layouts/govuk_template.html.erb @@ -20,8 +20,8 @@