diff --git a/.codeclimate.yml b/.codeclimate.yml index 8f6ae9def..a599be873 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -7,19 +7,39 @@ prepare: plugins: brakeman: enabled: true + checks: + mass_assign_permit!: + enabled: false bundler-audit: enabled: true duplication: enabled: true config: + count_threshold: 3 languages: - - ruby - - javascript + ruby: + mass_threshold: 100 + javascript: + mass_threshold: 100 eslint: enabled: true + channel: eslint-5 + fixme: + enabled: true + checks: + TODO: + enabled: false rubocop: enabled: true - channel: rubocop-1-12-1 + # channel: rubocop-1-56-3 + config: .rubocop.yml +checks: + method-lines: + config: + threshold: 40 + method-count: + config: + threshold: 25 exclude_patterns: - "bin/" - "config/" @@ -28,4 +48,5 @@ exclude_patterns: - "vendor/" - "test/" - "karma.conf.js" - - "app/packs/src/semantic/" + - "app/packs/" + - "CHANGELOG.md" \ No newline at end of file diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 62b812245..56806abee 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -15,8 +15,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04] - ruby: [ '3.2.2' ] + os: [ubuntu-22.04] + ruby: [ '3.2', '3.2.2' ] runs-on: ${{ matrix.os }} continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }} steps: @@ -36,14 +36,12 @@ jobs: bundle config set deployment '[secure]' bundle env head -n1 $(which bundle) - - name: Install wkhtmlpdf (push) + + - name: Install wkhtmltopdf (push) run: | - sudo apt-get update - wget http://ppa.launchpad.net/linuxuprising/libpng12/ubuntu/pool/main/libp/libpng/libpng12-0_1.2.54-1ubuntu1.1+1~ppa0~eoan_amd64.deb - sudo dpkg -i libpng12-0_1.2.54-1ubuntu1.1+1~ppa0~eoan_amd64.deb - wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb - sudo apt-get install -y gdebi - sudo gdebi --non-interactive wkhtmltox_0.12.6-1.focal_amd64.deb + sudo apt-get install -y xfonts-base xfonts-75dpi + wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb + sudo dpkg -i wkhtmltox_0.12.6.1-2.jammy_amd64.deb if: github.event_name == 'push' - name: Set ENV for codeclimate (pull_request) @@ -66,6 +64,7 @@ jobs: curl -LSs 'https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64' >./cc-test-reporter; chmod +x ./cc-test-reporter ./cc-test-reporter before-build + - name: Run Tests env: PG_DATABASE: postgres @@ -76,6 +75,7 @@ jobs: RAILS_ENV: test COVERAGE: true DISABLE_SPRING: 1 + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} run: | cp config/database.yml.sample config/database.yml cp config/customization.yml.sample config/customization.yml @@ -87,17 +87,19 @@ jobs: - name: Save coverage run: ./cc-test-reporter format-coverage --output coverage/codeclimate.${{ matrix.ruby }}.json - - uses: actions/upload-artifact@v4.3.1 + - uses: actions/upload-artifact@v3.1.3 with: name: coverage-${{ matrix.ruby }} path: coverage/codeclimate.${{ matrix.ruby }}.json upload_coverage: strategy: + fail-fast: false matrix: - os: [ ubuntu-20.04 ] + os: [ ubuntu-22.04 ] ruby: [ '3.2.2' ] runs-on: ubuntu-22.04 + env: CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} CC_TEST_REPORTER_URL: https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 @@ -111,7 +113,7 @@ jobs: - name: Give test coverage reporter executable permissions run: chmod +x cc-test-reporter - - uses: actions/download-artifact@v4.1.4 + - uses: actions/download-artifact@v3.0.2 with: name: coverage-${{ matrix.ruby }} path: coverage diff --git a/.gitignore b/.gitignore index 4d9ffc5f8..a8c858ef3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,6 @@ /public/assets .byebug_history -.rubocop-ruby.yml -.rubocop.yml # Test files /coverage/ @@ -55,5 +53,9 @@ yarn-debug.log* /public/assets/builds public/assets/builds/* app/assets/builds/* +.DS_Store +/app/assets/builds/* +!/app/assets/builds/.keep1 +!/app/assets/builds/.keep -.DS_Store \ No newline at end of file +Dockerfile.dev diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..2ff456cfc --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,6 @@ +AllCops: + Exclude: + - 'test/**/*' + - 'config/**/*' +Style/Documentation: + Enabled: false diff --git a/.ruby-version b/.ruby-version index 19ef286ee..be94e6f53 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1,2 +1 @@ 3.2.2 - diff --git a/Dockerfile b/Dockerfile index 1b56a62a1..282e11ae0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,78 +1,21 @@ -FROM internetee/ruby:3.2.2 +FROM internetee/ruby:3.2.2-refactor as base -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update > /dev/null && apt-get install -y --no-install-recommends > /dev/null \ - build-essential=* \ - imagemagick=* \ - curl \ - wget \ - gnupg2 \ - git \ - apt-utils \ - && apt-get dist-upgrade -yf\ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y locales -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen -RUN sed -i -e 's/# et_EE.UTF-8 UTF-8/et_EE.UTF-8 UTF-8/' /etc/locale.gen && \ - dpkg-reconfigure --frontend=noninteractive locales && \ - update-locale LANG=et_EE.UTF-8 - -ENV LANG et_EE.UTF-8 -RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc -s | apt-key add - -RUN sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list' -RUN apt-get update > /dev/null && apt-get install -y --no-install-recommends > /dev/null \ - postgresql-client-13=* \ - libpq-dev \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# add repository for Node.js in the LTS version -RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - +RUN npm install -g yarn@latest +COPY --link Gemfile Gemfile.lock ./ +RUN gem install bundler && bundle install --jobs 20 --retry 5 +COPY --link package.json yarn.lock ./ +RUN yarn install --frozen-lockfile -RUN apt-get install -y --no-install-recommends > /dev/null \ - nodejs=* \ - qt5-default=* \ - libqt5webkit5-dev=* \ - gstreamer1.0-plugins-base=* \ - libappindicator3-1=* \ - gstreamer1.0-tools=* \ - qtdeclarative5-dev=* \ - fonts-liberation=* \ - gstreamer1.0-x=* \ - libasound2=* \ - libnspr4=* \ - libnss3=* \ - libxss1=* \ - libxtst6=* \ - xdg-utils=* \ - qtdeclarative5-dev=* \ - fonts-liberation=* \ - gstreamer1.0-x=* \ - wkhtmltopdf \ - libxslt1-dev \ - libxml2-dev \ - python-dev \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +FROM base -RUN npm install -g yarn@latest +RUN useradd rails +RUN mkdir -p /home/rails && chown rails:rails /home/rails -RUN curl https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -o /chrome.deb -RUN dpkg -i /chrome.deb || apt-get update > /dev/null \ - && apt-get install -yf --no-install-recommends > /dev/null && apt-get clean \ - && rm -rf /var/lib/apt/lists/* -RUN dpkg -i /chrome.deb -RUN rm /chrome.deb -RUN sed -i 's/SECLEVEL=2/SECLEVEL=1/' /etc/ssl/openssl.cnf +USER rails:rails -RUN mkdir -p /opt/webapps/app/tmp/pids -WORKDIR /opt/webapps/app +RUN mkdir -p /opt/webapps/app/tmp/pids -COPY Rakefile Gemfile Gemfile.lock ./ -RUN gem install bundler && bundle install --jobs 20 --retry 5 -COPY package.json yarn.lock ./ -RUN yarn install --check-files +COPY --from=base --chown=rails:rails /usr/local/bundle /usr/local/bundle +COPY --from=base --chown=rails:rails /opt/webapps/app /opt/webapps/app EXPOSE 3000 diff --git a/Gemfile b/Gemfile index 0d60f6897..09b7466f1 100644 --- a/Gemfile +++ b/Gemfile @@ -8,37 +8,44 @@ gem 'amazing_print' gem 'bootsnap', '>= 1.1.0', require: false gem 'cancancan' gem 'chartkick' +gem 'cssbundling-rails' gem 'data_migrate' gem 'delayed_job', '~> 4.1.0' gem 'delayed_job_active_record' gem 'devise' gem 'directo', github: 'internetee/directo', branch: 'master' +gem 'faker' gem 'faraday' +gem 'freezolite', require: false +gem 'hotwire-rails', '~> 0.1.3' gem 'jbuilder', '~> 2.11' +gem 'jsbundling-rails' +gem 'jwt' gem 'lograge' gem 'mimemagic', '~> 0.4.3' gem 'money' +gem 'noticed', '~> 1.6' gem 'okcomputer', '~> 1.18.4' gem 'omniauth-rails_csrf_protection' gem 'omniauth-tara', github: 'internetee/omniauth-tara' +gem 'pagy', '~> 6.0' gem 'pdfkit' gem 'pg', '>= 0.18', '< 2.0' gem 'puma', '~> 6.4.2' +gem 'pg_search' +gem 'propshaft' gem 'rails', '>= 6.0.3.5' gem 'rails-i18n' gem 'recaptcha' +gem 'redis', '~> 5.0' gem 'ruby-openai' gem 'scenic' gem 'simpleidn' gem 'skylight' -gem 'sprockets', '~> 4.0' gem 'turbo-rails' -gem 'webpacker', '~> 6.0.0.rc.5' -gem 'webpush' gem 'valvat' - -# token -gem 'jwt' +gem 'view_component' +gem 'webpush' group :development, :test do gem 'bullet' @@ -52,37 +59,23 @@ group :development, :test do gem "rubocop-packaging" gem "rubocop-rspec" gem "rubocop-shopify" - gem "rubocop-thread_safety" + # gem "rubocop-thread_safety" end group :development do + gem 'htmlbeautifier' gem 'i18n-debug' + gem 'letter_opener', '~> 1.8' gem 'listen', '>= 3.0.5', '< 3.9' + gem 'ruby-lsp-rails' gem 'web-console', '>= 3.3.0' end group :test do gem 'capybara', '>= 2.15' - gem 'selenium-webdriver' - gem 'simplecov', '~> 0.10', '< 0.18', require: false + gem 'cuprite' + # gem 'selenium-webdriver' + gem 'simplecov', require: false gem 'spy' gem 'webmock' end - -# frontend -gem 'hotwire-rails', '~> 0.1.3' -# Use Redis for Action Cable - -# gem 'redis', '~> 5.0' -gem 'redis', '~> 5.0' - -# For search -gem 'pg_search' - -# For pagination -gem 'pagy', '~> 6.0' - -# For mock username -gem 'faker' - -gem 'noticed', '~> 1.6' diff --git a/Gemfile.lock b/Gemfile.lock index 0eb92a4dd..8122eef9f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,6 +122,11 @@ GEM crack (0.4.5) rexml crass (1.0.6) + cssbundling-rails (1.2.0) + railties (>= 6.0.0) + cuprite (0.14.3) + capybara (~> 3.0) + ferrum (~> 0.13.0) data_migrate (9.1.0) activerecord (>= 6.0) railties (>= 6.0) @@ -141,22 +146,30 @@ GEM domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) erubi (1.12.0) + event_stream_parser (1.0.0) faker (3.2.0) i18n (>= 1.8.11, < 2) - faraday (2.7.9) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (3.0.2) + faraday-net_http (3.1.0) + net-http + ferrum (0.13) + addressable (~> 2.5) + concurrent-ruby (~> 1.1) + webrick (~> 1.7) + websocket-driver (>= 0.6, < 0.8) ffi (1.15.5) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake - globalid (1.2.1) - activesupport (>= 6.1) + freezolite (0.3.0) + require-hooks (~> 0.1) + globalid (1.1.0) + activesupport (>= 5.0) hashdiff (1.0.1) hashie (5.0.0) hkdf (0.3.0) @@ -164,6 +177,7 @@ GEM rails (>= 6.0.0) stimulus-rails turbo-rails + htmlbeautifier (1.4.2) http (5.1.1) addressable (~> 2.8) http-cookie (~> 1.0) @@ -180,7 +194,9 @@ GEM jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) - json (2.6.3) + jsbundling-rails (1.1.2) + railties (>= 6.0.0) + json (2.7.2) json-jwt (1.16.6) activesupport (>= 4.2) aes_key_wrap @@ -190,6 +206,10 @@ GEM faraday-follow_redirects jwt (2.7.1) language_server-protocol (3.17.0.3) + launchy (2.5.2) + addressable (~> 2.8) + letter_opener (1.8.1) + launchy (>= 2.2, < 3) listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -220,7 +240,9 @@ GEM money (6.16.0) i18n (>= 0.6.4, <= 2) msgpack (1.7.1) - multipart-post (2.3.0) + multipart-post (2.4.1) + net-http (0.4.1) + uri net-imap (0.4.10) date net-protocol @@ -259,8 +281,8 @@ GEM webfinger (~> 1.2) orm_adapter (0.5.0) pagy (6.0.4) - parallel (1.23.0) - parser (3.2.2.3) + parallel (1.25.1) + parser (3.3.3.0) ast (~> 2.4.1) racc pdfkit (0.8.7.3) @@ -268,7 +290,12 @@ GEM pg_search (2.3.6) activerecord (>= 5.2) activesupport (>= 5.2) - prism (0.19.0) + prettier_print (1.2.1) + propshaft (0.7.0) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack + railties (>= 7.0.0) pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) @@ -285,8 +312,6 @@ GEM rack (>= 2.1.0) rack-protection (3.0.6) rack - rack-proxy (0.7.6) - rack rack-test (2.1.0) rack (>= 1.3) rails (7.0.8.1) @@ -331,74 +356,63 @@ GEM redis-client (>= 0.9.0) redis-client (0.15.0) connection_pool - regexp_parser (2.8.1) + regexp_parser (2.9.1) request_store (1.5.1) rack (>= 1.4) + require-hooks (0.1.0) responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.5) - rubocop (1.54.1) + rexml (3.2.6) + rubocop (1.64.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) - parser (>= 3.2.1.0) - rubocop-capybara (2.19.0) - rubocop (~> 1.41) - rubocop-factory_bot (2.24.0) - rubocop (~> 1.33) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) rubocop-packaging (0.5.2) rubocop (>= 1.33, < 2.0) - rubocop-performance (1.18.0) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - rubocop-rspec (2.25.0) - rubocop (~> 1.40) - rubocop-capybara (~> 2.17) - rubocop-factory_bot (~> 2.22) - rubocop-shopify (2.14.0) + rubocop-performance (1.21.0) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rspec (3.0.1) + rubocop (~> 1.61) + rubocop-shopify (2.15.1) rubocop (~> 1.51) - rubocop-thread_safety (0.5.1) - rubocop (>= 0.90.0) - ruby-lsp (0.13.2) + ruby-lsp (0.7.6) language_server-protocol (~> 3.17.0) - prism (>= 0.19.0, < 0.20) - sorbet-runtime (>= 0.5.5685) - ruby-openai (4.2.0) + sorbet-runtime + syntax_tree (>= 6.1.1, < 7) + ruby-lsp-rails (0.2.2) + rails (>= 6.0) + ruby-lsp (>= 0.6.2, < 0.8.0) + sorbet-runtime (>= 0.5.9897) + ruby-openai (7.0.1) + event_stream_parser (>= 0.3.0, < 2.0.0) faraday (>= 1) faraday-multipart (>= 1) ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - selenium-webdriver (4.10.0) - rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2, < 3.0) - websocket (~> 1.0) - semantic_range (3.0.0) - simplecov (0.17.1) + simplecov (0.22.0) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) simpleidn (0.2.1) unf (~> 0.1.4) skylight (5.3.4) activesupport (>= 5.2.0) sorbet-runtime (0.5.11164) - sprockets (4.2.0) - concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) spy (1.0.5) stimulus-rails (1.2.1) railties (>= 6.0.0) @@ -406,8 +420,10 @@ GEM activesupport (>= 3) attr_required (>= 0.0.5) httpclient (>= 2.4) - thor (1.3.1) - timeout (0.4.1) + syntax_tree (6.2.0) + prettier_print (>= 1.2.0) + thor (1.2.2) + timeout (0.4.0) turbo-rails (1.4.0) actionpack (>= 6.0.0) activejob (>= 6.0.0) @@ -417,8 +433,9 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.8.2) - unicode-display_width (2.4.2) + unicode-display_width (2.5.0) uniform_notifier (1.16.0) + uri (0.13.0) validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) @@ -427,6 +444,10 @@ GEM public_suffix valvat (1.4.0) rexml (>= 3.2, < 4.0) + view_component (3.4.0) + activesupport (>= 5.2.0, < 8.0) + concurrent-ruby (~> 1.0) + method_source (~> 1.0) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.0) @@ -441,16 +462,11 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) 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) webpush (1.1.0) hkdf (~> 0.2) jwt (~> 2.0) - websocket (1.2.9) - websocket-driver (0.7.6) + webrick (1.8.1) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) @@ -470,6 +486,8 @@ DEPENDENCIES cancancan capybara (>= 2.15) chartkick + cssbundling-rails + cuprite data_migrate delayed_job (~> 4.1.0) delayed_job_active_record @@ -477,10 +495,14 @@ DEPENDENCIES directo! faker faraday + freezolite hotwire-rails (~> 0.1.3) + htmlbeautifier i18n-debug jbuilder (~> 2.11) + jsbundling-rails jwt + letter_opener (~> 1.8) listen (>= 3.0.5, < 3.9) lograge mimemagic (~> 0.4.3) @@ -493,6 +515,7 @@ DEPENDENCIES pdfkit pg (>= 0.18, < 2.0) pg_search + propshaft pry puma (~> 6.4.2) rails (>= 6.0.3.5) @@ -504,21 +527,19 @@ DEPENDENCIES rubocop-performance rubocop-rspec rubocop-shopify - rubocop-thread_safety ruby-lsp + ruby-lsp-rails ruby-openai scenic - selenium-webdriver - simplecov (~> 0.10, < 0.18) + simplecov simpleidn skylight - sprockets (~> 4.0) spy turbo-rails valvat + view_component web-console (>= 3.3.0) webmock - webpacker (~> 6.0.0.rc.5) webpush BUNDLED WITH diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 000000000..a06051cfc --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,3 @@ +web: unset PORT && env RUBY_DEBUG_OPEN=true bin/rails s -b '0.0.0.0' +js: yarn build --watch +css: yarn build:css --watch diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/builds/google_analytics.js b/app/assets/builds/google_analytics.js deleted file mode 100644 index 6c3a94ea5..000000000 --- a/app/assets/builds/google_analytics.js +++ /dev/null @@ -1,23 +0,0 @@ -(() => { - // app/javascript/google_analytics.js - document.addEventListener("turbo:load", (event) => { - const googleId = document.getElementById("google-tracking-id").getAttribute("data-value"); - if (googleId) { - let gtag2 = function() { - dataLayer.push(arguments); - }; - var gtag = gtag2; - window.dataLayer = window.dataLayer || []; - ; - gtag2("js", new Date()); - gtag2("config", googleId); - gtag2("config", googleId, { - "page_location": event.detail.url, - "cookie_prefix": "AuctionTest", - "cookie_domain": "auction.ee" - }); - } - ; - }); -})(); -//# sourceMappingURL=assets/google_analytics.js.map diff --git a/app/assets/builds/google_analytics.js.map b/app/assets/builds/google_analytics.js.map deleted file mode 100644 index f5cee2d81..000000000 --- a/app/assets/builds/google_analytics.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../javascript/google_analytics.js"], - "sourcesContent": ["document.addEventListener('turbo:load', (event) => {\n const googleId = document\n .getElementById('google-tracking-id')\n .getAttribute('data-value');\n\n if (googleId) {\n window.dataLayer = window.dataLayer || [];\n function gtag() {\n dataLayer.push(arguments);\n };\n gtag('js', new Date());\n gtag('config', googleId);\n\n gtag('config', googleId, {\n 'page_location': event.detail.url,\n 'cookie_prefix': 'AuctionTest',\n 'cookie_domain': 'auction.ee',\n });\n };\n});"], - "mappings": ";;AAAA,WAAS,iBAAiB,cAAc,CAAC,UAAU;AAC/C,UAAM,WAAW,SACZ,eAAe,oBAAoB,EACnC,aAAa,YAAY;AAE9B,QAAI,UAAU;AAEV,UAASA,QAAT,WAAgB;AACZ,kBAAU,KAAK,SAAS;AAAA,MAC5B;AAFS,iBAAAA;AADT,aAAO,YAAY,OAAO,aAAa,CAAC;AAGvC;AACD,MAAAA,MAAK,MAAM,IAAI,KAAK,CAAC;AACrB,MAAAA,MAAK,UAAU,QAAQ;AAEvB,MAAAA,MAAK,UAAU,UAAU;AAAA,QACrB,iBAAiB,MAAM,OAAO;AAAA,QAC9B,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,MACrB,CAAC;AAAA,IACL;AAAC;AAAA,EACL,CAAC;", - "names": ["gtag"] -} diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js deleted file mode 100644 index 0967ef424..000000000 --- a/app/assets/config/manifest.js +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/app/assets/fonts/raleway-v28-latin-500.eot b/app/assets/fonts/raleway-v28-latin-500.eot new file mode 100644 index 000000000..fa94eb332 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-500.eot differ diff --git a/app/assets/fonts/raleway-v28-latin-500.svg b/app/assets/fonts/raleway-v28-latin-500.svg new file mode 100644 index 000000000..f6219ceb5 --- /dev/null +++ b/app/assets/fonts/raleway-v28-latin-500.svg @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/fonts/raleway-v28-latin-500.ttf b/app/assets/fonts/raleway-v28-latin-500.ttf new file mode 100644 index 000000000..e12feff15 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-500.ttf differ diff --git a/app/assets/fonts/raleway-v28-latin-500.woff b/app/assets/fonts/raleway-v28-latin-500.woff new file mode 100644 index 000000000..cdcfb9cf5 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-500.woff differ diff --git a/app/assets/fonts/raleway-v28-latin-500.woff2 b/app/assets/fonts/raleway-v28-latin-500.woff2 new file mode 100644 index 000000000..b9b8273e2 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-500.woff2 differ diff --git a/app/assets/fonts/raleway-v28-latin-800.eot b/app/assets/fonts/raleway-v28-latin-800.eot new file mode 100644 index 000000000..35a3f0834 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-800.eot differ diff --git a/app/assets/fonts/raleway-v28-latin-800.svg b/app/assets/fonts/raleway-v28-latin-800.svg new file mode 100644 index 000000000..17c10bbe4 --- /dev/null +++ b/app/assets/fonts/raleway-v28-latin-800.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/fonts/raleway-v28-latin-800.ttf b/app/assets/fonts/raleway-v28-latin-800.ttf new file mode 100644 index 000000000..78962f5a7 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-800.ttf differ diff --git a/app/assets/fonts/raleway-v28-latin-800.woff b/app/assets/fonts/raleway-v28-latin-800.woff new file mode 100644 index 000000000..f0776311c Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-800.woff differ diff --git a/app/assets/fonts/raleway-v28-latin-800.woff2 b/app/assets/fonts/raleway-v28-latin-800.woff2 new file mode 100644 index 000000000..4a43b6bed Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-800.woff2 differ diff --git a/app/assets/fonts/raleway-v28-latin-900.eot b/app/assets/fonts/raleway-v28-latin-900.eot new file mode 100644 index 000000000..fb497711b Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-900.eot differ diff --git a/app/assets/fonts/raleway-v28-latin-900.svg b/app/assets/fonts/raleway-v28-latin-900.svg new file mode 100644 index 000000000..f6af7484d --- /dev/null +++ b/app/assets/fonts/raleway-v28-latin-900.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/fonts/raleway-v28-latin-900.ttf b/app/assets/fonts/raleway-v28-latin-900.ttf new file mode 100644 index 000000000..a8171cb32 Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-900.ttf differ diff --git a/app/assets/fonts/raleway-v28-latin-900.woff b/app/assets/fonts/raleway-v28-latin-900.woff new file mode 100644 index 000000000..c6e351a3e Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-900.woff differ diff --git a/app/assets/fonts/raleway-v28-latin-900.woff2 b/app/assets/fonts/raleway-v28-latin-900.woff2 new file mode 100644 index 000000000..ee84172ad Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-900.woff2 differ diff --git a/app/assets/fonts/raleway-v28-latin-regular.eot b/app/assets/fonts/raleway-v28-latin-regular.eot new file mode 100644 index 000000000..72121eafd Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-regular.eot differ diff --git a/app/assets/fonts/raleway-v28-latin-regular.svg b/app/assets/fonts/raleway-v28-latin-regular.svg new file mode 100644 index 000000000..eca08e1d3 --- /dev/null +++ b/app/assets/fonts/raleway-v28-latin-regular.svg @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/fonts/raleway-v28-latin-regular.ttf b/app/assets/fonts/raleway-v28-latin-regular.ttf new file mode 100644 index 000000000..0ceb1cc1e Binary files /dev/null and b/app/assets/fonts/raleway-v28-latin-regular.ttf differ diff --git a/app/assets/images/arrow-dwn.svg b/app/assets/images/arrow-dwn.svg new file mode 100644 index 000000000..8fbd849a3 --- /dev/null +++ b/app/assets/images/arrow-dwn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/arrow-select.svg b/app/assets/images/arrow-select.svg new file mode 100644 index 000000000..a2ce79556 --- /dev/null +++ b/app/assets/images/arrow-select.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/card.svg b/app/assets/images/card.svg new file mode 100644 index 000000000..30fac8336 --- /dev/null +++ b/app/assets/images/card.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/close.svg b/app/assets/images/close.svg new file mode 100644 index 000000000..91846f5fc --- /dev/null +++ b/app/assets/images/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/copy.svg b/app/assets/images/copy.svg new file mode 100644 index 000000000..7f5873333 --- /dev/null +++ b/app/assets/images/copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/delete.svg b/app/assets/images/delete.svg new file mode 100644 index 000000000..904173ab0 --- /dev/null +++ b/app/assets/images/delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/edit.svg b/app/assets/images/edit.svg new file mode 100644 index 000000000..ae6bf2708 --- /dev/null +++ b/app/assets/images/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/gift.svg b/app/assets/images/gift.svg new file mode 100644 index 000000000..97be86056 --- /dev/null +++ b/app/assets/images/gift.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/hammer.svg b/app/assets/images/hammer.svg new file mode 100644 index 000000000..6c0e2ec07 --- /dev/null +++ b/app/assets/images/hammer.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/hero.png b/app/assets/images/hero.png new file mode 100644 index 000000000..5aa88c554 Binary files /dev/null and b/app/assets/images/hero.png differ diff --git a/app/assets/images/info.svg b/app/assets/images/info.svg new file mode 100644 index 000000000..cfceb6d8b --- /dev/null +++ b/app/assets/images/info.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/assets/images/io-icon.svg b/app/assets/images/io-icon.svg new file mode 100644 index 000000000..8fb364488 --- /dev/null +++ b/app/assets/images/io-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/notification.svg b/app/assets/images/notification.svg new file mode 100644 index 000000000..f6d129328 --- /dev/null +++ b/app/assets/images/notification.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/po-icon.svg b/app/assets/images/po-icon.svg new file mode 100644 index 000000000..f4d34b24f --- /dev/null +++ b/app/assets/images/po-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/search.svg b/app/assets/images/search.svg new file mode 100644 index 000000000..185115d9c --- /dev/null +++ b/app/assets/images/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/timer.svg b/app/assets/images/timer.svg new file mode 100644 index 000000000..291a71d6b --- /dev/null +++ b/app/assets/images/timer.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/assets/images/trophy.svg b/app/assets/images/trophy.svg new file mode 100644 index 000000000..83a62aaa1 --- /dev/null +++ b/app/assets/images/trophy.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/assets/images/user.svg b/app/assets/images/user.svg new file mode 100644 index 000000000..ed541ad24 --- /dev/null +++ b/app/assets/images/user.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index 899b84fc3..000000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,4 +0,0 @@ -/* - *= require_self - *= require_tree . - */ \ No newline at end of file diff --git a/app/assets/stylesheets/application.sass.scss b/app/assets/stylesheets/application.sass.scss new file mode 100644 index 000000000..0271bd6b7 --- /dev/null +++ b/app/assets/stylesheets/application.sass.scss @@ -0,0 +1,16 @@ +// Entry point for your Sass build +@use "toastify-js/src/toastify.css"; +@import "datatables/datatables"; +@import url(styles.scss); + +@import "generics/normalize"; +@import "settings/settings"; +@import "mixins/mixins"; + +@import "elements/elements"; +@import "tools/tools"; +@import "objects/objects"; +@import "elements/elements"; +@import "components/components"; +@import "utilities/utilities"; +@import "vanilla-cookieconsent/dist/cookieconsent"; diff --git a/app/assets/stylesheets/autobider.css b/app/assets/stylesheets/autobider.css deleted file mode 100644 index afad32db0..000000000 --- a/app/assets/stylesheets/autobider.css +++ /dev/null @@ -1,4 +0,0 @@ -/* - Place all the styles related to the matching controller here. - They will automatically be included in application.css. -*/ diff --git a/app/assets/stylesheets/components/_accordion.scss b/app/assets/stylesheets/components/_accordion.scss new file mode 100644 index 000000000..9c63a1cb8 --- /dev/null +++ b/app/assets/stylesheets/components/_accordion.scss @@ -0,0 +1,31 @@ +.c-accordion { + .c-accordion__item { + padding: rem(34px 0); + border-top: 1px solid $grey-200-color; + &:last-child{ + border-bottom: 1px solid $grey-200-color; + } + &.active{ + .o-arrowdown-icon{ + transform: rotate(180deg); + transition: transform .3s; + } + } + } + .c-accordion__question { + color: $black-color; + font-size: rem(18px); + font-weight: 800; + line-height: rem(28px); + margin: 0; + cursor: pointer; + } + .c-accordion__answer { + color: $black-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + margin: rem(8px 0 0 0); + } +} + diff --git a/app/assets/stylesheets/components/_account.scss b/app/assets/stylesheets/components/_account.scss new file mode 100644 index 000000000..09b15cc3b --- /dev/null +++ b/app/assets/stylesheets/components/_account.scss @@ -0,0 +1,297 @@ +.c-account__title-wrapper { + flex-direction: column-reverse; + + @include mq(tablet) { + flex-direction: row; + } +} + +.c-account__title { + margin-top: 0; + margin-bottom: rem(24px); + + @include mq(tablet) { + margin-bottom: rem(32px); + } +} + +.c-account--mainpage { + .c-account_editicons { + &>svg:first-child { + display: none; + } + } +} + +.c-account_editicons { + gap: 8px; + height: rem(32px); + margin-bottom: rem(24px); + + &>svg { + cursor: pointer; + opacity: 1; + transition: opacity .3s; + + &:nth-last-child(-n + 2) { + display: none; + } + + &:hover { + opacity: .7; + } + } + + &.is-editable { + >svg { + &:nth-last-child(-n + 2) { + display: inline-flex; + } + + &:nth-child(-n + 2) { + display: none; + } + } + } +} + +.c-account__input-wrapper { + flex-wrap: wrap; + gap: 0; + max-width: 450px; + justify-content: space-between; + + @include mq(tablet) { + column-gap: 15px; + justify-content: flex-start; + } + + .c-account__input { + width: 100%; + flex-shrink: 0; + + @include mq(mobile) { + width: 50%; + } + + @include mq(tablet) { + width: rem(131px); + } + + &:last-child { + @include mq(tablet) { + width: rem(165px); + } + } + } + + span { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + padding: rem(12px 16px); + + &:last-child { + padding-right: 0; + } + } + + .c-account__input--readonly { + label { + margin-bottom: rem(20px); + } + + input { + border: 0; + padding: 0; + } + } +} + +.c-account__input--editable { + + input, + select { + border-color: transparent; + transition: border .5s; + } + + &.is-editable { + + input, + select { + border: 1px solid #e2e2e2; + } + } +} + +.c-account__checkbox { + gap: 16px; + + label { + margin: 0; + } +} + +.c-account .c-account__checkbox label { + margin: 0; + margin-top: rem(5px); +} + +.c-account__chekboxtitle { + color: $black-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); +} + +.c-acount__link { + color: $primary-blue-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + text-decoration-line: underline; + margin-bottom: rem(30px); + display: block; +} + +.c-acount__buttons { + gap: 5px; + + @include mq(mobile) { + gap: 10px; + } + + @include mq(tablet) { + gap: 24px; + } +} + +.c-acount__button { + font-size: rem(11px); + + @include mq(mobile) { + font-size: rem(14px); + } +} + +.c-acount__button--icon { + display: inline-flex; + align-items: center; + gap: 10px; +} + +.c-account__label-explain { + display: flex; + justify-content: space-between; + + span { + font-weight: 500; + + &:first-child { + display: block; + + @include mq(tablet) { + display: inline-block; + } + } + } + + &>span { + color: $grey-400-color; + margin-right: rem(16px); + display: none; + + @include mq(tablet) { + display: block; + } + + @include mq(laptop) { + display: none; + } + + @include mq(menu) { + display: block; + } + } +} + +.c-account__input-explain { + color: $grey-400-color; + text-align: right; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + display: block; + + @include mq(tablet) { + display: none; + } + + @include mq(laptop) { + display: block; + } + + @include mq(menu) { + display: none; + } +} + +.c-account__title--password { + margin-bottom: rem(24px); + + @include mq(tablet) { + margin-bottom: rem(32px); + } +} + +.o-checkbox-text { + margin-left: rem(8px); + + @include mq(tablet) { + margin-left: rem(16px); + } + + white-space: normal; + + &:first-child { + margin-bottom: rem(24px); + + @include mq(tablet) { + margin-bottom: rem(32px); + } + } + + a { + color: $primary-blue-color; + text-decoration: underline; + } +} + + +.c-account__title--confirm { + margin-bottom: rem(8px); +} + +.c-account__subtitle { + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + margin: rem(0 0 16px 0); + + @include mq(tablet) { + margin: rem(0 0 24px 0); + } + + @include mq(laptop) { + margin: rem(0 0 32px 0); + } +} + +.c-account__btns { + justify-content: center; + + @include mq(laptop) { + justify-content: flex-start; + } +} diff --git a/app/assets/stylesheets/components/_address.scss b/app/assets/stylesheets/components/_address.scss new file mode 100644 index 000000000..6bb3ff4e8 --- /dev/null +++ b/app/assets/stylesheets/components/_address.scss @@ -0,0 +1,108 @@ +.c-account--adresses { + .c-account_editicons { + width: 100%; + + @include mq(laptop) { + width: unset; + } + } + + input { + max-width: 550px; + } + + .c-account__input-title { + input { + border-radius: 0; + } + + &.is-editable { + input { + border-color: transparent; + border-bottom-color: $grey-200-color; + } + } + } + + .c-account__input-wrapper { + max-width: 100%; + flex-wrap: wrap; + column-gap: 8px; + + @include mq(tablet) { + column-gap: 16px; + } + + .c-account__input { + input { + width: 100%; + } + + select { + cursor: pointer; + min-width: 62px; + width: fit-content; + background-position: right 0.6rem center; + } + + &:nth-child(1) { + width: 100%; + } + + &:nth-child(2) { + width: fit-content; + } + + &:nth-child(3) { + width: fit-content; + } + + &:nth-child(4) { + width: fit-content; + } + + @include mq(tablet) { + &:nth-child(1) { + width: 200px; + } + + &:nth-child(2) { + width: 130px; + } + + &:nth-child(3) { + width: 108px; + } + } + + @include mq(desktop) { + &:nth-child(1) { + width: 100%; + } + + &:nth-child(2) { + width: fit-content; + } + + &:nth-child(3) { + width: fit-content; + } + } + + @include mq(menu) { + &:nth-child(1) { + width: 200px; + } + + &:nth-child(2) { + width: 130px; + } + + &:nth-child(3) { + width: 108px; + } + } + } + + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_auctiondesc.scss b/app/assets/stylesheets/components/_auctiondesc.scss new file mode 100644 index 000000000..f06f095bc --- /dev/null +++ b/app/assets/stylesheets/components/_auctiondesc.scss @@ -0,0 +1,18 @@ +.c-auctiondesc__wrapper{ + width: 732px; + max-width: 100%; + p{ + color: $black-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + margin: rem(0 0 16px 0); + strong{ + font-weight: 800; + } + a{ + color: $primary-blue-color; + text-decoration: underline; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_auctions.scss b/app/assets/stylesheets/components/_auctions.scss new file mode 100644 index 000000000..5989ae69e --- /dev/null +++ b/app/assets/stylesheets/components/_auctions.scss @@ -0,0 +1,138 @@ +.c-auctions { + +} + +.c-auctions__title { + font-weight: 900; + font-size: rem(22px); + line-height: rem(22px); + margin: 0 0 rem(20px); + + @include mq(tablet) { + font-size: rem(30px); + line-height: rem(30px); + } +} +.c-auctions__desc { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + margin: 0 0 rem(32px); +} + +.c-auctions__list { + list-style: none; + border-top: 1px solid $grey-200-color; + display: flex; + flex-wrap: wrap; + position: relative; + padding: 0; + margin: 0; + + &::after { + content: ''; + width: 100%; + height: 1px; + background-color: $grey-200-color; + position: absolute; + bottom: 0; + display: block; + left: 0; + } + + li { + flex: 100%; + max-width: 100%; + border-bottom: 1px solid $grey-200-color; + + @media (min-width: 567px) { + border-right: 1px solid $grey-200-color; + flex: 50%; + max-width: 50%; + + &:nth-child(2n) { + border-right: 0; + } + } + + @include mq(laptop) { + border-right: 1px solid $grey-200-color; + flex: 33.33%; + max-width: 33.33%; + + &:nth-child(2n) { + border-right: 1px solid $grey-200-color; + } + + &:nth-child(3n) { + border-right: 0; + } + } + + span { + margin-right: rem(8px); + } + } + + strong { + font-weight: 800; + font-size: rem(14px); + line-height: rem(20px); + } + + li { + padding: rem(13px 17px); + } +} + +.c-auctions__footer { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + margin-top: rem(32px); + + @include mq(tablet) { + flex-wrap: nowrap; + } +} + +.c-auctions__legend { + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + display: flex; + gap: rem(16px); + align-items: center; +} + +.c-auctions__info { + gap: rem(16px); + display: flex; + align-items: center; +} + +.c-auctions__all-count { + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); +} + +.c-auctions-wrap { + position: relative; + padding-bottom: rem(48px); + + @include mq(tablet) { + padding-bottom: rem(64px); + } + + &::after { + content: ''; + width: 100%; + height: rem(134px); + position: absolute; + left: 0; + bottom: 0; + background: linear-gradient(0deg, #EEEEF0 0%, #FFFFFF 100%); + z-index: -1; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_badge.scss b/app/assets/stylesheets/components/_badge.scss new file mode 100644 index 000000000..e3d2b1eee --- /dev/null +++ b/app/assets/stylesheets/components/_badge.scss @@ -0,0 +1,77 @@ +.c-badge { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + padding: 0 rem(16px); + display: inline-flex; + gap: rem(8px); + border-radius: rem(12px); + align-items: center; + cursor: default; + + &::before { + content: ''; + width: rem(8px); + height: rem(8px); + display: block; + border: 2px solid transparent; + border-radius: rem(4px); + } +} + +.c-badge--green { + background-color: $green-light; + color: $primary-green-color; + + &::before { + background-color: $primary-green-color; + border-color: $primary-green-color; + } +} + +.c-badge--yellow { + background-color: $yellow-light; + color: $primary-yellow-color; + + &::before { + background-color: $primary-yellow-color; + border-color: $primary-yellow-color; + } +} + +.c-badge--red { + background-color: #f8d8db; + color: #ff2040; + + &::before { + background-color: #ff2040; + border-color: #ff2040; + } +} + +.c-badge--blue { + background-color: #dfeff9; + color: $primary-blue-color; + + &::before { + background-color: $primary-blue-color; + border-color: $primary-blue-color; + } +} + + +.c-badge--gray { + background-color: $grey-100-color; + color: $grey-400-color; + + &::before { + background-color: $grey-400-color; + border-color: $grey-400-color; + } +} + +.c-badge--circle { + &::before { + background-color: transparent; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_block.scss b/app/assets/stylesheets/components/_block.scss new file mode 100644 index 000000000..7698eead2 --- /dev/null +++ b/app/assets/stylesheets/components/_block.scss @@ -0,0 +1,7 @@ +.c-block { + margin-bottom: rem(48px); +} + +.c-block__title { + margin: 0 0 rem(32px); +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_btn.scss b/app/assets/stylesheets/components/_btn.scss new file mode 100644 index 000000000..4534dbcbb --- /dev/null +++ b/app/assets/stylesheets/components/_btn.scss @@ -0,0 +1,92 @@ +.c-btn--orange { + @include btn-orange; +} +.c-btn--orange-light { + @include btn-orange-light; +} +.c-btn--blue { + @include btn-blue; +} + +.c-btn--black { + @include btn-black; +} + +.c-btn--green { + @include btn-green; +} + +.c-btn--green-light { + @include btn-green-light; +} +.c-btn--red { + @include btn-red; +} + +.c-btn--blue-secondary { + @include btn-blue-secondary; +} + +.c-btn--ghost { + @include btn-black; + color: $black-color; + background-color: transparent; + border: 1px solid $grey-400-color; + + &:hover { + border-color: $black-color; + background-color: transparent; + } +} + +.c-btn--icon { + @include btn-black; + color: $black-color; + background-color: transparent; + border: 1px solid $grey-400-color; +} + +table { + .c-btn { + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + padding: rem(6px 16px); + } + + .c-btn--icon { + padding: 0 !important; + } +} + +.c-btn--icon, +.c-btn--icon-size { + width: rem(32px); + height: rem(32px); + padding: 0 !important; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.c-btn-group { + display: flex; + justify-content: flex-start; + gap: rem(16px); + + @include mq(laptop) { + justify-content: center; + } +} + +.c-account .c-btn { + font-size: rem(14px); +} +.c-account--changepass { + .c-btn { + width: 100%; + padding: rem(7px 1rem); + font-size: rem(15px); + margin: 2.5rem 0 1.5rem 0; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_components.scss b/app/assets/stylesheets/components/_components.scss new file mode 100644 index 000000000..44fe35ee1 --- /dev/null +++ b/app/assets/stylesheets/components/_components.scss @@ -0,0 +1,32 @@ +@import "toolbar"; +@import "header"; +@import "socials"; +@import "dropdown"; +@import "search"; +@import "hero"; +@import "domain-search"; +@import "cta"; +@import "btn"; +@import "auctions"; +@import "news"; +@import "footer"; +@import "block"; +@import "tabs"; +@import "badge"; +@import "notifications"; +@import "table"; +@import "modal"; +@import "notices"; +@import "login"; +@import "account"; +@import "create-account"; +@import "profile"; +@import "table-whishlist"; +@import "wishlist"; +@import "address"; +@import "fp"; +@import "imagebox"; +@import "auctiondesc"; +@import "accordion"; +@import "pagy"; +@import "notice"; \ No newline at end of file diff --git a/app/assets/stylesheets/components/_create-account.scss b/app/assets/stylesheets/components/_create-account.scss new file mode 100644 index 000000000..e18b325e8 --- /dev/null +++ b/app/assets/stylesheets/components/_create-account.scss @@ -0,0 +1,17 @@ +.c-account--createnew { + .o-checkbox-container--green { + .o-checkbox-text{ + margin-bottom: 0; + } + @include mq(laptop){ + padding-bottom: rem(40px); + } + } + .c-account__btn{ + margin: 0 auto; + display: block; + @include mq(laptop){ + margin: 0; + } + } +} diff --git a/app/assets/stylesheets/components/_cta.scss b/app/assets/stylesheets/components/_cta.scss new file mode 100644 index 000000000..922e2be45 --- /dev/null +++ b/app/assets/stylesheets/components/_cta.scss @@ -0,0 +1,290 @@ +.c-cta { + display: flex; + flex-direction: column; + justify-content: center; + padding: rem(20px); + position: relative; + border-radius: rem(8px); + + &:before { + content: ' '; + display: block; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0.25; + background: url("spider-bg.svg") right center/contain no-repeat; + z-index: 0; + } +} + +.c-cta--primary { + background-color: $secondary-blue-color; + min-height: rem(290px); + + @include mq(desktop) { + min-height: rem(390px); + } + + .c-cta__btn { + a { + @include btn-orange; + } + } +} + +.c-cta--secondary { + background-color: $primary-blue-color; + min-height: rem(275px); + + &::before { + filter: brightness(0.25); + background-size: 45%; + } + + .c-cta__btn { + a { + @include btn-black; + min-width: rem(153px); + } + } +} + +.c-cta__title { + font-weight: 900; + font-size: rem(22px); + line-height: rem(22px); + margin: 0; + color: $white-color; + position: relative; + z-index: 1; + + @include mq(tablet) { + font-size: rem(30px); + line-height: rem(30px); + } +} + +.c-cta__desc { + font-weight: 800; + font-size: rem(14px); + line-height: rem(18px); + margin: rem(15px 0); + color: $white-color; + position: relative; + z-index: 1; + + @include mq(tablet) { + font-size: rem(16px); + line-height: rem(20px); + } +} + +.c-cta__btn { + position: relative; + z-index: 1; +} + +.c-cta--two-col { + padding: rem(48px 0 0); + + @include mq(tablet) { + padding: rem(64px 0 0); + } +} + +.c-cta--horizontal { + display: flex; + flex-direction: column; + background: center center/cover no-repeat; + justify-content: space-between; + align-items: center; + padding: rem(48px 42px); + border-radius: rem(8px); + overflow: hidden; + + &::before { + content: ' '; + display: block; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0.5; + background: $black-color; + z-index: 0; + } + + @include mq(tablet) { + padding: rem(62px 56px); + flex-direction: row; + } + + .c-cta__title { + margin: 0 0 rem(20px); + } + + .c-cta__desc { + margin: 0; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + } + + .c-cta__btn { + a { + @include btn-blue; + } + } +} + +.c-cta--three-col { + padding: rem(48px 0); + + @include mq(tablet) { + padding: rem(64px 0); + } +} +.o-grid--auction-cards > div{ + height: 100%; +} +.c-cta--card { + padding: rem(24px 16px 30px); + color: $black-color; + @include box-shadow-light; + border-radius: rem(4px 4px 8px 8px); + position: relative; + height: 100%; + + &::before { + content: ''; + width: 100%; + height: rem(4px); + border-radius: rem(2px); + position: absolute; + top: 0; + left: 0; + } + + @include mq(laptop) { + padding: rem(32px 18px 32px); + } + + @include mq(desktop) { + padding: rem(34px 20px 30px); + } + + + @include mq(ultra) { + padding: rem(37px 24px 44px); + } + + .c-cta__title { + color: $black-color; + margin: 0 0 rem(20px); + } + + .c-cta__desc { + color: $black-color; + margin: 0; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + } + + .c-cta__grid { + display: flex; + gap: rem(16px); + justify-content: space-between; + } +} + +.c-cta--orange { + + &::before { + background-color: $primary-orange-color; + } + + .c-cta__btn { + a { + @include btn-orange; + } + } +} + +.c-cta--sec-blue { + + &::before { + background-color: $secondary-blue-color; + } + + .c-cta__btn { + a { + @include btn-blue-secondary; + } + } +} + +.c-cta--black { + + &::before { + background-color: $black-color; + } + + .c-cta__btn { + a { + @include btn-black; + } + } +} + +.c-cta--green { + &::before { + background-color: $primary-green-color; + } + + .c-cta__btn { + a { + @include btn-green; + } + } +} + +.c-cta--card-icon { + .c-cta__title { + display: flex; + gap: rem(12px); + + svg { + min-width: rem(32px); + } + } + @include mq(wide){ + align-items: center; + } +} + +.c-cta--inline { + padding: 0; +} +.c-cta--wishlist{ + background: #fff; + margin-bottom: rem(24px); + padding-bottom: 60px; + box-shadow: 0 0 48px rgba(0,0,0,.12); + @include mq(tablet){ + margin-bottom: rem(40px); + padding-bottom: 0px; + box-shadow:unset; + } + @include mq(laptop){ + margin-bottom: rem(60px); + } +} +.c-cta--auction{ + padding: rem(60px 0 50px 0); + background-image: $gradient-grey-180; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_domain-search.scss b/app/assets/stylesheets/components/_domain-search.scss new file mode 100644 index 000000000..567922dbe --- /dev/null +++ b/app/assets/stylesheets/components/_domain-search.scss @@ -0,0 +1,54 @@ +.c-domain-search { + flex-grow: 1; + border-radius: rem(8px 0 0 8px); + overflow: hidden; +} + +.c-domain-search__wrap { + display: flex; + align-items: center; + gap: rem(16px); + padding: rem(0 16px 0 0); + + @include mq(tablet) { + gap: rem(24px); + padding: rem(0 24px 0 0); + } +} + +.s-domain-search { + + input { + border: none; + background-color: transparent; + flex-grow: 1; + font-weight: 500; + font-size: rem(18px); + line-height: rem(21px); + padding: rem(24px 16px 24px 32px); + + @include mq(desktop) { + padding: rem(48px 24px 48px 48px); + } + + &::placeholder { + color: #051729; + opacity: 1; + } + } + + button { + background-color: $primary-blue-color; + border: none; + width: rem(48px); + height: rem(48px); + border-radius: rem(24px); + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background-color: $secondary-blue-color; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_dropdown.scss b/app/assets/stylesheets/components/_dropdown.scss new file mode 100644 index 000000000..f2a16ee3f --- /dev/null +++ b/app/assets/stylesheets/components/_dropdown.scss @@ -0,0 +1,73 @@ +.c-dropdown { + position: relative; + cursor: pointer; +} + +.c-dropdown__label { + font-weight: 500; + font-size: rem(14px); + line-height: rem(16px); + color: $white-color; + display: flex; + align-items: center; + + svg { + margin-left: rem(3.5px); + } +} + +.c-dropdown__label__lang { + width: rem(30px); + overflow: hidden; + text-transform: uppercase; +} + +.c-dropdown__list { + position: absolute; + right: 0; + top: rem(24px); + opacity: 0; + visibility: hidden; + transform: translateY(10px); + transition: 0.25s; +} + +.s-dropdown-list { + padding: rem(8px); + margin: 0; + list-style: none; + @include box-shadow; + border-radius: rem(8px); + z-index: 99; + + li { + display: flex; + } + + a { + padding: rem(4px 8px); + color: $black-color; + font-weight: 500; + font-size: rem(14px); + line-height: rem(16px); + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + + &:hover { + color: $primary-blue-color; + } + } + + li.active { + font-weight: 800; + } +} + +.c-dropdown.is-open { + .c-dropdown__list { + opacity: 1; + visibility: visible; + transform: translateX(0); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_footer.scss b/app/assets/stylesheets/components/_footer.scss new file mode 100644 index 000000000..5280b8c7f --- /dev/null +++ b/app/assets/stylesheets/components/_footer.scss @@ -0,0 +1,285 @@ +.c-footer { + background: linear-gradient(90deg, #003d63 1.35%, #064f7c 98.65%); + margin-top: rem(70px); + + @include mq(tablet) { + margin-top: rem(185px); + } + + @include mq(desktop) { + margin-top: rem(105px); + } +} + +.c-footer__grid { + display: flex; + flex-wrap: wrap; + gap: rem(32px); + position: relative; + @include mq(tablet) { + gap: rem(40px); + } + @include mq(desktop) { + gap: rem(112px); + flex-wrap: nowrap; + } +} + +.c-footer__highlight { + width: 100%; + background: $gradient-blue-secondary; + box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.2); + border-radius: rem(8px); + display: flex; + gap: rem(20px); + align-items: center; + padding: rem(20px 18px); + transform-origin: bottom left; + flex-direction: row; + justify-content: space-around; + transform: translateY(-20px); + + @include mq(tablet) { + padding: rem(22px 20px); + top: -22%; + position: absolute; + } + + @media (min-width: 992px) and (max-width: 1044px) { + top: -28%; + } + + @include mq(desktop) { + width: rem(290px); + flex-direction: column; + border-radius: rem(8px 8px 0 0); + transform: scale(1.1); + position: initial; + } + + @include mq(wide) { + padding: rem(40px 32px); + width: rem(340px); + } +} + +.c-footer__highlight__grid { + display: flex; + flex-direction: column; + gap: rem(20px); + text-align: left !important; + + @include mq(tablet) { + gap: rem(12px); + } + + @include mq(desktop) { + text-align: center !important; + gap: rem(20px); + } +} + +.c-footer__highlight__grid--row-t { + @include mq(tablet) { + flex-direction: row; + justify-content: space-around; + flex: 1; + align-items: flex-end; + } + + @include mq(desktop) { + width: auto; + flex-direction: column; + align-items: center; + } +} + +.c-footer__highlight__title { + font-weight: 900; + font-size: rem(16px); + line-height: rem(20px); + text-align: left; + color: $white-color; + margin: 0; + + @include mq(desktop) { + text-align: center; + } +} + +.c-footer__highlight__info, +.c-footer__highlight__contact { + font-weight: 500; + font-size: rem(14px); + line-height: rem(22px); + text-align: left; + color: $white-color; + + @include mq(desktop) { + text-align: center; + } + + a { + color: $white-color; + text-decoration: none; + + &:hover { + color: $secondary-blue-color; + } + } +} + +.c-footer__highlight__contact { + span { + display: flex; + align-items: center; + gap: rem(12px); + + @include mq(desktop) { + justify-content: center; + } + } +} + +.c-footer__grid__row { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + position: relative; + row-gap: rem(32px); + flex-grow: 1; + padding-top: rem(10px); + padding-bottom: rem(38px); + justify-content: space-between; + + @include mq(tablet) { + padding-bottom: rem(10px); + gap: rem(30px); + padding-top: rem(72px); + } + + @include mq(desktop) { + row-gap: rem(16px); + padding-bottom: 0; + flex-wrap: nowrap; + justify-content: space-between; + padding-top: rem(64px); + } +} + +.s-footer-col { + color: $white-color; + line-height: rem(24px); + + &:nth-child(1) { + width: 100%; + @include mq(tablet) { + width: calc(100% / 3); + } + @include mq(desktop) { + width: calc(100% / 4); + } + } + &:nth-child(2) { + width: 100%; + @include mq(tablet) { + width: calc(100% / 3); + flex: 1; + } + @include mq(desktop) { + width: calc(100% / 4); + flex: unset; + } + } + &:nth-child(3) { + width: 100%; + @include mq(tablet) { + padding-right: 0; + width: calc(100% / 3 - 4rem); + flex: unset; + } + @include mq(desktop) { + width: calc(100% / 4); + } + } + &:nth-child(4) { + width: 100%; + + @include mq(desktop) { + width: calc(100% / 4); + align-items: flex-start; + } + + } + + p { + color: $white-color; + } + + a { + color: $grey-300-color; + text-decoration: none; + margin-bottom: rem(2px); + display: inline-block; + + &:hover { + color: $primary-blue-color !important; + } + } + + .c-socials { + gap: rem(8px); + margin-bottom: rem(46px); + @include mq(desktop) { + display: flex; + } + + a { + width: rem(24px); + height: rem(24px); + + svg { + width: rem(24px); + height: rem(24px); + fill: $white-color; + } + } + } +} + +.c-footer__sviiter { + position: absolute; + bottom: rem(36px); + right: 0; + + @include mq(tablet) { + bottom: rem(50px); + right: rem(16px); + } + @include mq(desktop) { + bottom: rem(10px); + right: 1rem; + } + @include mq(menu) { + bottom: rem(10px); + right: 3rem; + } + svg, + svg path { + transition: $transition; + } + + &:hover { + svg, + svg path { + fill: $white-color; + } + } + + a:hover { + svg, + svg path { + fill: $primary-blue-color; + } + } +} diff --git a/app/assets/stylesheets/components/_fp.scss b/app/assets/stylesheets/components/_fp.scss new file mode 100644 index 000000000..8c7863c4e --- /dev/null +++ b/app/assets/stylesheets/components/_fp.scss @@ -0,0 +1,9 @@ +.c-fp__btns-wrapper{ + gap: 1rem; +} +.c-fp__input { + margin-bottom: 40px; +} +.c-fp__btn{ + font-size: rem(14px); +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_header.scss b/app/assets/stylesheets/components/_header.scss new file mode 100644 index 000000000..d229a6795 --- /dev/null +++ b/app/assets/stylesheets/components/_header.scss @@ -0,0 +1,88 @@ +.c-header { + background-color: $black-color; + color: $white-color; + display: flex; + align-items: center; + z-index: 99; + position: relative; +} + +.c-header__menu { + display: block; + + @include mq(desktop) { + display: none; + } +} + +.c-header__lang { + margin-left: auto; + margin-right: rem(20px); + @include mq(desktop) { + margin-right: rem(32px); + } + + @include mq(ultra) { + margin-left: 0; + margin-right: rem(50px); + } +} + +.c-header__toggle { + display: flex; + align-items: center; + gap: rem(16px); + padding: rem(16px 25px); + background-color: $gradient-blue; + color: $white-color; + border-radius: 0; + border: none; +} + +.c-header__search__toggle { + padding: rem(8px); + margin-left: rem(10px); + margin-right: rem(18px); + background-color: transparent; + border: none; + display: block; + + @include mq(desktop) { + display: none; + } +} + +.c-header__user { + display: flex; + gap: rem(9px); + color: $black-color; + align-items: center; + white-space: nowrap; + text-decoration: none; + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + color: var(--Black, #212224); + @include mq(desktop){ + color: $white-color; + } + svg { + fill: $black-color; + @include mq(desktop){ + fill: $white-color; + } + } + &:hover { + color: $black-color; + text-decoration: underline; + @include mq(desktop){ + color: $grey-200-color; + } + svg { + fill: $black-color; + @include mq(desktop){ + fill: $grey-200-color; + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_hero.scss b/app/assets/stylesheets/components/_hero.scss new file mode 100644 index 000000000..45e155ce0 --- /dev/null +++ b/app/assets/stylesheets/components/_hero.scss @@ -0,0 +1,116 @@ +.c-hero { + margin-bottom: rem(25px); +} + +.c-hero__content { + color: $white-color; + background: url("hero.png") center center/cover no-repeat; + border-radius: 0 0 rem(8px 8px); + display: flex; + flex-direction: column; + align-items: center; + padding: rem(32px 0 72px); + + @include mq(desktop) { + padding: rem(70px 0 64px); + } +} + +.c-hero__content__title { + font-weight: 900; + font-size: 8vw; + line-height: 1.2; + margin: rem(20px 0 16px); + text-align: center; + padding: rem(0 8px); + + @include mq(tablet) { + font-size: rem(40px); + line-height: rem(40px); + } +} +.c-hero__content__desc { + font-weight: 500; + font-size: 4vw; + line-height: 1.2; + margin: 0 0 rem(24px); + + @include mq(tablet) { + font-size: rem(18px); + line-height: rem(20px); + } +} + +.c-hero__content__action { + + a { + @include btn-orange; + } +} + +.c-hero__search { + background-color: transparent; + transform: translateY(rem(-60px)); + + padding: rem(0 16px); + + @include mq(tablet) { + padding: rem(0 24px); + } + + @include mq(desktop) { + padding: rem(0 40px); + } +} + +.c-hero__search__form { + background-color: $white-color; + border-radius: rem(8px 0 0 8px); + overflow: hidden; + display: flex; + align-items: center; + flex-grow: 1; +} + +.c-hero__search__grid { + @include box-shadow; + display: flex; + flex-wrap: wrap; + background-color: transparent !important; + border-radius: rem(8px); + overflow: hidden; + + @include mq(tablet) { + flex-wrap: nowrap; + } +} + +.c-hero__search__info { + background-color: $secondary-blue-color; + color: $white-color; + font-size: rem(14px); + line-height: rem(25px); + width: 100%; + padding: rem(20px 18px 20px 24px); + + @include mq(tablet) { + width: 40%; + } + + @include mq(desktop) { + width: 32%; + } + + @include mq(wide) { + width: 28%; + } + + a { + color: $white-color; + text-decoration: underline; + + &:hover { + color: $primary-orange-color; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_imagebox.scss b/app/assets/stylesheets/components/_imagebox.scss new file mode 100644 index 000000000..2da14f150 --- /dev/null +++ b/app/assets/stylesheets/components/_imagebox.scss @@ -0,0 +1,70 @@ +.c-imagebox { + position: relative; +} +.c-imagebox__img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: rem(8px); +} +.c-imagebox__text-wrapper { + position: absolute; + z-index: 1; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: rem(12px); + background: rgba(255, 255, 255, 0.65); + backdrop-filter: blur(2.784313678741455px); + width: rem(380px); + max-width: 90%; + height: 70px; + display: flex; + justify-content: space-between; + align-items: center; + padding: rem(0 18px); + text-decoration: none; +} +.c-imagebox__text { + color: $black-color; + font-size: rem(12px); + font-weight: 500; + line-height: rem(16px); + margin-left: rem(15px); + strong { + font-weight: 700; + display: block; + } +} +.c-imagebox__cta { + color: $grey-500-color; + font-size: rem(12px); + font-weight: 500; + line-height: rem(16px); + flex: 1; + text-align: right; +} +.c-textbox { + @include mq(laptop) { + padding: rem(0 44px); + } + p { + color: $black-color; + font-size: rem(14px); + font-weight: 400; + line-height: rem(28px); + margin: rem(0 0 16px 0); + @include mq(laptop) { + font-size: rem(18px); + font-weight: 500; + margin: rem(0 0 30px 0); + } + strong { + font-weight: 500; + @include mq(laptop) { + font-size: rem(16px); + font-weight: 700; + } + } + } +} diff --git a/app/assets/stylesheets/components/_login.scss b/app/assets/stylesheets/components/_login.scss new file mode 100644 index 000000000..592e4ce44 --- /dev/null +++ b/app/assets/stylesheets/components/_login.scss @@ -0,0 +1,65 @@ +.c-login__title{ + line-height: rem(30px); + margin-bottom: rem(16px); + + @include mq(tablet){ + margin-bottom: rem(30px); + } +} +.c-login__explanation { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + margin-bottom: rem(30px); + } +.c-login__button { + padding: rem(6px 16px); + font-size: rem(14px); + line-height: rem(20px); +} +.c-login__icons { + gap: rem(16px); +} + +.c-login__input{ + margin-bottom: rem(30px); +} +.c-login__btns-wrapper { + margin-bottom: rem(30px); + gap: 1rem; +} +.c-login__btns{ + display: flex; + gap: 1rem; + flex-wrap: wrap; +} +.c-login__btn{ + font-size: rem(14px); + flex-shrink: 0; + + &:first-child{ + @include mq(tablet){ + margin: 0 rem(10px) 0 0; + } + } +} +.c-login__checkbox-container{ + margin-top: rem(7px); + + .o-checkbox-text{ + white-space: nowrap; + } +} +.c-login__links { + flex-wrap: wrap; + gap: 8px; +} +.c-login__remind-link{ + color: $primary-blue-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + text-decoration-line: underline; + cursor: pointer; +} + diff --git a/app/assets/stylesheets/components/_modal.scss b/app/assets/stylesheets/components/_modal.scss new file mode 100644 index 000000000..46df2618e --- /dev/null +++ b/app/assets/stylesheets/components/_modal.scss @@ -0,0 +1,543 @@ +.c-modal { + background-color: $white-color; + opacity: 1; + border-radius: rem(8px); + position: fixed; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: 96%; + max-height: 98vh; + overflow: hidden; + + @include mq(tablet) { + width: rem(600px); + } + + @include mq(desktop) { + width: rem(780px); + } +} + +.c-modal--sm { + width: auto; + min-width: rem(320px); + max-width: 98%; +} + +.c-modal__scroll { + overflow-x: hidden; + overflow-y: auto; + scroll-behavior: smooth; + scrollbar-width: thin; + scrollbar-color: $grey-400-color $grey-100-color; + + /* width */ + ::-webkit-scrollbar { + width: 20px; + border-radius: 12px; + } + + /* Track */ + ::-webkit-scrollbar-track { + background: $grey-100-color; + border-radius: rem(20px); + } + + /* Handle */ + ::-webkit-scrollbar-thumb { + background: $grey-400-color; + } + + max-height: calc(98vh - 2.5rem); + + @include mq(tablet) { + padding-right: rem(16px); + max-height: calc(98vh - 3rem); + } + + @include mq(desktop) { + padding-right: rem(36px); + max-height: calc(98vh - 4.5rem); + } +} + +.c-modal__scroll__content { + padding: rem(20px 16px); + position: relative; + + @include mq(tablet) { + padding: rem(24px 20px); + width: rem(600px); + } + + @include mq(desktop) { + padding: rem(36px 40px); + width: rem(780px); + } +} + +.c-modal__header { + padding-bottom: rem(30px); + border-bottom: 1px solid $grey-200-color; +} + +.c-modal__header__title { + color: $primary-green-color; + display: flex; + gap: rem(8px); + align-items: center; + margin-bottom: rem(16px); + font-weight: 800; + font-size: rem(14px); + line-height: rem(24px); +} + +.c-modal__header__subject { + display: flex; + gap: rem(16px); + align-items: center; + margin-bottom: rem(20px); +} + +.c-modal__header__subject__title { + font-weight: 900; + font-size: 6vw; + line-height: 1.25; + + @include mq(tablet) { + font-size: rem(30px); + line-height: rem(30px); + } +} + +.c-modal__header__desc { + display: flex; + gap: rem(8px); + align-items: center; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); +} + +.c-modal__close-btn { + position: absolute; + padding: 0; + border: none; + border-radius: 50%; + background: transparent; + right: rem(16px); + top: rem(16px); + display: flex; + align-items: center; + justify-content: center; + + &:hover { + opacity: 0.7; + } + + @include mq(tablet) { + right: rem(20px); + top: rem(20px); + } + + @include mq(desktop) { + right: rem(36px); + top: rem(36px); + } +} + +.c-modal__grid { + display: flex; + gap: rem(12px); + + @include mq(tablet) { + gap: rem(0 20px); + } + + @include mq(laptop) { + gap: rem(0 30px); + } + + @include mq(desktop) { + gap: rem(0 40px); + } +} + +.c-modal__grid--gap { + &:nth-child(-n + 2) { + gap: rem(12px); + + @include mq(tablet) { + gap: rem(20px); + } + + @include mq(laptop) { + gap: rem(30px); + } + + @include mq(desktop) { + gap: rem(40px); + } + } +} + +.c-modal__main { + padding: rem(40px 0); +} + +.c-modal__main__info { + background-color: $grey-100-color; + display: flex; + flex-direction: column; + gap: rem(16px); + padding: rem(16px); + border-radius: rem(8px); + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + width: calc(50% - 0.375rem); + align-items: center; + text-align: center; + + @include mq(tablet) { + padding: rem(24px); + width: calc(50% - 0.75rem); + } + + @include mq(laptop) { + padding: rem(24px); + width: calc(50% - 0.9375rem); + } + + @include mq(desktop) { + padding: rem(24px); + width: calc(50% - 1.25rem); + } + + h3 { + line-height: 30px; + } +} + +.c-modal__main__info__wrapper { + height: 100%; + padding: rem(24px 0 0 0); + + @include mq(desktop) { + padding: rem(24px 6px 0 0); + } +} + +.c-modal__main__info--black { + background-color: $black-color; + color: $white-color; +} + +.c-modal__main__info--orange { + background: $gradient-orange-secondary; + color: $white-color; +} + +.c-modal__main__info--white { + background-color: $white-color; + padding: 0; + + span { + text-align: left; + } +} + +.c-modal__main__info--wide { + width: 100%; + + @include mq(tablet) { + width: calc(50% - .75rem); + } + + @include mq(laptop) { + width: calc(50% - 0.9375rem); + } + + @include mq(desktop) { + width: calc(50% - 1.25rem); + } +} + + + + +.c-modal__col { + width: calc(50% - 0.375rem); + + @include mq(tablet) { + width: calc(50% - 0.75rem); + } + + @include mq(laptop) { + width: calc(50% - 0.9375rem); + } + + @include mq(desktop) { + width: calc(50% - 1.25rem); + } +} + +.c-modal-overlay { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: rgba(21, 21, 21, 0.5); + z-index: 9999; + visibility: hidden; + opacity: 0; + transition: $transition; + + &.is-open { + visibility: visible; + opacity: 1; + } +} + +.c-modal { + label:not(.o-checkbox) { + width: 100%; + display: block; + padding-left: rem(16px); + margin-bottom: rem(8px); + font-weight: 800; + font-size: rem(14px); + line-height: rem(24px); + } + + .c-modal__col:nth-child(2) { + label { + padding-left: 0; + } + } + + input[type="submit"] { + cursor: pointer; + } +} + +.c-modal__calculation { + padding: rem(12px 0); + display: block; +} + +.c-modal__input-desc { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + color: $grey-400-color; + padding-left: rem(16px); + margin-top: rem(16px); + display: block; +} + +.c-modal__link { + font-weight: 500; + font-size: rem(14px); + line-height: rem(14px); + text-decoration: underline; + color: $primary-blue-color; + + &:hover { + color: $black-color; + } +} + +.c-modal__link--first-col { + display: inline-block; + margin-top: rem(24px); + margin-left: rem(16px); +} + +.c-modal__footer { + display: flex; + justify-content: space-between; +} + +.c-modal__footer__col { + display: flex; + gap: rem(24px); + .c-btn { + font-size: rem(14px); + cursor: pointer; + } +} + +.c-modal--error { + padding: rem(24px); + + .c-modal__header__title { + color: $error-color; + } + + .c-modal__header__subject__title { + font-size: rem(24px); + word-break: break-word; + } + + .c-modal__header { + padding: rem(0 0 8px); + } + + .c-modal__main { + padding: rem(8px 0); + } +} + +.c-modal.c-modal--bill { + .c-modal__grid { + gap: rem(30px); + + @include mq(tablet) { + gap: rem(0 20px); + } + + @include mq(laptop) { + gap: rem(0 30px); + } + + @include mq(desktop) { + gap: rem(0 40px); + } + } + + .c-modal__main { + padding: rem(30px 0 50px 0); + + @include mq(tablet) { + padding: rem(40px 0); + } + } + + .c-modal__scroll__content { + padding: rem(40px 20px 30px 20px); + + @include mq(tablet) { + padding: rem(40px); + } + } + + .c-modal__header__title { + color: $grey-400-color; + } + + .c-modal__text-title { + margin-bottom: 20px; + padding-left: 0; + } + + .c-modal__text { + color: $black-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + } + + .c-modal__extrainfo { + gap: 70px; + } + + td { + word-break: break-all; + + strong { + word-break: break-all; + } + } + + thead { + th { + background-color: $grey-400-color; + + &:first-child { + padding: 0; + font-size: 0; + + @include mq(tablet) { + padding: rem(8px 16px); + font-size: 14px; + } + } + } + } + + tbody { + tr { + transition: background 0.3s; + + td { + transition: font-weight 0.3s; + + &:first-child { + padding: 0; + font-size: 0; + + @include mq(tablet) { + padding: rem(8px 16px); + font-size: 14px; + } + } + } + + &:hover { + background-color: $grey-100-color; + } + } + } + + tfoot { + td { + background-color: $black-color; + color: $white-color; + font-weight: 800; + border: 0; + } + } + + .c-modal__footer { + margin-bottom: 20px; + + .c-btn { + font-size: rem(14px); + } + } + + .c-modal__col { + width: 100%; + + @include mq(tablet) { + width: calc(50% - 0.75rem); + } + + @include mq(laptop) { + width: calc(50% - 0.9375rem); + } + + @include mq(desktop) { + width: calc(50% - 1.25rem); + } + } +} + +.c-modal--fp { + .c-modal__header__title { + span { + &:last-child { + color: $primary-blue-color; + } + } + } +} + +.c-modal--vi { + .c-modal__header { + padding-bottom: rem(10px); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_news.scss b/app/assets/stylesheets/components/_news.scss new file mode 100644 index 000000000..f3d007f71 --- /dev/null +++ b/app/assets/stylesheets/components/_news.scss @@ -0,0 +1,164 @@ +.c-news { + padding: rem(40px 0 48px); + + @include mq(tablet) { + padding: rem(50px 0 64px); + } +} + +.c-news__header { + display: flex; + justify-content: space-between; + margin-bottom: rem(48px); + + @include mq(tablet) { + margin-bottom: 54px; + } +} + +.c-news__title { + margin: 0; +} + +.c-news__item__img { + display: flex; + height: rem(275px); + + img { + max-width: 100%; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + } +} + +.c-news__item { + background-color: $white-color; + @include box-shadow; + border-radius: rem(8px); + overflow: hidden; +} + +.c-news__item__content { + padding: rem(20px 16px); + + @include mq(tablet) { + padding: rem(24px 20px); + } + @include mq(desktop) { + padding: rem(34px 32px); + } +} + +.c-news__item__meta { + display: flex; + justify-content: space-between; + margin: 0 0 rem(24px); + color: $grey-400-color; + + .article--type { + font-weight: 800; + font-size: rem(14px); + line-height: rem(20px); + text-transform: uppercase; + margin: 0; + + @include mq(tablet) { + font-size: rem(13px); + } + + @include mq(laptop) { + font-size: rem(14px); + } + } + + .article--topic { + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + margin: 0; + + &::before { + content: '#'; + } + + @include mq(tablet) { + font-size: rem(13px); + } + @include mq(laptop) { + font-size: rem(14px); + } + } +} + +.c-news__item__title { + margin: 0 0 rem(24px); + + @include mq(tablet) { + font-size: rem(16px); + } + + @include mq(laptop) { + font-size: rem(24px); + } + + + a { + text-decoration: none; + color: $black-color; + + &:hover { + color: $primary-blue-color; + } + } +} + +.c-news__item__excerpt { + margin: 0 0 rem(24px); + font-weight: 500; + line-height: rem(25px); + + & > div { + text-align: left !important; + } + + @include mq(tablet) { + font-size: rem(13px); + } + + @include mq(laptop) { + font-size: rem(14px); + } + + b { + font-weight: 500; + } + + a { + color: $black-color; + + &:hover { + color: $grey-400-color; + } + } +} + +.c-news__item__footer { + display: flex; + justify-content: space-between; + + @include mq(tablet) { + font-size: rem(13px); + } + + @include mq(laptop) { + font-size: rem(14px); + } + + .article--date { + display: flex; + align-items: center; + gap: rem(9px); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_notice.scss b/app/assets/stylesheets/components/_notice.scss new file mode 100644 index 000000000..4b8d60dc1 --- /dev/null +++ b/app/assets/stylesheets/components/_notice.scss @@ -0,0 +1,120 @@ +// Overwrite default toastify styling +.toastify { + display: flex; + align-items: center; + gap: 2rem; + padding: 0.75rem 1rem; + color: $white-color; + background-color: $white-color; + justify-content: space-between; + font-size: rem(15px); + margin: 0 0 1rem; + border: 0; + border-radius: 8px; + box-shadow: 0 0 48px 0 #0000001F; +} + +.toastify.on { + opacity: 0.9; +} + +.toast-close { + background: transparent center center/100% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='16' cy='16' r='15.5' stroke-width='1.5' stroke='%23E2E2E2'/%3E%3Cpath d='M12 12L20 20' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12 20L20 12' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + width: 1.5rem; + height: 1.5rem; + display: block; + padding: 0; + transition: 0.3s; + border: none; + cursor: pointer; + font-size: 0; + opacity: 1; +} +// -- // + +.c-toast-wrapper { + position: fixed; + bottom: 50px; + left: 50%; + transform: translateX(-50%); + width: 95%; + transition: 0.5s; + + @include mq(tablet) { + width: 70%; + } + + @include mq(desktop) { + width: rem(800px); + } +} + +.c-toast { + display: flex; + gap: 2rem; + padding: 0.75rem 1rem; + color: $black-color; + background-color: $white-color; + justify-content: space-between; + font-size: rem(13px); + margin: 0 0 1rem; + border: 0; + border-radius: 8px; + box-shadow: 0 0 48px 0 #0000001F; + + &:last-child { + // margin-bottom: 0; + } + + p { + margin-top: 0; + font-size: inherit; + color: inherit; + line-height: 1.5rem; + margin-bottom: 0.5rem; + } + + p:last-child { + margin: 0; + } +} + +.c-toast__close { + background: transparent center center/100% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='16' cy='16' r='15.5' stroke-width='1.5' stroke='%23212224'/%3E%3Cpath d='M12 12L20 20' stroke='%23212224' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12 20L20 12' stroke='%23212224' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + width: 1.5rem; + height: 1.5rem; + display: block; + padding: 0; + transition: 0.3s; + border: none; + cursor: pointer; +} + +.c-toast--error { + background: linear-gradient(90deg, #FF6E00 1.35%, #E66300 98.65%); + color: $white-color; + + .c-toast__close { + background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='16' cy='16' r='15.5' stroke-width='1.5' stroke='%23E2E2E2'/%3E%3Cpath d='M12 12L20 20' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12 20L20 12' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + } +} + +.c-toast--success { + background-color: $primary-green-color; + color: $white-color; + + .c-toast__close { + background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='16' cy='16' r='15.5' stroke-width='1.5' stroke='%23E2E2E2'/%3E%3Cpath d='M12 12L20 20' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12 20L20 12' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + } +} + +.c-toast--info { + background: linear-gradient(90deg, #FBAA19 1.35%, #dc9415 98.65%); + color: $white-color; + + .c-toast__close { + background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='16' cy='16' r='15.5' stroke-width='1.5' stroke='%23E2E2E2'/%3E%3Cpath d='M12 12L20 20' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12 20L20 12' stroke='%23E2E2E2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_notices.scss b/app/assets/stylesheets/components/_notices.scss new file mode 100644 index 000000000..f5175a82d --- /dev/null +++ b/app/assets/stylesheets/components/_notices.scss @@ -0,0 +1,60 @@ +.c-notices { + background-color: $white-color; + @include box-shadow; + position: absolute; + top: rem(24px); + right: 0; + width: rem(340px); + z-index: 999; + padding: rem(0 16px); + border-radius: rem(8px); + opacity: 1; + visibility: visible; + transition: $transition; + color: $black-color; + font-size: rem(14px); + line-height: 1.2; + + a { + padding: 0 !important; + } + + @include mq(laptop) { + top: rem(48px); + right: rem(32px); + } + + &.is-hidden { + visibility: hidden; + opacity: 0; + } +} + +.c-notices__footer { + padding: rem(10px 0 12px); +} + +.c-notice { + border-bottom: 1px solid $grey-200-color; + padding: rem(16px 0); +} + +.c-notice__header { + display: flex; + gap: rem(8px); + align-items: center; + font-weight: 800; +} + +.c-notice__main { + margin: rem(8px 0); +} + +// Do not show notices in hamburger menu +.js-notices-list { + display: none; + + @include mq(desktop) { + display: block; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_notifications.scss b/app/assets/stylesheets/components/_notifications.scss new file mode 100644 index 000000000..c4832f45a --- /dev/null +++ b/app/assets/stylesheets/components/_notifications.scss @@ -0,0 +1,99 @@ +.c-notifications { + margin: rem(20px 0 40px); + display: block; +} + +.c-notification { + padding: rem(24px) 0; + border-bottom: 1px solid $grey-200-color; + + &:last-child { + border-bottom: 0; + } +} + +.c-notification__header { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: rem(8px); + margin-bottom: rem(8px); + + @include mq(tablet) { + flex-wrap: nowrap; + } +} + +.c-notification__header__title { + margin: 0; + line-height: 1.5; + margin-top: rem(2px); +} + +.c-notification__header__date { + color: $grey-400-color; + font-size: rem(12px); + line-height: rem(20px); + width: 100%; + + @media (min-width: 450px) { + width: auto; + margin-left: auto; + } + + @include mq(tablet) { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + } +} + +.c-notification__main { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + + + @include mq(tablet) { + max-width: 90%; + } + + @include mq(desktop) { + max-width: 80%; + } + + @include mq(wide) { + max-width: 70%; + } +} + +.c-notification--read { + .c-notification__header { + .c-notification__header__title { + filter: opacity(0.55); + } + + .c-notification__header__icon { + filter: brightness(1) grayscale(1) opacity(0.9); + } + + .o-new { + display: none; + } + } +} + +.c-notification__heading { + display: flex; + align-items: flex-start; + gap: rem(8px); +} + +.c-notifications--ajax { + .c-notification { + + &:last-child { + border-bottom: 1px solid $grey-200-color; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_pagy.scss b/app/assets/stylesheets/components/_pagy.scss new file mode 100644 index 000000000..45323feb6 --- /dev/null +++ b/app/assets/stylesheets/components/_pagy.scss @@ -0,0 +1,77 @@ +.pagy-nav .page.prev, +.pagy-nav .page.prev a, +.pagy-nav .page.next, +.pagy-nav .page.next a { + width: rem(32px); + height: rem(32px); + border-radius: rem(16px) !important; + border: 1px solid $grey-200-color !important; + display: flex !important; + justify-content: center; + align-items: center; + padding: 0 !important; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + + border: none !important; + padding: rem(4px 4px) !important; + width: rem(80px); +} + +.pagy-nav .page.active { + width: rem(32px); + height: rem(32px); + border-radius: rem(16px) !important; + border: 1px solid $grey-200-color !important; + display: flex !important; + justify-content: center; + align-items: center; + padding: 0 !important; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + + background-color: $primary-green-color !important; + border-color: $primary-green-color !important; + color: $white-color !important; +} + +.pagy-nav .page.gap { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.pagy-nav .page a { + text-decoration: none; + color: black !important; + width: rem(32px); + height: rem(32px); + border-radius: rem(16px) !important; + border: 1px solid $grey-200-color !important; + display: flex !important; + justify-content: center; + align-items: center; + padding: 0 !important; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); +} + +.pagy-nav .page a:hover { + color: $black-color !important; + background: $grey-200-color !important; +} + +.pagy-nav { + display: flex; + justify-content: center; + width: 100%; + margin-top: rem(48px); + gap: rem(24px); + + span { + display: flex; + gap: rem(8px); + } +} diff --git a/app/assets/stylesheets/components/_profile.scss b/app/assets/stylesheets/components/_profile.scss new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/app/assets/stylesheets/components/_profile.scss @@ -0,0 +1 @@ + diff --git a/app/assets/stylesheets/components/_search.scss b/app/assets/stylesheets/components/_search.scss new file mode 100644 index 000000000..a9bf7038c --- /dev/null +++ b/app/assets/stylesheets/components/_search.scss @@ -0,0 +1,62 @@ +.c-search { + width: 100%; + background: $primary-blue-color; + position: absolute; + top: rem(-15px); + right: 0; + padding: rem(15px 24px 15px 0); + display: flex; + justify-content: flex-end; + transition: $transition; + + &.is-open { + top: rem(50px); + } + + @include mq(tablet) { + width: rem(240px); + padding: rem(15px 24px 15px 0); + } + + @include mq(desktop) { + width: rem(240px); + padding: rem(15px 32px 15px 0); + } +} + +.c-search__form { + display: flex; + position: relative; +} + +.c-search__form__btn { + position: absolute; + right: 0; + top: 0; + height: rem(32px); + border: none; + background: transparent; + padding: 0 rem(16px) 0 rem(4px); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.c-search__form__input { + color: $white-color; + border: 1px solid $white-color; + background: transparent; + height: rem(32px); + line-height: rem(32px); + padding: 0 rem(16px); + border-radius: rem(16px); + font-weight: 500; + font-size: rem(14px); + width: rem(240px); + + &::placeholder { + color: $white-color; + opacity: 1; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_socials.scss b/app/assets/stylesheets/components/_socials.scss new file mode 100644 index 000000000..82ae13e8f --- /dev/null +++ b/app/assets/stylesheets/components/_socials.scss @@ -0,0 +1,30 @@ +.c-socials { + display: flex; + gap: rem(25px); + align-items: center; + @include mq(desktop) { + display: none; + } +} + +.c-socials__link { + display: flex; + align-items: center; + justify-content: center; + gap: 25px; + + svg { + transition: 0.25s; + fill: $black-color; + + @include mq(desktop) { + fill: $white-color; + } + } + + &:hover { + svg { + fill: $primary-blue-color; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_table-whishlist.scss b/app/assets/stylesheets/components/_table-whishlist.scss new file mode 100644 index 000000000..dd35f4a27 --- /dev/null +++ b/app/assets/stylesheets/components/_table-whishlist.scss @@ -0,0 +1,78 @@ +.c-wishlist__table{ + tr{ + th{ + white-space: nowrap; + } + } + tbody{ + margin-top: rem(24px); + @include mq(laptop){ + margin-top: rem(30px); + } + @include mq(laptop){ + margin-top: 0; + } + tr{ + td{ + &:nth-child(1){ + order: 2; + margin-left: auto; + font-weight: 800; + @include mq(laptop){ + order: 1; + margin-left: 0; + font-weight: 500; + } + } + &:nth-child(2){ + font-weight: 800; + order: 1; + margin-left: 0; + max-width: 90%; + margin-bottom: rem(8px); + @include mq(laptop){ + order: 2; + width: 100%; + margin-bottom: 0; + } + } + &:nth-child(3){ + order: 3; + margin-bottom: rem(14px); + @include mq(laptop){ + margin-bottom: 0; + } + } + &:nth-child(4){ + order: 4; + } + } + } + } + +} + +.c-table__filters__search--whishlist{ + margin: 0 -1rem; + @include mq(tablet){ + margin: auto; + } + .c-table__filters__search{ + text-align: left; + } + input{ + width: rem(180px); + margin-right: rem(5px); + font-size: rem(12px); + + @include mq(mobile){ + width: rem(255px); + margin-right: rem(16px); + font-size: rem(14px); + } + @include mq(laptop){ + width: 550px; + } + + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_table.scss b/app/assets/stylesheets/components/_table.scss new file mode 100644 index 000000000..f064d1477 --- /dev/null +++ b/app/assets/stylesheets/components/_table.scss @@ -0,0 +1,256 @@ +.c-table--sortable .dataTables_wrapper { + th, td { + @include mq(laptop) { + height: rem(30px) !important; + } + } + + .dataTables_filter { + display: none; + } + + .dataTables_paginate { + display: flex; + justify-content: center; + width: 100%; + margin-top: rem(48px); + gap: rem(24px); + + span { + display: flex; + gap: rem(8px); + } + + .paginate_button { + width: rem(32px); + height: rem(32px); + border-radius: rem(16px) !important; + border: 1px solid $grey-200-color !important; + display: flex !important; + justify-content: center; + align-items: center; + padding: 0 !important; + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + + &.current { + background-color: $primary-green-color !important; + border-color: $primary-green-color !important; + color: $white-color !important; + } + + &:hover { + color: $black-color !important; + background: $grey-200-color !important; + } + + &.next, &.previous { + border: none !important; + padding: rem(4px 4px) !important; + width: rem(80px); + } + } + } + + table.dataTable.no-footer, table.dataTable thead th, table.dataTable thead td { + border-bottom: 0; + } +} + + +.c-table__filters { + background-color: $white-color; + border-radius: rem(4px 4px 0 0); + @include box-shadow; + padding: rem(16px); + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: rem(24px); + + @include mq(tablet) { + flex-wrap: nowrap; + } + + .c-auctions__legend { + order: 4; + + @include mq(tablet) { + display: none; + } + } +} + +.c-table__filters__btns { + display: flex; + gap: rem(8px); + flex-wrap: wrap; + + @include mq(tablet) { + gap: rem(16px); + } +} + +.c-table__filters__btn { + @include btn; + background-color: transparent; + color: $black-color; + display: inline-flex; + gap: rem(8px); + border: 1px solid $black-color; + align-items: center; + + &:hover { + background-color: $black-color; + color: $white-color; + } + + &.is-active { + background-color: $black-color; + color: $white-color; + } +} + +.c-table__filters__search { + flex-grow: 1; + order: 2; + text-align: right; + + @include mq(tablet) { + order: 0; + } +} + +.c-table__search__input { + background: 96% center/16px no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.34779 12.0569C3.39471 12.0569 0.992188 9.58191 0.992188 6.5397C0.992188 3.4975 3.39471 1.02246 6.34779 1.02246C9.30087 1.02246 11.7034 3.4975 11.7034 6.5397C11.7034 9.58191 9.30087 12.0569 6.34779 12.0569ZM6.34779 2.12591C3.98543 2.12591 2.06331 4.10605 2.06331 6.5397C2.06331 8.97336 3.98543 10.9535 6.34779 10.9535C8.71015 10.9535 10.6323 8.97336 10.6323 6.5397C10.6323 4.10605 8.71068 2.12591 6.34779 2.12591Z' fill='%23212224'/%3E%3Cpath d='M14.0037 14.9771C13.9334 14.9772 13.8637 14.9629 13.7988 14.9352C13.7338 14.9075 13.6748 14.8668 13.6251 14.8155L9.5045 10.5705C9.45477 10.5193 9.41533 10.4585 9.38842 10.3915C9.36151 10.3246 9.34766 10.2529 9.34766 10.1804C9.34766 10.108 9.36151 10.0362 9.38842 9.96932C9.41533 9.90239 9.45477 9.84158 9.5045 9.79036C9.55422 9.73913 9.61325 9.6985 9.67822 9.67078C9.74318 9.64305 9.81282 9.62878 9.88314 9.62878C9.95346 9.62878 10.0231 9.64305 10.0881 9.67078C10.153 9.6985 10.2121 9.73913 10.2618 9.79036L14.3824 14.0353C14.4575 14.1124 14.5087 14.2107 14.5295 14.3178C14.5503 14.4249 14.5398 14.5359 14.4992 14.6369C14.4587 14.7378 14.3899 14.824 14.3017 14.8846C14.2135 14.9451 14.1098 14.9774 14.0037 14.9771Z' fill='%23212224'/%3E%3C/svg%3E%0A"); + line-height: rem(32px); + height: rem(32px); + padding: rem(0 16px); + border: 1px solid $grey-200-color; + border-radius: rem(16px); + transition: $transition; + max-width: calc(100%); + width: 100%; + + @include mq(laptop) { + width: rem(300px); + } + + &:focus { + border-color: $black-color; + } +} + +.c-table__filters__more { + text-align: right; + width: 40%; + max-width: 100%; + + @include mq(tablet) { + width: auto; + } + + @include mq(desktop) { + width: rem(392px); + } +} + +.c-table--sortable, .c-table--responsive { + + table { + display: flex; + + @include mq(laptop) { + display: table; + } + + thead { + display: none; + + @include mq(laptop) { + display: table-row-group; + } + } + } + + tbody { + display: flex; + flex-wrap: wrap; + margin-top: rem(40px); + gap: rem(20px); + + @include mq(tablet) { + gap: rem(40px 32px); + } + + @include mq(laptop) { + display: table-row-group; + } + } + + tbody tr { + display: flex; + width: 100%; + flex-wrap: wrap; + background: $white-color; + border-radius: rem(8px); + @include box-shadow-light; + padding: rem(12px 16px 16px); + + @media (min-width: 568px) { + width: calc(50% - 0.65rem); + } + + @include mq(tablet) { + width: calc(50% - 1rem); + } + + @include mq(laptop) { + display: table-row; + width: auto; + box-shadow: none; + } + + td, th { + border: none; + padding: rem(2px 0) !important; + height: auto !important; + + @include mq(laptop) { + height: rem(50px) !important; + padding: rem(8px 16px) !important; + + border-right: 1px solid $grey-200-color; + border-bottom: 1px solid $grey-200-color; + + &:last-child { + border-right: 0; + } + } + } + + td:nth-child(2) { + margin-left: auto; + } + + td:nth-child(3), td:nth-child(4), td:nth-child(5), td:nth-child(6) { + width: 100%; + + @include mq(laptop) { + width: auto; + } + } + + td:nth-child(6) { + margin-top: rem(8px); + } + } +} + +.c-table--first-col-full { + th:first-child, td:first-child { + width: 100%; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_tabs.scss b/app/assets/stylesheets/components/_tabs.scss new file mode 100644 index 000000000..2cec36566 --- /dev/null +++ b/app/assets/stylesheets/components/_tabs.scss @@ -0,0 +1,93 @@ +.c-tabs { + +} + +.c-tabs__nav { + display: flex; +} + +.c-tabs__nav__btn { + font-weight: 500; + font-size: 14px; + line-height: 24px; + border: none; + border-top: 1px solid $grey-100-color; + border-right: 1px solid $grey-100-color; + padding: rem(4px 9px 4px 13px); + background: $white-color; + border-radius: 0 rem(8px) 0 0; + position: relative; + transition: z-index 0s, all $transition; + + @include mq(tablet) { + padding: rem(16px 20px 16px 26px); + } + + &:hover { + background: $green-light; + } + + &.is-active { + background: $gradient-green; + border:none; + //font-weight: 800; + color: $white-color; + + sup { + font-weight: 500; + } + } + + &:nth-child(1) { + padding: rem(4px 9px); + z-index: 8; + } + + &:nth-child(2) { + transform: translateX(rem(-6px)); + z-index: 7; + } + + &:nth-child(3) { + transform: translateX(rem(-12px)); + z-index: 6; + } + + &:nth-child(4) { + transform: translateX(rem(-18px)); + z-index: 5; + } + + &:nth-child(5) { + transform: translateX(rem(-24px)); + z-index: 4; + } + + &:nth-child(6) { + transform: translateX(rem(-30px)); + z-index: 3; + } + + &:nth-child(7) { + transform: translateX(rem(-36px)); + z-index: 2; + } +} + +.c-tabs__nav__btn--ind { + margin-left: auto; + @include btn-black; + padding: rem(6px 16px); + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + align-self: center; +} + +.c-tabs__content__tab { + display: none; + + &.is-active { + display: block; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_toolbar.scss b/app/assets/stylesheets/components/_toolbar.scss new file mode 100644 index 000000000..0e6916d2e --- /dev/null +++ b/app/assets/stylesheets/components/_toolbar.scss @@ -0,0 +1,367 @@ +.c-toolbar { + color: $white-color; + display: none; + min-width: rem(375px); + width: auto; + border-radius: rem(0 0 8px 8px); + overflow: hidden; + @include box-shadow-light; + background-color: $black-color; + position: absolute; + top: rem(50.4px); + left: 0; + flex-grow: 1; + z-index: 99; + + @include mq(desktop) { + position: initial; + border-radius: 0; + max-width: 100%; + min-width: unset; + display: block; + padding-right: 0; + } +} + +.c-toolbar__grid { + display: flex; + align-items: center; + flex-direction: column; + + @include mq(desktop) { + flex-direction: row; + } +} + +.c-toolbar__nav { + width: 100%; + max-width: rem(375px); + + @include mq(desktop) { + max-width: unset; + } +} + +.s-toolbar-nav { + margin: 0; + padding: 0 0 0 0; + display: flex; + list-style: none; + flex-direction: column; + background: $primary-blue-color; + + @include mq(desktop) { + background: transparent; + padding: 0; + flex-direction: row; + } + + & > li a { + font-weight: 800; + + @include mq(desktop) { + font-weight: 500; + } + } + + li { + display: flex; + gap: rem(6px); + flex-wrap: wrap; + align-items: center; + + &:nth-child(1) a:hover { + background-color: $primary-blue-color; + color: $white-color; + } + &:nth-child(2) a:hover { + background-color: $primary-green-color; + color: $white-color; + } + &:nth-child(3) a:hover { + background-color: $primary-orange-color; + color: $white-color; + } + &:nth-child(4) a:hover { + background-color: $secondary-blue-color; + color: $white-color; + } + &:nth-child(n+5) a:hover { + color: $white-color; + @include mq(desktop) { + background-color: #000000; + } + } + + @include mq(desktop) { + flex-wrap: nowrap; + } + } + + a { + display: block; + padding: rem(4px 0px 4px 24px); + text-decoration: none; + color: $white-color; + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + transition: $transition; + text-transform: uppercase; + white-space: nowrap; + + &:hover { + + @include mq(desktop) { + color: $primary-blue-color; + } + } + + @include mq(desktop) { + padding: rem(20px 32px); + text-transform: initial; + } + } + + li.is-active { + & > a { + @include mq(desktop) { + box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.2); + } + } + + a { + font-weight: 800; + + @include mq(desktop) { + background: $gradient-blue; + } + + &:hover { + color: $white-color; + } + } + + &:nth-child(2) { + a { + @include mq(desktop) { + background: $gradient-green; + } + } + + .submenu { + background: $primary-green-color; + } + } + + .submenu { + display: block; + @include mq(desktop) { + background-color: $primary-blue-color; + display: flex; + position: absolute; + top: rem(60px); + left: 0; + padding: 0 0 0 rem(32px); + gap: rem(32px); + } + + @include mq(wide) { + gap: rem(48px); + } + + li { + position: relative; + } + } + + .submenu a, .submenu button { + text-transform: uppercase; + font-weight: 500; + background: transparent; + border: none; + + @include mq(desktop) { + padding: rem(20px) 0; + } + } + + .submenu a:hover { + color: $black-color; + } + } + + li.hidden { + display: none; + } + + .submenu { + width: 100%; + list-style: none; + padding: rem(0 0 8px 24px); + margin: 0; + display: none; + + a { + font-weight: 500; + padding: rem(8px 0); + } + } + + & > li { + padding: rem(16px 0); + + @include mq(desktop) { + padding: 0; + } + + &:nth-child(2) { + background: $primary-green-color; + + @include mq(desktop) { + background: transparent; + } + } + &:nth-child(3) { + background: $primary-orange-color; + + @include mq(desktop) { + background: transparent; + } + } + &:nth-child(4) { + background: $secondary-blue-color; + + @include mq(desktop) { + background: transparent; + } + } + &:nth-child(5) { + background: $black-color; + + @include mq(desktop) { + background: transparent; + } + } + } + + .submenu .submenu { + display: none; + padding: rem(0 0 4px 12px) !important; + + @include mq(desktop) { + display: flex; + width: auto; + padding: rem(4px 0) !important; + gap: rem(4px) !important; + position: absolute; + top: 100%; + left: 0; + flex-direction: column; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: $transition; + + a { + padding: rem(8px 14px) !important; + max-width: rem(320px); + } + } + + a { + word-break: break-word; + white-space: initial; + max-width: rem(230px); + + @include mq(tablet) { + max-width: rem(250px); + } + + @include mq(tablet) { + word-break: normal; + white-space: initial; + max-width: rem(350px); + min-width: rem(200px); + } + + &:hover { + color: $secondary-blue-color; + } + } + } + + .submenu li:hover { + .o-arrow { + transform: rotate(180deg); + } + + .submenu { + opacity: 1; + visibility: visible; + transform: unset; + } + } +} + +.c-toolbar__socials { + margin-left: auto; + margin-right: rem(24px); + width: 100%; + background: $white-color; + padding: rem(16px 25px); + display: flex; + justify-content: space-between; + + + @include mq(desktop) { + margin-right: 0; + width: auto; + background: transparent; + } +} + +.c-toolbar__search { + margin-left: auto; + position: relative; + + .c-search { + position: initial; + top: unset; + right: unset; + } +} + +.c-toolbar__notice { + display: flex !important; + gap: rem(9px); + color: $white-color; + align-items: center; + white-space: nowrap; + text-decoration: none; + font-weight: 500; + font-size: rem(14px); + line-height: rem(20px); + padding: rem(0 24px 0 0) !important; + + @include mq(desktop) { + padding: rem(0 32px 0 0) !important; + } + + span { + display: flex; + gap: rem(4px); + } + + sup { + top: 0.65em; + } + + &:hover { + color: $black-color !important; + + svg { + fill: $black-color !important; + } + } +} diff --git a/app/assets/stylesheets/components/_wishlist.scss b/app/assets/stylesheets/components/_wishlist.scss new file mode 100644 index 000000000..b026f2df3 --- /dev/null +++ b/app/assets/stylesheets/components/_wishlist.scss @@ -0,0 +1,16 @@ +.c-whishlist__notice{ + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + display: inline-block; + padding-top: 30px; + @include mq(tablet){ + padding-top: 40px; + } + @include mq(laptop){ + padding-top: 60px; + } +} +.c-wishlist__btn{ + font-size: rem(14px); +} \ No newline at end of file diff --git a/app/assets/stylesheets/datatables/_datatables.scss b/app/assets/stylesheets/datatables/_datatables.scss new file mode 100644 index 000000000..df914ce0f --- /dev/null +++ b/app/assets/stylesheets/datatables/_datatables.scss @@ -0,0 +1,824 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#dt/dt-1.13.4/r-2.4.1/rg-1.3.1 + * + * Included libraries: + * DataTables 1.13.4, Responsive 2.4.1, RowGroup 1.3.1 + */ + +@charset "UTF-8"; +:root { + --dt-row-selected: 13, 110, 253; + --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-link: 9, 10, 11; +} + +table.dataTable td.dt-control { + text-align: center; + cursor: pointer; +} +table.dataTable td.dt-control:before { + height: 1em; + width: 1em; + margin-top: -9px; + display: inline-block; + color: white; + border: 0.15em solid white; + border-radius: 1em; + box-shadow: 0 0 0.2em #444; + box-sizing: content-box; + text-align: center; + text-indent: 0 !important; + font-family: "Courier New", Courier, monospace; + line-height: 1em; + content: "+"; + background-color: #31b131; +} +table.dataTable tr.dt-hasChild td.dt-control:before { + content: "-"; + background-color: #d33333; +} + +table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled, +table.dataTable thead > tr > td.sorting, +table.dataTable thead > tr > td.sorting_asc, +table.dataTable thead > tr > td.sorting_desc, +table.dataTable thead > tr > td.sorting_asc_disabled, +table.dataTable thead > tr > td.sorting_desc_disabled { + cursor: pointer; + position: relative; + padding-right: 26px; +} +table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after, +table.dataTable thead > tr > td.sorting:before, +table.dataTable thead > tr > td.sorting:after, +table.dataTable thead > tr > td.sorting_asc:before, +table.dataTable thead > tr > td.sorting_asc:after, +table.dataTable thead > tr > td.sorting_desc:before, +table.dataTable thead > tr > td.sorting_desc:after, +table.dataTable thead > tr > td.sorting_asc_disabled:before, +table.dataTable thead > tr > td.sorting_asc_disabled:after, +table.dataTable thead > tr > td.sorting_desc_disabled:before, +table.dataTable thead > tr > td.sorting_desc_disabled:after { + position: absolute; + display: block; + opacity: 0.125; + right: 10px; + line-height: 9px; + font-size: 0.8em; +} +table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before, +table.dataTable thead > tr > td.sorting:before, +table.dataTable thead > tr > td.sorting_asc:before, +table.dataTable thead > tr > td.sorting_desc:before, +table.dataTable thead > tr > td.sorting_asc_disabled:before, +table.dataTable thead > tr > td.sorting_desc_disabled:before { + bottom: 50%; + content: "▲"; + content: "▲"/""; +} +table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, +table.dataTable thead > tr > td.sorting:after, +table.dataTable thead > tr > td.sorting_asc:after, +table.dataTable thead > tr > td.sorting_desc:after, +table.dataTable thead > tr > td.sorting_asc_disabled:after, +table.dataTable thead > tr > td.sorting_desc_disabled:after { + top: 50%; + content: "▼"; + content: "▼"/""; +} +table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, +table.dataTable thead > tr > td.sorting_asc:before, +table.dataTable thead > tr > td.sorting_desc:after { + opacity: 0.6; +} +table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, +table.dataTable thead > tr > td.sorting_desc_disabled:after, +table.dataTable thead > tr > td.sorting_asc_disabled:before { + display: none; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} + +div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after, +div.dataTables_scrollBody > table.dataTable > thead > tr > td:before, +div.dataTables_scrollBody > table.dataTable > thead > tr > td:after { + display: none; +} + +div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -26px; + text-align: center; + padding: 2px; +} +div.dataTables_processing > div:last-child { + position: relative; + width: 80px; + height: 15px; + margin: 1em auto; +} +div.dataTables_processing > div:last-child > div { + position: absolute; + top: 0; + width: 13px; + height: 13px; + border-radius: 50%; + background: rgb(13, 110, 253); + background: rgb(var(--dt-row-selected)); + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} +div.dataTables_processing > div:last-child > div:nth-child(1) { + left: 8px; + animation: datatables-loader-1 0.6s infinite; +} +div.dataTables_processing > div:last-child > div:nth-child(2) { + left: 8px; + animation: datatables-loader-2 0.6s infinite; +} +div.dataTables_processing > div:last-child > div:nth-child(3) { + left: 32px; + animation: datatables-loader-2 0.6s infinite; +} +div.dataTables_processing > div:last-child > div:nth-child(4) { + left: 56px; + animation: datatables-loader-3 0.6s infinite; +} + +@keyframes datatables-loader-1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} +@keyframes datatables-loader-3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} +@keyframes datatables-loader-2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(24px, 0); + } +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} +table.dataTable th.dt-left, +table.dataTable td.dt-left { + text-align: left; +} +table.dataTable th.dt-center, +table.dataTable td.dt-center, +table.dataTable td.dataTables_empty { + text-align: center; +} +table.dataTable th.dt-right, +table.dataTable td.dt-right { + text-align: right; +} +table.dataTable th.dt-justify, +table.dataTable td.dt-justify { + text-align: justify; +} +table.dataTable th.dt-nowrap, +table.dataTable td.dt-nowrap { + white-space: nowrap; +} +table.dataTable thead th, +table.dataTable thead td, +table.dataTable tfoot th, +table.dataTable tfoot td { + text-align: left; +} +table.dataTable thead th.dt-head-left, +table.dataTable thead td.dt-head-left, +table.dataTable tfoot th.dt-head-left, +table.dataTable tfoot td.dt-head-left { + text-align: left; +} +table.dataTable thead th.dt-head-center, +table.dataTable thead td.dt-head-center, +table.dataTable tfoot th.dt-head-center, +table.dataTable tfoot td.dt-head-center { + text-align: center; +} +table.dataTable thead th.dt-head-right, +table.dataTable thead td.dt-head-right, +table.dataTable tfoot th.dt-head-right, +table.dataTable tfoot td.dt-head-right { + text-align: right; +} +table.dataTable thead th.dt-head-justify, +table.dataTable thead td.dt-head-justify, +table.dataTable tfoot th.dt-head-justify, +table.dataTable tfoot td.dt-head-justify { + text-align: justify; +} +table.dataTable thead th.dt-head-nowrap, +table.dataTable thead td.dt-head-nowrap, +table.dataTable tfoot th.dt-head-nowrap, +table.dataTable tfoot td.dt-head-nowrap { + white-space: nowrap; +} +table.dataTable tbody th.dt-body-left, +table.dataTable tbody td.dt-body-left { + text-align: left; +} +table.dataTable tbody th.dt-body-center, +table.dataTable tbody td.dt-body-center { + text-align: center; +} +table.dataTable tbody th.dt-body-right, +table.dataTable tbody td.dt-body-right { + text-align: right; +} +table.dataTable tbody th.dt-body-justify, +table.dataTable tbody td.dt-body-justify { + text-align: justify; +} +table.dataTable tbody th.dt-body-nowrap, +table.dataTable tbody td.dt-body-nowrap { + white-space: nowrap; +} + +/* + * Table styles + */ +table.dataTable { + width: 100%; + margin: 0 auto; + clear: both; + border-collapse: separate; + border-spacing: 0; + /* + * Header and footer styles + */ + /* + * Body styles + */ +} +table.dataTable thead th, +table.dataTable tfoot th { + font-weight: bold; +} +table.dataTable thead th, +table.dataTable thead td { + padding: 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} +table.dataTable thead th:active, +table.dataTable thead td:active { + outline: none; +} +table.dataTable tfoot th, +table.dataTable tfoot td { + padding: 10px 10px 6px 10px; + border-top: 1px solid rgba(0, 0, 0, 0.3); +} +table.dataTable tbody tr { + background-color: transparent; +} +table.dataTable tbody tr.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9); + color: rgb(255, 255, 255); + color: rgb(var(--dt-row-selected-text)); +} +table.dataTable tbody tr.selected a { + color: rgb(9, 10, 11); + color: rgb(var(--dt-row-selected-link)); +} +table.dataTable tbody th, +table.dataTable tbody td { + padding: 8px 10px; +} +table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { + border-top: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.row-border tbody tr:first-child th, +table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, +table.dataTable.display tbody tr:first-child td { + border-top: none; +} +table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { + border-top: 1px solid rgba(0, 0, 0, 0.15); + border-right: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid rgba(0, 0, 0, 0.15); +} +table.dataTable.cell-border tbody tr:first-child th, +table.dataTable.cell-border tbody tr:first-child td { + border-top: none; +} +table.dataTable.stripe > tbody > tr.odd > *, table.dataTable.display > tbody > tr.odd > * { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.023); +} +table.dataTable.stripe > tbody > tr.odd.selected > *, table.dataTable.display > tbody > tr.odd.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.923); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.923)); +} +table.dataTable.hover > tbody > tr:hover > *, table.dataTable.display > tbody > tr:hover > * { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.035); +} +table.dataTable.hover > tbody > tr.selected:hover > *, table.dataTable.display > tbody > tr.selected:hover > * { + box-shadow: inset 0 0 0 9999px #0d6efd !important; + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 1)) !important; +} +table.dataTable.order-column > tbody tr > .sorting_1, +table.dataTable.order-column > tbody tr > .sorting_2, +table.dataTable.order-column > tbody tr > .sorting_3, table.dataTable.display > tbody tr > .sorting_1, +table.dataTable.display > tbody tr > .sorting_2, +table.dataTable.display > tbody tr > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); +} +table.dataTable.order-column > tbody tr.selected > .sorting_1, +table.dataTable.order-column > tbody tr.selected > .sorting_2, +table.dataTable.order-column > tbody tr.selected > .sorting_3, table.dataTable.display > tbody tr.selected > .sorting_1, +table.dataTable.display > tbody tr.selected > .sorting_2, +table.dataTable.display > tbody tr.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.919)); +} +table.dataTable.display > tbody > tr.odd > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.054); +} +table.dataTable.display > tbody > tr.odd > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.047); +} +table.dataTable.display > tbody > tr.odd > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.odd > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.039); +} +table.dataTable.display > tbody > tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.954); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.954)); +} +table.dataTable.display > tbody > tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.947); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.947)); +} +table.dataTable.display > tbody > tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.odd.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.939); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.939)); +} +table.dataTable.display > tbody > tr.even > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019); +} +table.dataTable.display > tbody > tr.even > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.011); +} +table.dataTable.display > tbody > tr.even > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.003); +} +table.dataTable.display > tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.919)); +} +table.dataTable.display > tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.911); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.911)); +} +table.dataTable.display > tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.903); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.903)); +} +table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.082); +} +table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.074); +} +table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.062); +} +table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.982); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.982)); +} +table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.974); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.974)); +} +table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.962); + box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.962)); +} +table.dataTable.no-footer { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} +table.dataTable.compact thead th, +table.dataTable.compact thead td, +table.dataTable.compact tfoot th, +table.dataTable.compact tfoot td, +table.dataTable.compact tbody th, +table.dataTable.compact tbody td { + padding: 4px; +} + +table.dataTable th, +table.dataTable td { + box-sizing: content-box; +} + +/* + * Control feature layout + */ +.dataTables_wrapper { + position: relative; + clear: both; +} +.dataTables_wrapper .dataTables_length { + float: left; +} +.dataTables_wrapper .dataTables_length select { + border: 1px solid #aaa; + border-radius: 3px; + padding: 5px; + background-color: transparent; + padding: 4px; +} +.dataTables_wrapper .dataTables_filter { + float: right; + text-align: right; +} +.dataTables_wrapper .dataTables_filter input { + border: 1px solid #aaa; + border-radius: 3px; + padding: 5px; + background-color: transparent; + margin-left: 3px; +} +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + padding-top: 0.755em; +} +.dataTables_wrapper .dataTables_paginate { + float: right; + text-align: right; + padding-top: 0.25em; +} +.dataTables_wrapper .dataTables_paginate .paginate_button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em 1em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + color: inherit !important; + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} +.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + color: inherit !important; + border: 1px solid rgba(0, 0, 0, 0.3); + background-color: rgba(230, 230, 230, 0.1); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); /* IE10+ */ + background: -o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%); /* W3C */ +} +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { + cursor: default; + color: #666 !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none; +} +.dataTables_wrapper .dataTables_paginate .paginate_button:hover { + color: white !important; + border: 1px solid #111; + background-color: #585858; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); /* W3C */ +} +.dataTables_wrapper .dataTables_paginate .paginate_button:active { + outline: none; + background-color: #2b2b2b; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); /* W3C */ + box-shadow: inset 0 0 3px #111; +} +.dataTables_wrapper .dataTables_paginate .ellipsis { + padding: 0 1em; +} +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: inherit; +} +.dataTables_wrapper .dataTables_scroll { + clear: both; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { + -webkit-overflow-scrolling: touch; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { + vertical-align: middle; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; +} +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} +.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, +.dataTables_wrapper.no-footer div.dataTables_scrollBody > table { + border-bottom: none; +} +.dataTables_wrapper:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; +} + +@media screen and (max-width: 767px) { + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em; + } +} +@media screen and (max-width: 640px) { + .dataTables_wrapper .dataTables_length, + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em; + } +} + + +table.dataTable.dtr-inline.collapsed > tbody > tr > td.child, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.child, +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty { + cursor: default !important; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before { + display: none !important; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control { + position: relative; + padding-left: 30px; + cursor: pointer; +} +table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before { + top: 50%; + left: 5px; + height: 1em; + width: 1em; + margin-top: -9px; + display: block; + position: absolute; + color: white; + border: 0.15em solid white; + border-radius: 1em; + box-shadow: 0 0 0.2em #444; + box-sizing: content-box; + text-align: center; + text-indent: 0 !important; + font-family: "Courier New", Courier, monospace; + line-height: 1em; + content: "+"; + background-color: #31b131; +} +table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before { + content: "-"; + background-color: #d33333; +} +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control, +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control { + padding-left: 27px; +} +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control:before { + left: 4px; + height: 14px; + width: 14px; + border-radius: 14px; + line-height: 14px; + text-indent: 3px; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control, +table.dataTable.dtr-column > tbody > tr > th.dtr-control, +table.dataTable.dtr-column > tbody > tr > td.control, +table.dataTable.dtr-column > tbody > tr > th.control { + position: relative; + cursor: pointer; +} +table.dataTable.dtr-column > tbody > tr > td.dtr-control:before, +table.dataTable.dtr-column > tbody > tr > th.dtr-control:before, +table.dataTable.dtr-column > tbody > tr > td.control:before, +table.dataTable.dtr-column > tbody > tr > th.control:before { + top: 50%; + left: 50%; + height: 0.8em; + width: 0.8em; + margin-top: -0.5em; + margin-left: -0.5em; + display: block; + position: absolute; + color: white; + border: 0.15em solid white; + border-radius: 1em; + box-shadow: 0 0 0.2em #444; + box-sizing: content-box; + text-align: center; + text-indent: 0 !important; + font-family: "Courier New", Courier, monospace; + line-height: 1em; + content: "+"; + background-color: #31b131; +} +table.dataTable.dtr-column > tbody > tr.parent td.dtr-control:before, +table.dataTable.dtr-column > tbody > tr.parent th.dtr-control:before, +table.dataTable.dtr-column > tbody > tr.parent td.control:before, +table.dataTable.dtr-column > tbody > tr.parent th.control:before { + content: "-"; + background-color: #d33333; +} +table.dataTable > tbody > tr.child { + padding: 0.5em 1em; +} +table.dataTable > tbody > tr.child:hover { + background: transparent !important; +} +table.dataTable > tbody > tr.child ul.dtr-details { + display: inline-block; + list-style-type: none; + margin: 0; + padding: 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li { + border-bottom: 1px solid #efefef; + padding: 0.5em 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li:first-child { + padding-top: 0; +} +table.dataTable > tbody > tr.child ul.dtr-details > li:last-child { + border-bottom: none; +} +table.dataTable > tbody > tr.child span.dtr-title { + display: inline-block; + min-width: 75px; + font-weight: bold; +} +div.dtr-modal { + position: fixed; + box-sizing: border-box; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 100; + padding: 10em 1em; +} +div.dtr-modal div.dtr-modal-display { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 50%; + height: 50%; + overflow: auto; + margin: auto; + z-index: 102; + overflow: auto; + background-color: #f5f5f7; + border: 1px solid black; + border-radius: 0.5em; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6); +} +div.dtr-modal div.dtr-modal-content { + position: relative; + padding: 1em; +} +div.dtr-modal div.dtr-modal-close { + position: absolute; + top: 6px; + right: 6px; + width: 22px; + height: 22px; + border: 1px solid #eaeaea; + background-color: #f9f9f9; + text-align: center; + border-radius: 3px; + cursor: pointer; + z-index: 12; +} +div.dtr-modal div.dtr-modal-close:hover { + background-color: #eaeaea; +} +div.dtr-modal div.dtr-modal-background { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 101; + background: rgba(0, 0, 0, 0.6); +} + +@media screen and (max-width: 767px) { + div.dtr-modal div.dtr-modal-display { + width: 95%; + } +} + + +table.dataTable tr.dtrg-group th { + background-color: #e0e0e0; + text-align: left; +} + +table.dataTable tr.dtrg-group.dtrg-level-0 th { + font-weight: bold; +} + +table.dataTable tr.dtrg-group.dtrg-level-1 th, +table.dataTable tr.dtrg-group.dtrg-level-2 th, +table.dataTable tr.dtrg-group.dtrg-level-3 th, +table.dataTable tr.dtrg-group.dtrg-level-4 th, +table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: #f0f0f0; + padding-top: 0.25em; + padding-bottom: 0.25em; + padding-left: 2em; + font-size: 0.9em; +} + +table.dataTable tr.dtrg-group.dtrg-level-2 th { + background-color: #f3f3f3; + padding-left: 2.5em; +} + +table.dataTable tr.dtrg-group.dtrg-level-3 th { + background-color: #f3f3f3; + padding-left: 3em; +} + +table.dataTable tr.dtrg-group.dtrg-level-4 th { + background-color: #f3f3f3; + padding-left: 3.5em; +} + +table.dataTable tr.dtrg-group.dtrg-level-5 th { + background-color: #f3f3f3; + padding-left: 4em; +} + + diff --git a/app/assets/stylesheets/elements/_body.scss b/app/assets/stylesheets/elements/_body.scss new file mode 100644 index 000000000..c22644874 --- /dev/null +++ b/app/assets/stylesheets/elements/_body.scss @@ -0,0 +1,6 @@ +body { + font-size: 16px; + font-family: $font-family; + color: $black-color; +} + diff --git a/app/assets/stylesheets/elements/_button.scss b/app/assets/stylesheets/elements/_button.scss new file mode 100644 index 000000000..85b0b7eb6 --- /dev/null +++ b/app/assets/stylesheets/elements/_button.scss @@ -0,0 +1,4 @@ +button { + transition: $transition; + cursor: pointer; +} \ No newline at end of file diff --git a/app/assets/stylesheets/elements/_elements.scss b/app/assets/stylesheets/elements/_elements.scss new file mode 100644 index 000000000..6e2c8285b --- /dev/null +++ b/app/assets/stylesheets/elements/_elements.scss @@ -0,0 +1,8 @@ +@import "body"; +@import "html"; +@import "link"; +@import "button"; +@import "strong"; +@import "svg"; +@import "table"; +@import "form"; diff --git a/app/assets/stylesheets/elements/_form.scss b/app/assets/stylesheets/elements/_form.scss new file mode 100644 index 000000000..530d73a15 --- /dev/null +++ b/app/assets/stylesheets/elements/_form.scss @@ -0,0 +1,101 @@ +input { + font-weight: 500; + font-size: rem(14px); + height: rem(48px); + line-height: rem(24px); + padding: rem(12px 16px); + border: 1px solid $grey-200-color; + border-radius: rem(8px); + transition: $transition; + width: 100%; + max-width: 100%; + color: $black-color; + font-family: $font-family; + + &:focus { + border-color: $black-color; + } + + &:disabled { + color: $grey-400-color; + border-color: $grey-200-color; + + &::placeholder { + color: $grey-400-color; + } + } +} + +textarea { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + padding: rem(12px 16px); + border: 1px solid $grey-200-color; + border-radius: rem(8px); + transition: $transition; + width: 100%; + max-width: 100%; + + &:focus { + border-color: $black-color; + } + + &:disabled { + color: $grey-400-color; + border-color: $grey-200-color; + + &::placeholder { + color: $grey-400-color; + } + } +} + +select { + font-weight: 500; + font-size: rem(14px); + height: rem(48px); + line-height: rem(24px); + padding: rem(12px 16px); + border: 1px solid $grey-200-color; + border-radius: rem(8px); + transition: $transition; + width: 100%; + max-width: 100%; + appearance: none; + background: transparent 97.5% center/10px 6px no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.47343 5.77219L0.218112 1.32775C-0.072704 1.02401 -0.072704 0.531547 0.218112 0.227806C0.508928 -0.0759353 0.980434 -0.0759353 1.27125 0.227806L5 4.12228L8.72875 0.227806C9.01957 -0.0759353 9.49107 -0.0759353 9.78189 0.227806C10.0727 0.531547 10.0727 1.02401 9.78189 1.32775L5.52657 5.77219C5.23575 6.07594 4.76425 6.07594 4.47343 5.77219Z' fill='%23212224'/%3E%3C/svg%3E%0A"); + + &:focus { + border-color: $black-color; + } + + &:disabled { + color: $grey-400-color; + border-color: $grey-200-color; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.47343 5.77219L0.218112 1.32775C-0.072704 1.02401 -0.072704 0.531547 0.218112 0.227806C0.508928 -0.0759353 0.980434 -0.0759353 1.27125 0.227806L5 4.12228L8.72875 0.227806C9.01957 -0.0759353 9.49107 -0.0759353 9.78189 0.227806C10.0727 0.531547 10.0727 1.02401 9.78189 1.32775L5.52657 5.77219C5.23575 6.07594 4.76425 6.07594 4.47343 5.77219Z' fill='%23E2E2E2'/%3E%3C/svg%3E%0A"); + + &::placeholder { + color: $grey-400-color; + } + } +} + +label { + padding-left: rem(16px); + margin-bottom: rem(8px); + display: block; + font-weight: 800; + font-size: rem(14px); + line-height: rem(24px); +} + +input:not([type="checkbox"]):not([type="radio"]).is-invalid, select.is-invalid, textarea.is-invalid { + border-color: $primary-orange-color; + background-color: $orange-light; + + & + span { + color: $primary-orange-color; + } +} + diff --git a/app/assets/stylesheets/elements/_html.scss b/app/assets/stylesheets/elements/_html.scss new file mode 100644 index 000000000..807237a4f --- /dev/null +++ b/app/assets/stylesheets/elements/_html.scss @@ -0,0 +1,3 @@ +html { + font-family: $font-family; +} \ No newline at end of file diff --git a/app/assets/stylesheets/elements/_link.scss b/app/assets/stylesheets/elements/_link.scss new file mode 100644 index 000000000..85fe52c10 --- /dev/null +++ b/app/assets/stylesheets/elements/_link.scss @@ -0,0 +1,4 @@ +a { + cursor: pointer; + transition: $transition; +} \ No newline at end of file diff --git a/app/assets/stylesheets/elements/_strong.scss b/app/assets/stylesheets/elements/_strong.scss new file mode 100644 index 000000000..c0d54083f --- /dev/null +++ b/app/assets/stylesheets/elements/_strong.scss @@ -0,0 +1,3 @@ +strong { + font-weight: 800; +} \ No newline at end of file diff --git a/app/assets/stylesheets/elements/_svg.scss b/app/assets/stylesheets/elements/_svg.scss new file mode 100644 index 000000000..b685bd108 --- /dev/null +++ b/app/assets/stylesheets/elements/_svg.scss @@ -0,0 +1,3 @@ +svg { + transition: $transition; +} \ No newline at end of file diff --git a/app/assets/stylesheets/elements/_table.scss b/app/assets/stylesheets/elements/_table.scss new file mode 100644 index 000000000..7891ed1e0 --- /dev/null +++ b/app/assets/stylesheets/elements/_table.scss @@ -0,0 +1,39 @@ +table { + border-collapse: collapse; + width: 100%; +} + +th, td { + height: rem(50px); + padding: rem(8px 16px); + font-weight: 500; + font-size: 14px; + line-height: rem(24px); + text-align: left; + + * { + vertical-align: middle; + } +} + +td { + border-right: 1px solid $grey-200-color; + border-bottom: 1px solid $grey-200-color; + + &:last-child { + border-right: 0; + } +} + +th { + background: $primary-green-color; + color: $white-color; + font-weight: 800; +} + +.table--black { + + th { + background: $black-color; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/generics/_normalize.scss b/app/assets/stylesheets/generics/_normalize.scss new file mode 100644 index 000000000..9454728b3 --- /dev/null +++ b/app/assets/stylesheets/generics/_normalize.scss @@ -0,0 +1,449 @@ +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers (opinionated). + */ + +body { + margin: 0; +} + +/** + * Add the correct display in IE 9-. + */ + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + * 1. Add the correct display in IE. + */ + +figcaption, +figure, +main { /* 1 */ + display: block; +} + +/** + * Add the correct margin in IE 8. + */ + +figure { + margin: 1em 40px; +} + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ + +a { + background-color: transparent; /* 1 */ + -webkit-text-decoration-skip: objects; /* 2 */ +} + +/** + * 1. Remove the bottom border in Chrome 57- and Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ + +b, +strong { + font-weight: inherit; +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font style in Android 4.3-. + */ + +dfn { + font-style: italic; +} + +/** + * Add the correct background and color in IE 9-. + */ + +mark { + background-color: #ff0; + color: #000; +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +audio, +video { + display: inline-block; +} + +/** + * Add the correct display in iOS 4-7. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Remove the border on images inside links in IE 10-. + */ + +img { + border-style: none; +} + +/** + * Hide the overflow in IE. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ + +button, +html [type="button"], /* 1 */ +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 2 */ +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Remove the default vertical scrollbar in IE. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in IE 9-. + * 1. Add the correct display in Edge, IE, and Firefox. + */ + +details, /* 1 */ +menu { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Scripting + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +canvas { + display: inline-block; +} + +/** + * Add the correct display in IE. + */ + +template { + display: none; +} + +/* Hidden + ========================================================================== */ + +/** + * Add the correct display in IE 10-. + */ + +[hidden] { + display: none; +} + +@import "reset"; \ No newline at end of file diff --git a/app/assets/stylesheets/generics/_reset.scss b/app/assets/stylesheets/generics/_reset.scss new file mode 100644 index 000000000..a527bb50b --- /dev/null +++ b/app/assets/stylesheets/generics/_reset.scss @@ -0,0 +1,81 @@ +/* ========================================================================== + #RESET + ========================================================================== */ + +/** + * A very simple reset that sits on top of Normalize.css. + */ + +body, +h1, h2, h3, h4, h5, h6, +blockquote, p, pre, +dl, dd, ol, ul, +figure, +hr, +fieldset, legend { + //margin: 0; + //padding: 0; +} + + + +/** + * Remove trailing margins from nested lists. + */ + +li > { + + ol, + ul { + margin-bottom: 0; + } + +} + + + +/** + * Remove default table spacing. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + + + +/** + * 1. Reset Chrome and Firefox behaviour which sets a `min-width: min-content;` + * on fieldsets. + */ + +fieldset { + min-width: 0; /* [1] */ + border: 0; +} + +/* ========================================================================== + #BOX-SIZING + ========================================================================== */ + +html { + box-sizing: border-box; +} + +* { + + &, + &:before, + &:after { + box-sizing: inherit; + } + +} + + +input, textarea, select, button { + &:focus { + outline: none; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/histories.css b/app/assets/stylesheets/histories.css deleted file mode 100644 index afad32db0..000000000 --- a/app/assets/stylesheets/histories.css +++ /dev/null @@ -1,4 +0,0 @@ -/* - Place all the styles related to the matching controller here. - They will automatically be included in application.css. -*/ diff --git a/app/assets/stylesheets/mixins/_btn.scss b/app/assets/stylesheets/mixins/_btn.scss new file mode 100644 index 000000000..164b16372 --- /dev/null +++ b/app/assets/stylesheets/mixins/_btn.scss @@ -0,0 +1,106 @@ +@mixin btn { + font-weight: 500; + font-size: rem(16px); + line-height: rem(32px); + text-align: center; + display: inline-block; + border-radius: rem(32px); + padding: rem(0 16px); + text-decoration: none; + white-space: nowrap; + color: $white-color; + border: none; + font-family: $font-family; + + &:disabled { + pointer-events: none; + background-color: $grey-200-color; + border-color: $grey-200-color; + color: $grey-400-color; + } +} + +@mixin btn-orange { + @include btn; + background: $primary-orange-color; + border: 1px solid $primary-orange-color; + + &:hover { + background: $white-color; + color: $primary-orange-color; + } +} +@mixin btn-orange-light { + @include btn; + background: $orange-light; + border: 1px solid $primary-orange-color; + color: $primary-orange-color; + + &:hover { + background: $white-color; + } +} + +@mixin btn-blue { + @include btn; + background: $primary-blue-color; + border: 1px solid $white-color; + + &:hover { + background: $white-color; + color: $primary-blue-color; + border: 1px solid $primary-blue-color; + } +} + +@mixin btn-blue-secondary { + @include btn; + background: $secondary-blue-color; + border: 1px solid $secondary-blue-color; + &:hover { + background: $white-color; + color: $secondary-blue-color; + + } +} + +@mixin btn-black { + @include btn; + background: $black-color; + + &:hover { + background: $white-color; + color: $black-color; + } +} + +@mixin btn-green { + @include btn; + background: $primary-green-color; + border: 1px solid $primary-green-color; + + &:hover { + background: $white-color; + color: $primary-green-color; + } +} + +@mixin btn-green-light { + @include btn; + color: $primary-green-color; + background-color: $green-light; + border: 1px solid $primary-green-color; + + &:hover { + background: $white-color; + } +} + +@mixin btn-red { + @include btn; + background: $error-color; + + &:hover { + background: $black-color; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/mixins/_container.scss b/app/assets/stylesheets/mixins/_container.scss new file mode 100644 index 000000000..bde810eeb --- /dev/null +++ b/app/assets/stylesheets/mixins/_container.scss @@ -0,0 +1,74 @@ +@mixin min($bp, $max: "null", $device: "screen") { + @if $max == "null" { + @media only #{$device} and (min-width: #{$bp}) { + @content; + } + } @else { + @media only #{$device} and (min-width: #{$bp}) and (max-width: #{$max}) { + @content; + } + } +} + +@function bp($bp) { + @return map-get($breakpoints, $bp); +} + +$breakpoints: ( + na: 0px, + // For BS grid + xs: 320px, + // Smartphone + sm: 1140px, + // Tablets + md: 1250px, + // Tablets Landscape and small desktops + lg: 1410px, + // Desktops + xl: 1800px, + // Large Desktop +); + +@function container($container-size, $true-val: false) { + @return map-get($container-sizes, $container-size); +} + +$container-sizes: ( + sm: map-get($breakpoints, sm) - 30px, + md: map-get($breakpoints, md) - 40px, + lg: map-get($breakpoints, lg) - 50px, + xl: map-get($breakpoints, xl) - 400px +); + +.o-container { + padding-right: 1rem; + padding-left: 1rem; + + &:not(.is-fluid) { + margin: 0 auto; + + @each $bp, $container-size in $container-sizes { + @include min(#{bp(#{$bp})}) { + width: 100%; + max-width: container(#{$bp}); + } + } + } +} + +@each $bp, $container-size in $container-sizes { + .o-container-#{$bp} { + margin: 0 auto; + padding-right: 1rem; + padding-left: 1rem; + width: 100%; + + $i: index($container-sizes, $bp $container-size); + + @for $j from $i through length($container-sizes) { + @include min(#{bp(nth(nth($container-sizes, $j), 1))}) { + max-width: container(#{nth(nth($container-sizes, $j), 1)}); + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/mixins/_mixins.scss b/app/assets/stylesheets/mixins/_mixins.scss new file mode 100644 index 000000000..08e7107d8 --- /dev/null +++ b/app/assets/stylesheets/mixins/_mixins.scss @@ -0,0 +1,12 @@ +@import "container"; +@import "btn"; + +@mixin box-shadow { + background-color: $white-color; + box-shadow: 0 0 48px rgba(0, 0, 0, 0.12); +} + +@mixin box-shadow-light { + background-color: $white-color; + box-shadow: 0 0 48px rgba(0, 0, 0, 0.06); +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_arrow.scss b/app/assets/stylesheets/objects/_arrow.scss new file mode 100644 index 000000000..a4d682242 --- /dev/null +++ b/app/assets/stylesheets/objects/_arrow.scss @@ -0,0 +1,22 @@ +.o-arrow { + width: 1.2rem; + height: 1.2rem; + background: url("arrow-dwn.svg") center center/16px 16px no-repeat; + cursor: pointer; + display: inline-block; + transition: $transition; + + @include mq(desktop) { + display: none; + } + + &.is-active { + transform: rotate(180deg); + } +} + +.o-arrow--visible-on-desktop { + @include mq(desktop) { + display: inline-block; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_card.scss b/app/assets/stylesheets/objects/_card.scss new file mode 100644 index 000000000..3423eae8a --- /dev/null +++ b/app/assets/stylesheets/objects/_card.scss @@ -0,0 +1,52 @@ +.o-card { + border-radius: rem(8px); + background-color: $white-color; + box-shadow: 0px 0px 48px 0px rgba(0, 0, 0, 0.06); + padding: rem(24px 24px 48px 24px); + align-self: self-start; + position: relative; + @include mq(tablet){ + padding: rem(30px 48px 48px 48px); + } +} +.o-card--no-shadow { + background-color: transparent; + box-shadow: unset; + @include mq(tablet){ + padding: rem(0 48px 0 48px); + } + @include mq(laptop){ + padding: rem(40px 48px 48px 48px); + } +} +.o-card__title{ + color: $black-color; + font-size: 24px; + font-weight: 900; +} +.c-account{ + .o-card{ + height: 100%; + padding: rem(36px 24px 48px 24px); + @include mq(tablet){ + padding: rem(40px 48px 48px 48px); + } + } +} +.c-account--createnew { + .o-card { + height: auto; + &.o-card--personaldata{ + margin-bottom: rem(50px); + @include mq(laptop){ + margin-bottom: rem(80px); + } + } + } +} +.c-account--changepass{ + .o-card{ + width:640px; + max-width: 100%; + } + } \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_container.scss b/app/assets/stylesheets/objects/_container.scss new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/stylesheets/objects/_form.scss b/app/assets/stylesheets/objects/_form.scss new file mode 100644 index 000000000..7a259e945 --- /dev/null +++ b/app/assets/stylesheets/objects/_form.scss @@ -0,0 +1,157 @@ +.o-input-desc { + font-weight: 500; + font-size: rem(14px); + line-height: rem(24px); + margin-top: rem(8px); + padding-left: rem(16px); + color: $grey-400-color; + display: inline-block; +} +.o-checkbox-container { + display: block; + position: relative; + cursor: pointer; + user-select: none; + margin-bottom: 0; +} +.o-checkbox-text{ + color: $black-color; + font-size: rem(14px); + font-weight: 500; + line-height: rem(24px); + margin-left: 1rem; + white-space: nowrap; +} +.o-checkbox-sample { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.o-checkmark-sample { + position: absolute; + top: rem(3px); + left: 0; + height: rem(18px); + width: rem(18px); + border-radius: rem(4px); + border: 1px solid $grey-200-color; + background: $white-color; +} +.o-checkbox-container:hover input ~ .o-checkmark-sample { + background-color: $grey-100-color; +} +.o-checkbox-sample:checked ~ .o-checkmark-sample { + background-color: $white-color; +} + +.o-checkmark-sample:after { + content: ""; + position: absolute; + display: none; + left: rem(6px); + top: rem(2px); + width: rem(5.5px); + height: rem(10px); + border: solid $grey-400-color; + border-width: 0 3px 3px 0; + transform: rotate(45deg); +} + +.o-checkbox-sample:checked ~ .o-checkmark-sample:after { + display: block; +} +.o-checkbox-container--grey{ + .o-checkbox-sample:checked ~ .o-checkmark-sample { + background-color: $grey-400-color; + } + .o-checkmark-sample:after { + border: solid $white-color; + border-width: 0 3px 3px 0; + } +} + +.o-checkbox-container--green{ + .o-checkmark-sample:after { + border: solid $primary-green-color; + border-width: 0 3px 3px 0; + } +} + +// Custom Checkbox +.o-checkbox { + display: inline-block; + height: rem(19px); + position: relative; + width: rem(33px); + + &.u-disabled { + opacity: 0.6; + } +} + +.o-checkbox input { + display:none; +} + +.o-checkbox__slider { + background-color: $grey-400-color; + bottom: 0; + cursor: pointer; + left: 0; + position: absolute; + right: 0; + top: 0; + transition: .4s; +} + +.o-checkbox__slider:before { + background: $gradient-grey; + bottom: 2px; + content: ""; + height: rem(15px); + left: 2px; + position: absolute; + transition: .4s; + width: rem(15px); +} + +input:checked + .o-checkbox__slider { + background-color: $primary-green-color; +} + +input:checked + .o-checkbox__slider:before { + transform: translateX(14px); +} + +.o-checkbox__slider.round { + border-radius: 34px; +} + +.o-checkbox__slider.round:before { + border-radius: 50%; +} +// <-- Custom checkbox + +.o-input { + position: relative; +} + +.o-input-copy { + position: absolute; + right: rem(16px); + top: 50%; + transform: translateY(-50%); + width: rem(24px); + height: rem(24px); + background: transparent center center/75% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='17' height='20' viewBox='0 0 17 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.3337 0.833313H2.33366C1.41283 0.833313 0.666992 1.57915 0.666992 2.49998V14.1666H2.33366V2.49998H12.3337V0.833313ZM14.8337 4.16665H5.66699C4.74616 4.16665 4.00033 4.91248 4.00033 5.83331V17.5C4.00033 18.4208 4.74616 19.1666 5.66699 19.1666H14.8337C15.7545 19.1666 16.5003 18.4208 16.5003 17.5V5.83331C16.5003 4.91248 15.7545 4.16665 14.8337 4.16665ZM14.8337 17.5H5.66699V5.83331H14.8337V17.5Z' fill='%2348A23F'/%3E%3C/svg%3E%0A"); + transition: $transition; + border: none; + + &:hover { + filter: grayscale(1) opacity(0.9); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_grid.scss b/app/assets/stylesheets/objects/_grid.scss new file mode 100644 index 000000000..144661d77 --- /dev/null +++ b/app/assets/stylesheets/objects/_grid.scss @@ -0,0 +1,101 @@ +.o-grid { + display: grid; + grid-template-columns: repeat(1, 1fr); + row-gap: rem(24px); + column-gap: rem(20px); + + @include mq(desktop) { + column-gap: rem(32px); + row-gap: rem(32px); + } + + @include mq(wide) { + column-gap: rem(40px); + row-gap: rem(40px); + } + + @include mq(ultra) { + column-gap: rem(48px); + row-gap: rem(48px); + } +} + +.o-grid--two-col { + @include mq(tablet) { + grid-template-columns: repeat(2, 1fr); + } +} + +.o-grid--two-col-laptop { + @include mq(laptop) { + grid-template-columns: repeat(2, 1fr); + } +} + +.o-grid--two-col-desktop { + @include mq(desktop) { + grid-template-columns: repeat(2, 1fr); + } +} + +.o-grid--three-col { + @include mq(tablet) { + grid-template-columns: repeat(3, 1fr); + } +} + +.o-grid--sep { + row-gap: rem(48px); +} + +.o-grid--wishlist { + row-gap: rem(24px); + + @include mq(tablet) { + row-gap: rem(40px); + } + + @include mq(laptop) { + row-gap: 0; + } +} + +.o-grid--auction { + column-gap: rem(24px); + + @include mq(tablet) { + column-gap: rem(50px); + } +} + +.o-grid--auction-cards { + column-gap: rem(16px); + + @include mq(laptop) { + column-gap: rem(48px); + } +} + +.c-account, +.c-login { + .o-grid--two-col { + grid-template-columns: repeat(1, 1fr); + + @include mq(laptop) { + grid-template-columns: repeat(2, 1fr); + } + } + + .o-grid--sep { + row-gap: rem(20px); + + @include mq(tablet) { + row-gap: rem(40px); + } + + @include mq(laptop) { + row-gap: rem(50px); + column-gap: rem(50px); + } + } +} diff --git a/app/assets/stylesheets/objects/_icons.scss b/app/assets/stylesheets/objects/_icons.scss new file mode 100644 index 000000000..cee261e08 --- /dev/null +++ b/app/assets/stylesheets/objects/_icons.scss @@ -0,0 +1,233 @@ +.o-logo-icon{ + display: inline-block; + vertical-align: middle; + width: rem(33px); + height: rem(33px); + background: center center/100% no-repeat; + background-image:url('data:image/svg+xml,'); +} +.o-io-icon { + display: inline-block; + vertical-align: middle; + width: rem(15px); + height: rem(15px); + background: center center/100% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='13' height='10' viewBox='0 0 13 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.72869 0.772727V9.5H0.883523V0.772727H2.72869ZM12.2667 5.13636C12.2667 6.08807 12.0863 6.89773 11.7255 7.56534C11.3675 8.23295 10.8789 8.7429 10.2596 9.09517C9.64311 9.4446 8.94993 9.61932 8.18004 9.61932C7.40447 9.61932 6.70845 9.44318 6.09197 9.09091C5.4755 8.73864 4.98828 8.22869 4.63033 7.56108C4.27237 6.89347 4.09339 6.08523 4.09339 5.13636C4.09339 4.18466 4.27237 3.375 4.63033 2.70739C4.98828 2.03977 5.4755 1.53125 6.09197 1.18182C6.70845 0.829545 7.40447 0.653409 8.18004 0.653409C8.94993 0.653409 9.64311 0.829545 10.2596 1.18182C10.8789 1.53125 11.3675 2.03977 11.7255 2.70739C12.0863 3.375 12.2667 4.18466 12.2667 5.13636ZM10.396 5.13636C10.396 4.51989 10.3036 4 10.119 3.5767C9.93714 3.15341 9.68004 2.83239 9.34766 2.61364C9.01527 2.39489 8.62607 2.28551 8.18004 2.28551C7.73402 2.28551 7.34482 2.39489 7.01243 2.61364C6.68004 2.83239 6.42152 3.15341 6.23686 3.5767C6.05504 4 5.96413 4.51989 5.96413 5.13636C5.96413 5.75284 6.05504 6.27273 6.23686 6.69602C6.42152 7.11932 6.68004 7.44034 7.01243 7.65909C7.34482 7.87784 7.73402 7.98722 8.18004 7.98722C8.62607 7.98722 9.01527 7.87784 9.34766 7.65909C9.68004 7.44034 9.93714 7.11932 10.119 6.69602C10.3036 6.27273 10.396 5.75284 10.396 5.13636Z' fill='%230085CA'/%3E%3C/svg%3E%0A"); +} + +.o-po-icon { + display: inline-block; + vertical-align: middle; + width: rem(18px); + height: rem(15px); + background: center center/100% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='16' height='10' viewBox='0 0 16 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.180398 9.5V0.772727H3.62358C4.28551 0.772727 4.84943 0.899148 5.31534 1.15199C5.78125 1.40199 6.13636 1.75 6.38068 2.19602C6.62784 2.6392 6.75142 3.15057 6.75142 3.73011C6.75142 4.30966 6.62642 4.82102 6.37642 5.2642C6.12642 5.70739 5.7642 6.05256 5.28977 6.29972C4.81818 6.54687 4.24716 6.67045 3.5767 6.67045H1.3821V5.19176H3.27841C3.63352 5.19176 3.92614 5.13068 4.15625 5.00852C4.3892 4.88352 4.5625 4.71165 4.67614 4.4929C4.79261 4.27131 4.85085 4.01705 4.85085 3.73011C4.85085 3.44034 4.79261 3.1875 4.67614 2.97159C4.5625 2.75284 4.3892 2.58381 4.15625 2.46449C3.9233 2.34233 3.62784 2.28125 3.26989 2.28125H2.02557V9.5H0.180398ZM15.9698 5.13636C15.9698 6.08807 15.7894 6.89773 15.4286 7.56534C15.0707 8.23295 14.582 8.7429 13.9627 9.09517C13.3462 9.4446 12.6531 9.61932 11.8832 9.61932C11.1076 9.61932 10.4116 9.44318 9.7951 9.09091C9.17862 8.73864 8.69141 8.22869 8.33345 7.56108C7.9755 6.89347 7.79652 6.08523 7.79652 5.13636C7.79652 4.18466 7.9755 3.375 8.33345 2.70739C8.69141 2.03977 9.17862 1.53125 9.7951 1.18182C10.4116 0.829545 11.1076 0.653409 11.8832 0.653409C12.6531 0.653409 13.3462 0.829545 13.9627 1.18182C14.582 1.53125 15.0707 2.03977 15.4286 2.70739C15.7894 3.375 15.9698 4.18466 15.9698 5.13636ZM14.0991 5.13636C14.0991 4.51989 14.0067 4 13.8221 3.5767C13.6403 3.15341 13.3832 2.83239 13.0508 2.61364C12.7184 2.39489 12.3292 2.28551 11.8832 2.28551C11.4371 2.28551 11.0479 2.39489 10.7156 2.61364C10.3832 2.83239 10.1246 3.15341 9.93999 3.5767C9.75817 4 9.66726 4.51989 9.66726 5.13636C9.66726 5.75284 9.75817 6.27273 9.93999 6.69602C10.1246 7.11932 10.3832 7.44034 10.7156 7.65909C11.0479 7.87784 11.4371 7.98722 11.8832 7.98722C12.3292 7.98722 12.7184 7.87784 13.0508 7.65909C13.3832 7.44034 13.6403 7.11932 13.8221 6.69602C14.0067 6.27273 14.0991 5.75284 14.0991 5.13636Z' fill='%23FF6E00'/%3E%3C/svg%3E%0A"); +} + +.o-io-icon--bg-blue { + background-color: $blue-light; + width: rem(32px); + min-width: rem(32px); + height: rem(32px); + display: flex; + align-items: center; + justify-content: center; + border-radius: rem(16px); + background-size: rem(15px); +} + +.o-io-icon--bg-orange { + background-color: $orange-light; + width: rem(32px); + min-width: rem(32px); + height: rem(32px); + display: flex; + align-items: center; + justify-content: center; + border-radius: rem(16px); + background-size: rem(15px); +} + +.o-icon--sm { + height: rem(20px); +} + +.o-trophy-icon { + display: inline-block; + vertical-align: middle; + width: rem(24px); + height: rem(24px); + min-width: rem(24px); + min-height: rem(24px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_657_13218)'%3E%3Cpath d='M9.9987 15.25C6.8737 15.25 4.33203 12.7083 4.33203 9.62498V1.04165C4.33203 0.583313 4.70703 0.208313 5.16536 0.208313H14.832C15.2904 0.208313 15.6654 0.583313 15.6654 1.04165V9.62498C15.6654 12.75 13.1237 15.25 9.9987 15.25ZM5.9987 1.83331V9.62498C5.9987 11.8333 7.79036 13.625 9.9987 13.625C12.207 13.625 13.9987 11.8333 13.9987 9.62498V1.83331H5.9987Z' fill='%2348A23F'/%3E%3Cpath d='M10.0013 19.7917C9.54297 19.7917 9.16797 19.4167 9.16797 18.9583V14.4583C9.16797 14 9.54297 13.625 10.0013 13.625C10.4596 13.625 10.8346 14 10.8346 14.4583V19C10.8346 19.4167 10.4596 19.7917 10.0013 19.7917Z' fill='%2348A23F'/%3E%3Cpath d='M13.5417 19.7917H6.45833C6 19.7917 5.625 19.4167 5.625 18.9583C5.625 18.5 6 18.125 6.45833 18.125H13.5417C14 18.125 14.375 18.5 14.375 18.9583C14.375 19.4167 14 19.7917 13.5417 19.7917Z' fill='%2348A23F'/%3E%3Cpath d='M16.2904 9.16669C15.6237 9.16669 14.957 8.95835 14.3737 8.58335C14.1237 8.41669 13.9987 8.16669 13.9987 7.91669V3.41669C13.9987 3.12502 14.1237 2.87502 14.3737 2.75002C14.957 2.37502 15.582 2.16669 16.2904 2.16669C18.207 2.16669 19.7904 3.75002 19.7904 5.66669C19.7904 7.58335 18.207 9.16669 16.2904 9.16669ZM15.6654 7.41669C15.8737 7.50002 16.082 7.54169 16.2904 7.54169C17.332 7.54169 18.1654 6.70835 18.1654 5.66669C18.1654 4.62502 17.332 3.79169 16.2904 3.79169C16.082 3.79169 15.832 3.83335 15.6654 3.91669V7.41669ZM3.70703 9.16669C1.79036 9.16669 0.207031 7.58335 0.207031 5.66669C0.207031 3.75002 1.79036 2.16669 3.70703 2.16669C4.3737 2.16669 5.04036 2.37502 5.6237 2.75002C5.8737 2.91669 5.9987 3.16669 5.9987 3.41669V7.91669C5.9987 8.20835 5.8737 8.45835 5.6237 8.58335C5.04036 8.95835 4.3737 9.16669 3.70703 9.16669ZM3.70703 3.79169C2.66536 3.79169 1.83203 4.62502 1.83203 5.66669C1.83203 6.70835 2.66536 7.54169 3.70703 7.54169C3.91536 7.54169 4.16536 7.50002 4.33203 7.41669V3.91669C4.16536 3.83335 3.91536 3.79169 3.70703 3.79169Z' fill='%2348A23F'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_657_13218'%3E%3Crect width='20' height='20' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"); +} + +.o-hammer-icon { + display: inline-block; + vertical-align: middle; + width: rem(24px); + height: rem(24px); + min-width: rem(24px); + min-height: rem(24px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='19' height='19' viewBox='0 0 19 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.7 16.3L11.9 9.50001L14.7 6.70001C15.5 5.90001 15.5 4.50001 14.7 3.70001L12.3 1.30001C11.5 0.500012 10.1 0.500012 9.3 1.30001L2.8 7.80001C2 8.60001 2 10 2.8 10.8L5.2 13.2C5.6 13.6 6.2 13.8 6.7 13.8C7.2 13.8 7.8 13.6 8.2 13.2L10.5 10.9L17.3 17.7C17.5 17.9 17.8 18 18 18C18.2 18 18.5 17.9 18.7 17.7C19.1 17.3 19.1 16.7 18.7 16.3ZM6.8 11.8C6.8 11.8 6.7 11.8 6.6 11.8L4.2 9.40001C4.2 9.40001 4.2 9.30001 4.2 9.20001L10.7 2.70001H10.8H10.9L13.3 5.10001C13.3 5.10001 13.3 5.20001 13.3 5.30001L6.8 11.8ZM13 19H1C0.4 19 0 18.6 0 18C0 17.4 0.4 17 1 17H13C13.6 17 14 17.4 14 18C14 18.6 13.6 19 13 19Z' fill='%23FF6E00'/%3E%3C/svg%3E%0A"); +} + +.o-edit-icon { + display: inline-block; + vertical-align: middle; + width: rem(16px); + height: rem(16px); + min-width: rem(16px); + min-height: rem(16px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.9335 2.89333L9.10686 1.06666C8.86846 0.842727 8.55606 0.714236 8.22908 0.705632C7.90211 0.697027 7.58338 0.80891 7.33353 1.02L1.33353 7.02C1.11804 7.23731 0.983867 7.52213 0.953531 7.82666L0.666864 10.6067C0.657884 10.7043 0.670554 10.8027 0.703972 10.8949C0.73739 10.9871 0.790733 11.0708 0.860198 11.14C0.922491 11.2018 0.996369 11.2507 1.07759 11.2838C1.15882 11.317 1.24579 11.3338 1.33353 11.3333H1.39353L4.17353 11.08C4.47806 11.0497 4.76289 10.9155 4.9802 10.7L10.9802 4.7C11.2131 4.45397 11.3389 4.12567 11.3302 3.78702C11.3214 3.44837 11.1788 3.127 10.9335 2.89333ZM4.05353 9.74666L2.05353 9.93333L2.23353 7.93333L6.0002 4.21333L7.8002 6.01333L4.05353 9.74666ZM8.66686 5.12L6.8802 3.33333L8.1802 2L10.0002 3.82L8.66686 5.12Z' fill='black'/%3E%3C/svg%3E%0A"); +} + +.c-btn--icon:hover .o-edit-icon { + background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.9335 2.89333L9.10686 1.06666C8.86846 0.842727 8.55606 0.714236 8.22908 0.705632C7.90211 0.697027 7.58338 0.80891 7.33353 1.02L1.33353 7.02C1.11804 7.23731 0.983867 7.52213 0.953531 7.82666L0.666864 10.6067C0.657884 10.7043 0.670554 10.8027 0.703972 10.8949C0.73739 10.9871 0.790733 11.0708 0.860198 11.14C0.922491 11.2018 0.996369 11.2507 1.07759 11.2838C1.15882 11.317 1.24579 11.3338 1.33353 11.3333H1.39353L4.17353 11.08C4.47806 11.0497 4.76289 10.9155 4.9802 10.7L10.9802 4.7C11.2131 4.45397 11.3389 4.12567 11.3302 3.78702C11.3214 3.44837 11.1788 3.127 10.9335 2.89333ZM4.05353 9.74666L2.05353 9.93333L2.23353 7.93333L6.0002 4.21333L7.8002 6.01333L4.05353 9.74666ZM8.66686 5.12L6.8802 3.33333L8.1802 2L10.0002 3.82L8.66686 5.12Z' fill='%2348A23F'/%3E%3C/svg%3E%0A"); +} +.o-fp-icon{ + display: inline-block; + vertical-align: middle; + width: rem(25px); + height: rem(25px); + min-width: rem(25px); + min-height: rem(25px); + background: center center/85% no-repeat; + background-image: url('data:image/svg+xml,'); +} +.o-bill-icon{ + display: inline-block; + vertical-align: middle; + width: rem(25px); + height: rem(25px); + min-width: rem(25px); + min-height: rem(25px); + background: center center/85% no-repeat; + background-image: url('data:image/svg+xml,'); +} +.o-edit-icon--green { + display: inline-block; + vertical-align: middle; + width: rem(18px); + height: rem(18px); + min-width: rem(18px); + min-height: rem(18px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.9335 2.89333L9.10686 1.06666C8.86846 0.842727 8.55606 0.714236 8.22908 0.705632C7.90211 0.697027 7.58338 0.80891 7.33353 1.02L1.33353 7.02C1.11804 7.23731 0.983867 7.52213 0.953531 7.82666L0.666864 10.6067C0.657884 10.7043 0.670554 10.8027 0.703972 10.8949C0.73739 10.9871 0.790733 11.0708 0.860198 11.14C0.922491 11.2018 0.996369 11.2507 1.07759 11.2838C1.15882 11.317 1.24579 11.3338 1.33353 11.3333H1.39353L4.17353 11.08C4.47806 11.0497 4.76289 10.9155 4.9802 10.7L10.9802 4.7C11.2131 4.45397 11.3389 4.12567 11.3302 3.78702C11.3214 3.44837 11.1788 3.127 10.9335 2.89333ZM4.05353 9.74666L2.05353 9.93333L2.23353 7.93333L6.0002 4.21333L7.8002 6.01333L4.05353 9.74666ZM8.66686 5.12L6.8802 3.33333L8.1802 2L10.0002 3.82L8.66686 5.12Z' fill='%2348A23F'/%3E%3C/svg%3E%0A"); +} + +.o-delete-icon { + display: inline-block; + vertical-align: middle; + width: rem(16px); + height: rem(16px); + min-width: rem(16px); + min-height: rem(16px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='12' viewBox='0 0 10 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 2.54351C0 2.69147 0.0587754 2.83337 0.163396 2.93799C0.268017 3.04261 0.409914 3.10138 0.55787 3.10138H0.8554V9.69354C0.855892 10.1803 1.04945 10.647 1.3936 10.9912C1.73776 11.3354 2.20441 11.5291 2.69116 11.5297H7.30884C7.79559 11.5291 8.26224 11.3354 8.6064 10.9912C8.95055 10.647 9.14411 10.1803 9.1446 9.69354V3.10138H9.44213C9.59009 3.10138 9.73198 3.04261 9.8366 2.93799C9.94122 2.83337 10 2.69147 10 2.54351C10 2.39556 9.94122 2.25366 9.8366 2.14904C9.73198 2.04442 9.59009 1.98564 9.44213 1.98564H7.48959V1.40992C7.48959 0.632624 6.85733 0 6.08004 0H3.91996C3.14267 0 2.51041 0.632252 2.51041 1.40992V1.98527H0.55787C0.409914 1.98527 0.268017 2.04405 0.163396 2.14867C0.0587754 2.25329 0 2.39556 0 2.54351ZM3.62615 1.41029C3.62615 1.24777 3.75781 1.11611 3.91996 1.11611H6.08004C6.24219 1.11611 6.37385 1.24777 6.37385 1.41029V1.98564H3.62578V1.40992L3.62615 1.41029ZM1.97114 3.10138H8.02886V9.69354C8.02866 9.88448 7.95275 10.0675 7.81778 10.2026C7.6828 10.3376 7.49977 10.4136 7.30884 10.4139H2.69116C2.50023 10.4136 2.3172 10.3376 2.18222 10.2026C2.04725 10.0675 1.97134 9.88448 1.97114 9.69354V3.10138Z' fill='black'/%3E%3Cpath d='M3.61256 9.38725C3.76051 9.38725 3.90241 9.32847 4.00703 9.22385C4.11165 9.11923 4.17043 8.97733 4.17043 8.82938V4.68775C4.17043 4.5398 4.11165 4.3979 4.00703 4.29328C3.90241 4.18866 3.76051 4.12988 3.61256 4.12988C3.4646 4.12988 3.3227 4.18866 3.21808 4.29328C3.11346 4.3979 3.05469 4.5398 3.05469 4.68775V8.82938C3.05469 8.97733 3.11346 9.11923 3.21808 9.22385C3.3227 9.32847 3.4646 9.38725 3.61256 9.38725ZM6.38554 9.38725C6.5335 9.38725 6.67539 9.32847 6.78001 9.22385C6.88463 9.11923 6.94341 8.97733 6.94341 8.82938V4.68775C6.94341 4.5398 6.88463 4.3979 6.78001 4.29328C6.67539 4.18866 6.5335 4.12988 6.38554 4.12988C6.23758 4.12988 6.09569 4.18866 5.99107 4.29328C5.88645 4.3979 5.82767 4.5398 5.82767 4.68775V8.82938C5.82767 8.97733 5.88645 9.11923 5.99107 9.22385C6.09569 9.32847 6.23758 9.38725 6.38554 9.38725Z' fill='black'/%3E%3C/svg%3E%0A"); +} + +.c-btn--icon:hover .o-delete-icon { + background-image: url("data:image/svg+xml,%3Csvg width='10' height='12' viewBox='0 0 10 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 2.54351C0 2.69147 0.0587754 2.83337 0.163396 2.93799C0.268017 3.04261 0.409914 3.10138 0.55787 3.10138H0.8554V9.69354C0.855892 10.1803 1.04945 10.647 1.3936 10.9912C1.73776 11.3354 2.20441 11.5291 2.69116 11.5297H7.30884C7.79559 11.5291 8.26224 11.3354 8.6064 10.9912C8.95055 10.647 9.14411 10.1803 9.1446 9.69354V3.10138H9.44213C9.59009 3.10138 9.73198 3.04261 9.8366 2.93799C9.94122 2.83337 10 2.69147 10 2.54351C10 2.39556 9.94122 2.25366 9.8366 2.14904C9.73198 2.04442 9.59009 1.98564 9.44213 1.98564H7.48959V1.40992C7.48959 0.632624 6.85733 0 6.08004 0H3.91996C3.14267 0 2.51041 0.632252 2.51041 1.40992V1.98527H0.55787C0.409914 1.98527 0.268017 2.04405 0.163396 2.14867C0.0587754 2.25329 0 2.39556 0 2.54351ZM3.62615 1.41029C3.62615 1.24777 3.75781 1.11611 3.91996 1.11611H6.08004C6.24219 1.11611 6.37385 1.24777 6.37385 1.41029V1.98564H3.62578V1.40992L3.62615 1.41029ZM1.97114 3.10138H8.02886V9.69354C8.02866 9.88448 7.95275 10.0675 7.81778 10.2026C7.6828 10.3376 7.49977 10.4136 7.30884 10.4139H2.69116C2.50023 10.4136 2.3172 10.3376 2.18222 10.2026C2.04725 10.0675 1.97134 9.88448 1.97114 9.69354V3.10138Z' fill='%2348A23F'/%3E%3Cpath d='M3.61256 9.38725C3.76051 9.38725 3.90241 9.32847 4.00703 9.22385C4.11165 9.11923 4.17043 8.97733 4.17043 8.82938V4.68775C4.17043 4.5398 4.11165 4.3979 4.00703 4.29328C3.90241 4.18866 3.76051 4.12988 3.61256 4.12988C3.4646 4.12988 3.3227 4.18866 3.21808 4.29328C3.11346 4.3979 3.05469 4.5398 3.05469 4.68775V8.82938C3.05469 8.97733 3.11346 9.11923 3.21808 9.22385C3.3227 9.32847 3.4646 9.38725 3.61256 9.38725ZM6.38554 9.38725C6.5335 9.38725 6.67539 9.32847 6.78001 9.22385C6.88463 9.11923 6.94341 8.97733 6.94341 8.82938V4.68775C6.94341 4.5398 6.88463 4.3979 6.78001 4.29328C6.67539 4.18866 6.5335 4.12988 6.38554 4.12988C6.23758 4.12988 6.09569 4.18866 5.99107 4.29328C5.88645 4.3979 5.82767 4.5398 5.82767 4.68775V8.82938C5.82767 8.97733 5.88645 9.11923 5.99107 9.22385C6.09569 9.32847 6.23758 9.38725 6.38554 9.38725Z' fill='%2348A23F'/%3E%3C/svg%3E%0A"); +} + +.o-delete-icon--red { + display: inline-block; + vertical-align: middle; + width: rem(16px); + height: rem(16px); + min-width: rem(16px); + min-height: rem(16px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='10' height='12' viewBox='0 0 10 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 2.54351C0 2.69147 0.0587754 2.83337 0.163396 2.93799C0.268017 3.04261 0.409914 3.10138 0.55787 3.10138H0.8554V9.69354C0.855892 10.1803 1.04945 10.647 1.3936 10.9912C1.73776 11.3354 2.20441 11.5291 2.69116 11.5297H7.30884C7.79559 11.5291 8.26224 11.3354 8.6064 10.9912C8.95055 10.647 9.14411 10.1803 9.1446 9.69354V3.10138H9.44213C9.59009 3.10138 9.73198 3.04261 9.8366 2.93799C9.94122 2.83337 10 2.69147 10 2.54351C10 2.39556 9.94122 2.25366 9.8366 2.14904C9.73198 2.04442 9.59009 1.98564 9.44213 1.98564H7.48959V1.40992C7.48959 0.632624 6.85733 0 6.08004 0H3.91996C3.14267 0 2.51041 0.632252 2.51041 1.40992V1.98527H0.55787C0.409914 1.98527 0.268017 2.04405 0.163396 2.14867C0.0587754 2.25329 0 2.39556 0 2.54351ZM3.62615 1.41029C3.62615 1.24777 3.75781 1.11611 3.91996 1.11611H6.08004C6.24219 1.11611 6.37385 1.24777 6.37385 1.41029V1.98564H3.62578V1.40992L3.62615 1.41029ZM1.97114 3.10138H8.02886V9.69354C8.02866 9.88448 7.95275 10.0675 7.81778 10.2026C7.6828 10.3376 7.49977 10.4136 7.30884 10.4139H2.69116C2.50023 10.4136 2.3172 10.3376 2.18222 10.2026C2.04725 10.0675 1.97134 9.88448 1.97114 9.69354V3.10138Z' fill='%23fc2043'/%3E%3Cpath d='M3.61256 9.38725C3.76051 9.38725 3.90241 9.32847 4.00703 9.22385C4.11165 9.11923 4.17043 8.97733 4.17043 8.82938V4.68775C4.17043 4.5398 4.11165 4.3979 4.00703 4.29328C3.90241 4.18866 3.76051 4.12988 3.61256 4.12988C3.4646 4.12988 3.3227 4.18866 3.21808 4.29328C3.11346 4.3979 3.05469 4.5398 3.05469 4.68775V8.82938C3.05469 8.97733 3.11346 9.11923 3.21808 9.22385C3.3227 9.32847 3.4646 9.38725 3.61256 9.38725ZM6.38554 9.38725C6.5335 9.38725 6.67539 9.32847 6.78001 9.22385C6.88463 9.11923 6.94341 8.97733 6.94341 8.82938V4.68775C6.94341 4.5398 6.88463 4.3979 6.78001 4.29328C6.67539 4.18866 6.5335 4.12988 6.38554 4.12988C6.23758 4.12988 6.09569 4.18866 5.99107 4.29328C5.88645 4.3979 5.82767 4.5398 5.82767 4.68775V8.82938C5.82767 8.97733 5.88645 9.11923 5.99107 9.22385C6.09569 9.32847 6.23758 9.38725 6.38554 9.38725Z' fill='%23fc2043'/%3E%3C/svg%3E%0A"); +} + +.o-info-icon { + display: inline-block; + vertical-align: middle; + width: rem(16px); + height: rem(16px); + min-width: rem(16px); + min-height: rem(16px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.9987 0.333252C5.68016 0.333252 4.39123 0.724245 3.2949 1.45679C2.19857 2.18933 1.34409 3.23052 0.839504 4.4487C0.33492 5.66687 0.202898 7.00731 0.460132 8.30052C0.717367 9.59373 1.35231 10.7816 2.28466 11.714C3.21701 12.6463 4.40489 13.2813 5.6981 13.5385C6.99131 13.7957 8.33175 13.6637 9.54992 13.1591C10.7681 12.6545 11.8093 11.8 12.5418 10.7037C13.2744 9.60739 13.6654 8.31846 13.6654 6.99992C13.6654 6.12444 13.4929 5.25753 13.1579 4.4487C12.8229 3.63986 12.3318 2.90493 11.7127 2.28587C11.0937 1.66682 10.3588 1.17575 9.54992 0.840722C8.74109 0.50569 7.87418 0.333252 6.9987 0.333252ZM6.9987 12.3333C5.94387 12.3333 4.91272 12.0205 4.03566 11.4344C3.1586 10.8484 2.47501 10.0154 2.07134 9.0409C1.66768 8.06636 1.56206 6.994 1.76785 5.95944C1.97363 4.92487 2.48158 3.97456 3.22746 3.22868C3.97334 2.4828 4.92365 1.97485 5.95822 1.76906C6.99278 1.56328 8.06514 1.66889 9.03968 2.07256C10.0142 2.47623 10.8472 3.15982 11.4332 4.03688C12.0192 4.91394 12.332 5.94508 12.332 6.99992C12.332 8.41441 11.7701 9.77096 10.7699 10.7712C9.76974 11.7713 8.41319 12.3333 6.9987 12.3333Z' fill='%23A7A8AA'/%3E%3Cpath d='M6.9987 4.99996C7.36689 4.99996 7.66536 4.70148 7.66536 4.33329C7.66536 3.9651 7.36689 3.66663 6.9987 3.66663C6.63051 3.66663 6.33203 3.9651 6.33203 4.33329C6.33203 4.70148 6.63051 4.99996 6.9987 4.99996Z' fill='%23A7A8AA'/%3E%3Cpath d='M6.9987 5.66663C6.82189 5.66663 6.65232 5.73686 6.52729 5.86189C6.40227 5.98691 6.33203 6.15648 6.33203 6.33329V9.66663C6.33203 9.84344 6.40227 10.013 6.52729 10.138C6.65232 10.2631 6.82189 10.3333 6.9987 10.3333C7.17551 10.3333 7.34508 10.2631 7.4701 10.138C7.59513 10.013 7.66536 9.84344 7.66536 9.66663V6.33329C7.66536 6.15648 7.59513 5.98691 7.4701 5.86189C7.34508 5.73686 7.17551 5.66663 6.9987 5.66663Z' fill='%23A7A8AA'/%3E%3C/svg%3E%0A"); + cursor: help; +} + +.o-info-icon--green { + display: inline-block; + vertical-align: middle; + width: rem(16px); + height: rem(16px); + min-width: rem(16px); + min-height: rem(16px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.9987 0.333252C5.68016 0.333252 4.39123 0.724245 3.2949 1.45679C2.19857 2.18933 1.34409 3.23052 0.839504 4.4487C0.33492 5.66687 0.202898 7.00731 0.460132 8.30052C0.717367 9.59373 1.35231 10.7816 2.28466 11.714C3.21701 12.6463 4.40489 13.2813 5.6981 13.5385C6.99131 13.7957 8.33175 13.6637 9.54992 13.1591C10.7681 12.6545 11.8093 11.8 12.5418 10.7037C13.2744 9.60739 13.6654 8.31846 13.6654 6.99992C13.6654 6.12444 13.4929 5.25753 13.1579 4.4487C12.8229 3.63986 12.3318 2.90493 11.7127 2.28587C11.0937 1.66682 10.3588 1.17575 9.54992 0.840722C8.74109 0.50569 7.87418 0.333252 6.9987 0.333252ZM6.9987 12.3333C5.94387 12.3333 4.91272 12.0205 4.03566 11.4344C3.1586 10.8484 2.47501 10.0154 2.07134 9.0409C1.66768 8.06636 1.56206 6.994 1.76785 5.95944C1.97363 4.92487 2.48158 3.97456 3.22746 3.22868C3.97334 2.4828 4.92365 1.97485 5.95822 1.76906C6.99278 1.56328 8.06514 1.66889 9.03968 2.07256C10.0142 2.47623 10.8472 3.15982 11.4332 4.03688C12.0192 4.91394 12.332 5.94508 12.332 6.99992C12.332 8.41441 11.7701 9.77096 10.7699 10.7712C9.76974 11.7713 8.41319 12.3333 6.9987 12.3333Z' fill='%2348A23F'/%3E%3Cpath d='M6.9987 4.99996C7.36689 4.99996 7.66536 4.70148 7.66536 4.33329C7.66536 3.9651 7.36689 3.66663 6.9987 3.66663C6.63051 3.66663 6.33203 3.9651 6.33203 4.33329C6.33203 4.70148 6.63051 4.99996 6.9987 4.99996Z' fill='%2348A23F'/%3E%3Cpath d='M6.9987 5.66663C6.82189 5.66663 6.65232 5.73686 6.52729 5.86189C6.40227 5.98691 6.33203 6.15648 6.33203 6.33329V9.66663C6.33203 9.84344 6.40227 10.013 6.52729 10.138C6.65232 10.2631 6.82189 10.3333 6.9987 10.3333C7.17551 10.3333 7.34508 10.2631 7.4701 10.138C7.59513 10.013 7.66536 9.84344 7.66536 9.66663V6.33329C7.66536 6.15648 7.59513 5.98691 7.4701 5.86189C7.34508 5.73686 7.17551 5.66663 6.9987 5.66663Z' fill='%2348A23F'/%3E%3C/svg%3E%0A"); + cursor: help; +} + +.o-info-icon--yellow { + display: inline-block; + vertical-align: middle; + width: rem(16px); + height: rem(16px); + min-width: rem(16px); + min-height: rem(16px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.9987 0.333252C5.68016 0.333252 4.39123 0.724245 3.2949 1.45679C2.19857 2.18933 1.34409 3.23052 0.839504 4.4487C0.33492 5.66687 0.202898 7.00731 0.460132 8.30052C0.717367 9.59373 1.35231 10.7816 2.28466 11.714C3.21701 12.6463 4.40489 13.2813 5.6981 13.5385C6.99131 13.7957 8.33175 13.6637 9.54992 13.1591C10.7681 12.6545 11.8093 11.8 12.5418 10.7037C13.2744 9.60739 13.6654 8.31846 13.6654 6.99992C13.6654 6.12444 13.4929 5.25753 13.1579 4.4487C12.8229 3.63986 12.3318 2.90493 11.7127 2.28587C11.0937 1.66682 10.3588 1.17575 9.54992 0.840722C8.74109 0.50569 7.87418 0.333252 6.9987 0.333252ZM6.9987 12.3333C5.94387 12.3333 4.91272 12.0205 4.03566 11.4344C3.1586 10.8484 2.47501 10.0154 2.07134 9.0409C1.66768 8.06636 1.56206 6.994 1.76785 5.95944C1.97363 4.92487 2.48158 3.97456 3.22746 3.22868C3.97334 2.4828 4.92365 1.97485 5.95822 1.76906C6.99278 1.56328 8.06514 1.66889 9.03968 2.07256C10.0142 2.47623 10.8472 3.15982 11.4332 4.03688C12.0192 4.91394 12.332 5.94508 12.332 6.99992C12.332 8.41441 11.7701 9.77096 10.7699 10.7712C9.76974 11.7713 8.41319 12.3333 6.9987 12.3333Z' fill='%23FBAA19'/%3E%3Cpath d='M6.9987 4.99996C7.36689 4.99996 7.66536 4.70148 7.66536 4.33329C7.66536 3.9651 7.36689 3.66663 6.9987 3.66663C6.63051 3.66663 6.33203 3.9651 6.33203 4.33329C6.33203 4.70148 6.63051 4.99996 6.9987 4.99996Z' fill='%23FBAA19'/%3E%3Cpath d='M6.9987 5.66663C6.82189 5.66663 6.65232 5.73686 6.52729 5.86189C6.40227 5.98691 6.33203 6.15648 6.33203 6.33329V9.66663C6.33203 9.84344 6.40227 10.013 6.52729 10.138C6.65232 10.2631 6.82189 10.3333 6.9987 10.3333C7.17551 10.3333 7.34508 10.2631 7.4701 10.138C7.59513 10.013 7.66536 9.84344 7.66536 9.66663V6.33329C7.66536 6.15648 7.59513 5.98691 7.4701 5.86189C7.34508 5.73686 7.17551 5.66663 6.9987 5.66663Z' fill='%23FBAA19'/%3E%3C/svg%3E%0A"); + cursor: help; +} + +.o-close-icon { + display: inline-block; + vertical-align: middle; + width: rem(30px); + height: rem(30px); + min-width: rem(30px); + min-height: rem(30px); + background: center center/85% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='28' height='28' viewBox='0 0 28 28' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.0158 0.671877C6.65487 0.667483 0.684084 6.63111 0.67969 13.992C0.675295 21.3529 6.63892 27.3237 13.9998 27.3281C17.5362 27.3339 20.9292 25.9305 23.4282 23.4284C25.9282 20.9305 27.3338 17.542 27.3359 14.008C27.3403 6.64706 21.3767 0.676272 14.0158 0.671877ZM14.0162 25.9948C7.3916 25.9993 2.01774 20.6327 2.01319 14.0081C2.00871 7.38363 7.37533 2.00969 13.9998 2.00521C17.1826 1.99951 20.2364 3.26237 22.4855 5.51433C24.7354 7.76213 26.0007 10.8114 26.0028 13.9919C26.0073 20.6164 20.6407 25.9902 14.0162 25.9948ZM14.9425 14L19.1847 9.75782C19.4387 9.49862 19.4387 9.08382 19.1847 8.82463C18.927 8.56169 18.505 8.55746 18.242 8.81511L13.9998 13.0573L9.75765 8.81519C9.49846 8.5612 9.08366 8.5612 8.82447 8.81519C8.56153 9.07284 8.55729 9.49488 8.81494 9.75782L13.0571 14L8.81494 18.2422C8.68994 18.3672 8.61979 18.5367 8.61971 18.7135C8.61971 19.0816 8.91813 19.3801 9.2863 19.3802C9.46314 19.3805 9.63282 19.3101 9.75765 19.1849L13.9998 14.9427L18.242 19.1849C18.3669 19.3101 18.5365 19.3805 18.7134 19.3802C18.8901 19.3801 19.0596 19.31 19.1846 19.1851C19.445 18.9247 19.445 18.5026 19.1847 18.2422L14.9425 14Z' fill='%23A7A8AA'/%3E%3C/svg%3E%0A"); +} + +.o-icon--md { + background-size: 75%; +} + +.o-user-icon { + display: inline-block; + vertical-align: middle; + width: rem(16px); + min-width: rem(16px); + height: rem(16px); + background: center center/80% no-repeat; + background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_1113_12583)'%3E%3Cpath d='M6 7.5C7.65685 7.5 9 6.15685 9 4.5C9 2.84315 7.65685 1.5 6 1.5C4.34315 1.5 3 2.84315 3 4.5C3 6.15685 4.34315 7.5 6 7.5Z' stroke='%2348A23F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M1.45215 10.1246C1.91319 9.32654 2.57612 8.6639 3.37434 8.20321C4.17256 7.74251 5.07797 7.49999 5.99959 7.5C6.92122 7.50001 7.82661 7.74255 8.62483 8.20326C9.42304 8.66397 10.086 9.32663 10.547 10.1247' stroke='%2348A23F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_1113_12583'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"); + transition: $transition; + cursor: pointer; + + &:hover { + filter: grayscale(1); + } +} +.o-arrowdown-icon { + display: inline-block; + vertical-align: middle; + width: rem(24px); + height: rem(24px); + background: center center no-repeat; + background-image: url('data:image/svg+xml,'); + transition: $transition; +} + +.o-edit-icon--deposit { + display: inline-block; + vertical-align: middle; + width: rem(18px); + height: rem(18px); + min-width: rem(18px); + min-height: rem(18px); + background: center center/85% no-repeat; + background-image: url('data:image/svg+xml,'); +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_link.scss b/app/assets/stylesheets/objects/_link.scss new file mode 100644 index 000000000..55a8a51f2 --- /dev/null +++ b/app/assets/stylesheets/objects/_link.scss @@ -0,0 +1,25 @@ +.o-link { + font-weight: 500; + font-size: rem(14px); + line-height: rem(14px); + text-decoration: underline !important; + color: $primary-blue-color !important; + cursor: pointer; + + &:hover { + color: $secondary-blue-color !important; + } + + &:visited { + color: $primary-blue-color !important; + + &:hover { + color: $secondary-blue-color !important; + } + } + + &:disabled, &.u-disabled { + color: $grey-300-color !important; + pointer-events: none; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_new.scss b/app/assets/stylesheets/objects/_new.scss new file mode 100644 index 000000000..8b7af51fd --- /dev/null +++ b/app/assets/stylesheets/objects/_new.scss @@ -0,0 +1,9 @@ +.o-new { + display: inline-block; + vertical-align: middle; + width: rem(4px); + height: rem(4px); + border-radius: rem(2px); + background-color: $primary-orange-color; + margin-left: rem(8px); +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_objects.scss b/app/assets/stylesheets/objects/_objects.scss new file mode 100644 index 000000000..54fa970fb --- /dev/null +++ b/app/assets/stylesheets/objects/_objects.scss @@ -0,0 +1,11 @@ +@import "container"; +@import "hamburger/hamburgers"; +@import "arrow"; +@import "spacer"; +@import "grid"; +@import "icons"; +@import "new"; +@import "link"; +@import "tooltip"; +@import "form"; +@import "card"; \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_spacer.scss b/app/assets/stylesheets/objects/_spacer.scss new file mode 100644 index 000000000..805f44b9c --- /dev/null +++ b/app/assets/stylesheets/objects/_spacer.scss @@ -0,0 +1,11 @@ +.o-spacer { + +} + +.o-spacer--header { + margin-top: 0; + + @include mq(desktop) { + margin-top: rem(60px); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/objects/_tooltip.scss b/app/assets/stylesheets/objects/_tooltip.scss new file mode 100644 index 000000000..b5a1eb65c --- /dev/null +++ b/app/assets/stylesheets/objects/_tooltip.scss @@ -0,0 +1,21 @@ +.tippy-tooltip.eis-theme, .tippy-box[data-theme="eis"] .tippy-content { + background-color: $black-color !important; + color: $white-color !important; + text-align: center; + font-size: rem(12px); + line-height: rem(20px); + border-radius: rem(8px) !important; + + @include mq(laptop) { + font-size: rem(14px); + line-height: rem(24px); + } +} + +.tippy-box[data-theme="eis"] { + border-radius: rem(8px) !important; + + @include mq(laptop) { + max-width: rem(385px) !important; + } +} diff --git a/app/assets/stylesheets/objects/hamburger/_base.scss b/app/assets/stylesheets/objects/hamburger/_base.scss new file mode 100644 index 000000000..faf8a7c08 --- /dev/null +++ b/app/assets/stylesheets/objects/hamburger/_base.scss @@ -0,0 +1,95 @@ +// Hamburger +// ================================================== +.hamburger { + padding: $hamburger-padding-y $hamburger-padding-x; + display: inline-block; + cursor: pointer; + + transition-property: opacity, filter; + transition-duration: 0.15s; + transition-timing-function: linear; + + // Normalize ( + + +
+
+
+ +
+ +
+ <% if user_signed_in? %> + <%= link_to destroy_user_session_path, class: "c-header__user", method: :delete, data: { 'turbo-method': 'delete' } do %> + <%= render 'svg/person' %><%= t(:sign_out) %> + <% end %> + <% else %> + <%= link_to new_user_session_path, class: "c-header__user" do %> + <%= render 'svg/person' %><%= t(:sign_in) %> + <% end %> + <% end %> +
+ +
+ +
+
+
+ +
+
+ + + <%= current_language %> + + <%= render 'svg/down_arrow' %> + + + +
+
+ + diff --git a/app/components/common/header/component.rb b/app/components/common/header/component.rb new file mode 100644 index 000000000..9ba0d3d2c --- /dev/null +++ b/app/components/common/header/component.rb @@ -0,0 +1,75 @@ +module Common + module Header + class Component < ApplicationViewComponent + include Devise::Controllers::Helpers + + attr_reader :notifications + + def initialize(notifications:) + super + + @notifications = notifications + end + + def languages + languages = { et: 'Est', en: 'Eng' } + + l = languages.map do |lang, desc| + content_tag('li') do + content_tag('a', desc, href: locale_path(locale: lang), method: :put, data: { "turbo-method": 'put' }) + end + end + + safe_join(l) + end + + def regular_menu_list + menu = current_user&.roles&.include?(User::ADMINISTATOR_ROLE) ? admin_menu_list_items : regular_menu_list_items + + m = menu.map do |item| + content_tag('li') do + content_tag('a', item[:name], href: item[:path], style: active_class(item[:path])) + end + end + + safe_join(m) + end + + def current_language + I18n.locale == :et ? 'Est' : 'Eng' + end + + def active_class(link_path) + current_page?(link_path) ? 'text-decoration: underline;' : '' + end + + private + + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize + def admin_menu_list_items + [{ name: t(:auctions_name), path: admin_auctions_path }, + { name: t(:results_name), path: admin_results_path }, + { name: t(:finished_auctions), path: admin_finished_auctions_index_path }, + { name: t(:billing_profiles_name), path: admin_billing_profiles_path }, + { name: t(:users_name), path: admin_users_path }, + { name: t(:bans_name), path: admin_bans_path }, + { name: t(:invoices_name), path: admin_invoices_path }, + { name: t(:jobs_name), path: admin_jobs_path }, + { name: t(:settings_name), path: admin_settings_path }, + { name: t(:paid_deposits_name), path: admin_paid_deposits_path }, + { name: t(:statistics_name), path: admin_statistics_path }] + end + + def regular_menu_list_items + items = [{ name: t(:auctions_name), path: auctions_path }, + { name: t(:my_invoices), path: invoices_path }, + { name: t(:my_offers), path: offers_path }, + { name: t(:my_wishlist), path: wishlist_items_path }] + + items.insert(1, { name: t(:profile), path: user_path(current_user&.uuid) }) if user_signed_in? + items + end + end + end +end diff --git a/app/components/common/header/component_deprecated.html.erb b/app/components/common/header/component_deprecated.html.erb new file mode 100644 index 000000000..4d5780d47 --- /dev/null +++ b/app/components/common/header/component_deprecated.html.erb @@ -0,0 +1,66 @@ +
+
+ +
+
+
+
+ +
+ +
+ <% if user_signed_in? %> + <%= link_to destroy_user_session_path, class: "c-header__user", method: :delete, data: { 'turbo-method': 'delete' } do %> + <%= render 'svg/person' %><%= t(:sign_out) %> + <% end %> + <% else %> + <%= link_to new_user_session_path, class: "c-header__user" do %> + <%= render 'svg/person' %><%= t(:sign_in) %> + <% end %> + <% end %> +
+
+
+
+
+ +
+
+ + <%= current_language %><%= render 'svg/down_arrow' %> + +
    + <%= languages %> +
+
+
+
\ No newline at end of file diff --git a/app/components/common/header/notification/component.html.erb b/app/components/common/header/notification/component.html.erb new file mode 100644 index 000000000..c92a0d1e7 --- /dev/null +++ b/app/components/common/header/notification/component.html.erb @@ -0,0 +1,35 @@ +<% if user_signed_in? %> + +<% end %> diff --git a/app/components/common/header/notification/component.rb b/app/components/common/header/notification/component.rb new file mode 100644 index 000000000..2179b037a --- /dev/null +++ b/app/components/common/header/notification/component.rb @@ -0,0 +1,15 @@ +module Common + module Header + module Notification + class Component < ApplicationViewComponent + attr_reader :notifications + + def initialize(notifications:) + super + + @notifications = notifications + end + end + end + end +end diff --git a/app/components/common/hero/component.html.erb b/app/components/common/hero/component.html.erb new file mode 100644 index 000000000..d2c85b1a1 --- /dev/null +++ b/app/components/common/hero/component.html.erb @@ -0,0 +1,19 @@ +
+
+
+
+ + + + + +

<%= @title %>

+
+
+
diff --git a/app/components/common/hero/component.rb b/app/components/common/hero/component.rb new file mode 100644 index 000000000..937e0f8d5 --- /dev/null +++ b/app/components/common/hero/component.rb @@ -0,0 +1,14 @@ +module Common + module Hero + class Component < ApplicationViewComponent + + attr_reader :title + + def initialize(title:) + super + + @title = title + end + end + end +end \ No newline at end of file diff --git a/app/components/common/links/link_button/component.html.erb b/app/components/common/links/link_button/component.html.erb new file mode 100644 index 000000000..e36c9f579 --- /dev/null +++ b/app/components/common/links/link_button/component.html.erb @@ -0,0 +1,7 @@ +<% if content.present? %> + <%= link_to @href, class: "c-btn #{colorize} c-btn--icon", **@options do %> + <%= content %> + <% end %> +<% else %> + <%= link_to @link_title, @href, class: "c-btn #{colorize} #{@css_class_as_login}", **@options %> +<% end %> diff --git a/app/components/common/links/link_button/component.rb b/app/components/common/links/link_button/component.rb new file mode 100644 index 000000000..b6905b86b --- /dev/null +++ b/app/components/common/links/link_button/component.rb @@ -0,0 +1,34 @@ +module Common + module Links + module LinkButton + class Component < ApplicationViewComponent + attr_reader :link_title, :href, :color, :css_class_as_login, :options + + def initialize(link_title: nil, href:, css_class_as_login: false, color: 'green', options: {}) + super + + @link_title = link_title + @href = href + @color = color + @options = options + @css_class_as_login = css_class_as_login ? 'c-btn--login' : '' + end + + def colorize + colors_hash[color] + end + + def colors_hash + { + 'blue-secondary' => 'c-btn--blue-secondary', + 'green' => 'c-btn--green', + 'blue' => 'c-btn--blue', + 'orange' => 'c-btn--orange', + 'black' => 'c-btn--black', + 'ghost' => 'c-btn--ghost' + } + end + end + end + end +end \ No newline at end of file diff --git a/app/components/common/notifications/icon/component.html.erb b/app/components/common/notifications/icon/component.html.erb new file mode 100644 index 000000000..145dabc95 --- /dev/null +++ b/app/components/common/notifications/icon/component.html.erb @@ -0,0 +1,23 @@ +<%- if @readed %> + <%- if @notification_type == 'AuctionWinnerNotification' %> + +
<%= t('english_offers.index.you_won') %>!
+ <%- elsif @notification_type == 'OfferNotification' %> + +
<%= t('english_offers.index.new_bid') %>!
+ <%- elsif @notification_type == 'AuctionLooserNotification' %> + +
<%= t('english_offers.index.you_lose') %>!
+ <%- end %> +<%- else %> + <%- if @notification_type == 'AuctionWinnerNotification' %> + +
<%= t('english_offers.index.you_won') %>!
+ <%- elsif @notification_type == 'OfferNotification' %> + +
<%= t('english_offers.index.new_bid') %>!
+ <%- elsif @notification_type == 'AuctionLooserNotification' %> + +
<%= t('english_offers.index.you_lose') %>!
+ <%- end %> +<%- end %> diff --git a/app/components/common/notifications/icon/component.rb b/app/components/common/notifications/icon/component.rb new file mode 100644 index 000000000..851aeb900 --- /dev/null +++ b/app/components/common/notifications/icon/component.rb @@ -0,0 +1,16 @@ +module Common + module Notifications + module Icon + class Component < ViewComponent::Base + attr_reader :notification_type, :readed + + def initialize(notification:, readed:) + @notification_type = notification.type + @readed = readed + + super + end + end + end + end +end diff --git a/app/components/common/pagy/component.html.erb b/app/components/common/pagy/component.html.erb new file mode 100644 index 000000000..a50d5d9e1 --- /dev/null +++ b/app/components/common/pagy/component.html.erb @@ -0,0 +1,7 @@ +
+ <% if @pagy.pages > 1 %> +
+ <%== pagy_nav(@pagy) %> +
+ <% end %> +
diff --git a/app/components/common/pagy/component.rb b/app/components/common/pagy/component.rb new file mode 100644 index 000000000..90ad4ce7f --- /dev/null +++ b/app/components/common/pagy/component.rb @@ -0,0 +1,15 @@ +module Common + module Pagy + class Component < ApplicationViewComponent + include PagyHelper + + attr_reader :pagy + + def initialize(pagy:) + @pagy = pagy + + super + end + end + end +end diff --git a/app/components/common/table/component.html.erb b/app/components/common/table/component.html.erb new file mode 100644 index 000000000..517965372 --- /dev/null +++ b/app/components/common/table/component.html.erb @@ -0,0 +1,9 @@ +<%= tag.table **@options do %> + + + <%= table_header_generator %> + + + + <%= content %> +<% end %> diff --git a/app/components/common/table/component.rb b/app/components/common/table/component.rb new file mode 100644 index 000000000..5f07ed824 --- /dev/null +++ b/app/components/common/table/component.rb @@ -0,0 +1,65 @@ +module Common + module Table + class Component < ApplicationViewComponent + attr_reader :header_collection, :options + + def initialize(header_collection:, options: {}) + @header_collection = header_collection + @options = options + + super + end + + def table_header_generator + safe_join(header_collection.map do |item| + option = item[:options] || {} + item[:column].nil? ? tag.th(item[:caption], **option) : sortable_column(item[:column], item[:caption], option) + end) + end + + private + + def sortable_column(column, caption, option) + tag.th caption, **option, + data: { controller: stimulus_controller_name_dirty, + action: "click->#{stimulus_controller_name_dirty}#resortTable", + "#{stimulus_controller_name_dirty}-direction-value": next_direction(column), + "#{stimulus_controller_name_dirty}-column-value": column, + "#{stimulus_controller_name_dirty}-frame-name-value": frame_name, + "#{stimulus_controller_name_dirty}-asc-class": sorting_asc_class, + "#{stimulus_controller_name_dirty}-desc-class": sorting_desc_class, + "#{stimulus_controller_name_dirty}-target": target_element_name } + end + + def stimulus_controller_name_dirty + 'table--ordeable' + end + + def sorting_asc_class + 'sorting_asc' + end + + def sorting_desc_class + 'sorting_desc' + end + + def target_element_name + 'th' + end + + def frame_name + 'results' + end + + def currently_sorted?(column) + params[:sort_by] == column.to_s + end + + def next_direction(column) + return unless currently_sorted?(column) + + params[:sort_direction] == 'asc' ? 'desc' : 'asc' + end + end + end +end diff --git a/app/components/common/timer/component.html.erb b/app/components/common/timer/component.html.erb new file mode 100644 index 000000000..89c25dfe7 --- /dev/null +++ b/app/components/common/timer/component.html.erb @@ -0,0 +1,4 @@ + + diff --git a/app/components/common/timer/component.rb b/app/components/common/timer/component.rb new file mode 100644 index 000000000..b8a33e5d3 --- /dev/null +++ b/app/components/common/timer/component.rb @@ -0,0 +1,13 @@ +module Common + module Timer + class Component < ApplicationViewComponent + attr_reader :auction + + def initialize(auction:) + super + + @auction = auction + end + end + end +end diff --git a/app/components/common/webpush/setting/component.html.erb b/app/components/common/webpush/setting/component.html.erb new file mode 100644 index 000000000..3b719a4f3 --- /dev/null +++ b/app/components/common/webpush/setting/component.html.erb @@ -0,0 +1,4 @@ + diff --git a/app/components/common/webpush/setting/component.rb b/app/components/common/webpush/setting/component.rb new file mode 100644 index 000000000..d27b05ef1 --- /dev/null +++ b/app/components/common/webpush/setting/component.rb @@ -0,0 +1,16 @@ +module Common + module Webpush + module Setting + class Component < ApplicationViewComponent + def data_data + { + controller: 'push-notification', + # profile_webpush_target: 'checkbox', + push_notification_vapid_public_value: Rails.configuration.customization[:vapid_public], + action: 'change->push-notification#setupPushNotifications' + } + end + end + end + end +end diff --git a/app/components/modals/change_offer/component.html.erb b/app/components/modals/change_offer/component.html.erb new file mode 100644 index 000000000..4f9bedcc2 --- /dev/null +++ b/app/components/modals/change_offer/component.html.erb @@ -0,0 +1,112 @@ +
+
+
+
+ +
+
+ + <%= t('offer.change_bid') %> +
+
+ <%= @auction.domain_name %> + +
+
+ <%= t('english_offers.timer') %> <%= component 'common/timer', auction: @auction %> +
+
+ +
+
+
+ <%= t('english_offers.show.min_bid') %> +

<%= Money.from_amount(@auction.min_bids_step.to_f, Setting.find_by(code: 'auction_currency').retrieve, precision: 2) %> €

+
+
+ <%= t('english_offers.show.current_price') %> +

+ + <%= current_price %> € + +

+ <%= current_bidder %> +
+
+ + <%= form_with **autobider_form_properties do |f| %> +
+ <%= f.hidden_field :domain_name, value: offer.auction.domain_name %> + <%= f.hidden_field :user_id, value: offer.user_id %> + + +
+ <%= component 'common/form/number_field', form: f, **number_field_properties %> +
+ + <%= component 'captcha', captcha_required: @captcha_required, show_checkbox_recaptcha: @show_checkbox_recaptcha, action: 'autobidder' %> + +
+
+ <%= component 'common/form/checkboxes/rounded_checkbox', form: f, **autobider_checkbox_properties %> + + + <%= t('english_offers.form.autobider') %>: <%= @autobider.enable ? t('english_offers.form.yep') : t('english_offers.form.nope') %> + +
+
+
+ <% end %> + + <%= form_with **offer_form_properties do |f| %> + <%= f.hidden_field :user_id, value: offer.user_id %> + <%= f.hidden_field :auction_id, value: offer.auction_id %> + +
+
+ + + <%= component 'modals/change_offer/number_form_field', offer_value: @auction.min_bids_step.to_f, offer_disabled: @auction.finished? %> + +
+ +
+ + + <% if offer.billing_profile.present? %> + <%= t('offers.price_with_wat', + price: (@auction.min_bids_step.to_f * offer.billing_profile.vat_rate.to_f).to_f + @auction.min_bids_step.to_f, + tax: offer.billing_profile.vat_rate * 100) %> + <% else %> + <%= t('offers.price_is_without_vat') %> + <% end %> + +
+
+
+ <%= f.label :billing_profile, t('.bidder') %> + <%= component 'common/form/dropdown_input', form: f, **billing_profile_dropdown_properties %> +
+ + <%= component 'captcha', captcha_required: @captcha_required, show_checkbox_recaptcha: @show_checkbox_recaptcha, action: 'english_offer' %> + + <%= link_to t('new_billing_profile'), billing_profiles_path, class: 'c-modal__link c-modal__link--first-col', target: '_top' %> + <% end %> +
+ + + <%#= component 'captcha', captcha_required: @captcha_required, show_checkbox_recaptcha: @show_checkbox_recaptcha, action: 'english_offer' %> + +
+
+
+
diff --git a/app/components/modals/change_offer/component.rb b/app/components/modals/change_offer/component.rb new file mode 100644 index 000000000..0a252935c --- /dev/null +++ b/app/components/modals/change_offer/component.rb @@ -0,0 +1,149 @@ +module Modals + module ChangeOffer + class Component < ApplicationViewComponent + attr_reader :offer, :auction, :autobider, :update, :current_user, :captcha_required, :show_checkbox_recaptcha + + def initialize(offer:, auction:, autobider:, update:, current_user:, captcha_required:, show_checkbox_recaptcha:) + super + + @offer = offer + @auction = auction + @autobider = autobider + @update = update + @current_user = current_user + @captcha_required = captcha_required + @show_checkbox_recaptcha = show_checkbox_recaptcha + end + + def url + if update + english_offer_path(offer.uuid) + else + auction_english_offers_path(auction_uuid: auction.uuid) + end + end + + def autobider_url + if autobider.new_record? + autobider_index_path + else + autobider_path(uuid: @autobider.uuid) + end + end + + def offer_disabled? + offer.auction.finished? ? true : false + end + + # def current_price(offer, current_user) + # return unless offer + + # is_user_offer = offer&.billing_profile&.user_id == current_user&.id + # username = is_user_offer ? I18n.t('auctions.you').to_s : offer.username + # content_tag(:span, class: 'current_price', + # data: { 'user-id': offer.user_id, you: I18n.t('auctions.you') }) do + # content = "#{offer.price} " + # content << content_tag(:span, (username.nil? ? '' : "(#{username})"), class: 'bidder') + # content.html_safe + # end + # end + + def current_price + return Money.new(0) unless last_actual_offer + + last_actual_offer.price + end + + def current_bidder + return unless last_actual_offer + + is_user_offer = last_actual_offer&.billing_profile&.user_id == current_user&.id + username = is_user_offer ? I18n.t('auctions.you').to_s : last_actual_offer.username + + username + end + + def last_actual_offer + @last_actual_offer ||= auction.offers.order(updated_at: :desc).first + end + + def number_field_properties + { + attribute: :price, + options: { + min: 0.0, + step: 0.01, + # value: number_with_precision(@autobider.price.to_f, precision: 2) || @auction.min_bids_step.to_f, + value: number_with_precision(@autobider.price.to_f, precision: 2, delimiter: '', separator: '.') || @auction.min_bids_step.to_f, + disabled: is_number_field_disabled?, + data: { + action: 'keydown->form--autobider-submit#validatePrice input->form--autobider-submit#validatingInputPrice', + form__autobider_submit_target: 'price', + form__autobider_validation_target: 'bidInput' + } + } + } + end + + def autobider_form_properties + { + model: @autobider, + url: autobider_url, + data: { + turbo_frame: '_top', + controller: 'form--autobider-submit form--autobider-validation', + form__autobider_submit_target: 'form', + form__autobider_validation_bid_min_value: @auction.min_bids_step.to_f + }, + html: { id: ' autobid_form ' } + } + end + + def autobider_checkbox_properties + { + attribute: :enable, + options: { + id: 'checkbox', + data: { + form__autobider_submit_target: 'checkbox', + action: 'change->form--autobider-submit#submitAutobider' + } + } + } + end + + def is_number_field_disabled? + @auction.finished? ? true : false + end + + def offer_form_properties + { + model: @offer, + url:, + id: 'english_offer_form', + data: { + turbo: false, + controller: 'autotax-counter', + autotax_counter_template_value: t('english_offers.price_with_wat_template'), + autotax_counter_tax_value: "#{offer.billing_profile.present? ? offer.billing_profile.vat_rate : 0.0}", + autotax_counter_defaulttemplate_value: t('offers.price_is_without_vat') + } + } + end + + def billing_profile_dropdown_properties + { + attribute: :billing_profile_id, + enum: billing_profiles = BillingProfile.where(user_id: offer.user_id).collect do |b| + [b.name, b.id, {'data-vat-rate' => b.vat_rate}] + end, + first_options: { }, + second_options: { + class: billing_profiles.size == 1 ? "disabled" : "", + data: { action: 'change->autotax-counter#updateTax' } + } + } + end + end + end +end diff --git a/app/components/modals/change_offer/number_form_field/component.html.erb b/app/components/modals/change_offer/number_form_field/component.html.erb new file mode 100644 index 000000000..3bb050d80 --- /dev/null +++ b/app/components/modals/change_offer/number_form_field/component.html.erb @@ -0,0 +1 @@ +<%= number_field_tag 'offer_price', number_with_precision(@offer_value, precision: 2, separator: '.'), name: "offer[price]", class: "form-control", min: 0, step: "0.01", disabled: @offer_disabled, data: { action: 'autotax-counter#count' } %> diff --git a/app/components/modals/change_offer/number_form_field/component.rb b/app/components/modals/change_offer/number_form_field/component.rb new file mode 100644 index 000000000..56d9cec62 --- /dev/null +++ b/app/components/modals/change_offer/number_form_field/component.rb @@ -0,0 +1,16 @@ +module Modals + module ChangeOffer + module NumberFormField + class Component < ApplicationViewComponent + attr_reader :offer_value, :offer_disabled + + def initialize(offer_value:, offer_disabled:) + super + + @offer_value = offer_value + @offer_disabled = offer_disabled + end + end + end + end +end \ No newline at end of file diff --git a/app/components/modals/change_offer_po/component.html.erb b/app/components/modals/change_offer_po/component.html.erb new file mode 100644 index 000000000..54977d1bc --- /dev/null +++ b/app/components/modals/change_offer_po/component.html.erb @@ -0,0 +1,87 @@ +
+
+
+
+ +
+
+ + <%= t('offers.change_bid') %> +
+
+ <%= @auction.domain_name %> + +
+
+ <%= t('offers.new.title') %> +
+
+ +
+
+
+ <%= t('english_offers.show.min_bid') %> +

<%= Money.new(Setting.find_by(code: 'auction_minimum_offer').retrieve) %> €

+
+
+ <%= t('auctions.you') %> +

<%= current_price.nil? ? 0.0 : current_price %> €

+
+
+ + <%= form_with **offer_form_properties do |f| %> + <%= f.hidden_field :user_id, value: offer.user_id %> + <%= f.hidden_field :auction_id, value: offer.auction_id %> +
+
+ + + <%= component 'modals/change_offer/number_form_field', offer_value: current_price, offer_disabled: @auction.finished? %> + + <%= minimum_offer %> +
+ +
+ + + <% if offer.billing_profile.present? %> + <%= t('offers.price_with_wat', price: (@auction.min_bids_step.to_f * offer.billing_profile.vat_rate.to_f).to_f + @auction.min_bids_step.to_f, tax: offer.billing_profile.vat_rate * 100) %> + <% else %> + <%= t('offers.price_is_without_vat') %> + <% end %> + +
+
+
+ <%= f.label :billing_profile, t('.bidder') %> + <%= component 'common/form/dropdown_input', form: f, **billing_profile_dropdown_properties %> +
+ <%= component 'captcha', captcha_required: @captcha_required, show_checkbox_recaptcha: @show_checkbox_recaptcha, action: 'offer' %> + + <%= link_to t('new_billing_profile'), billing_profiles_path, class: 'c-modal__link c-modal__link--first-col', target: '_top' %> + <% end %> + +
+ + + <%#= component 'captcha', captcha_required: @captcha_required, show_checkbox_recaptcha: @show_checkbox_recaptcha, action: 'offer' %> +
+
+
+
diff --git a/app/components/modals/change_offer_po/component.rb b/app/components/modals/change_offer_po/component.rb new file mode 100644 index 000000000..ef9c23bf3 --- /dev/null +++ b/app/components/modals/change_offer_po/component.rb @@ -0,0 +1,96 @@ +module Modals + module ChangeOfferPo + class Component < ApplicationViewComponent + attr_reader :offer, :auction, :update, :current_user, :captcha_required, :show_checkbox_recaptcha + + def initialize(offer:, auction:, update:, current_user:, captcha_required:, show_checkbox_recaptcha:) + super + + @offer = offer + @auction = auction + @update = update + @current_user = current_user + @captcha_required = captcha_required + @show_checkbox_recaptcha = show_checkbox_recaptcha + end + + def url + if update + offer_path(@offer.uuid) + else + auction_offers_path(auction_uuid: params[:auction_uuid]) + end + end + + def offer_disabled? + offer.auction.finished? ? true : false + end + + def current_price + auction.current_price_from_user(current_user) if auction.offer_from_user(current_user).present? + end + + def minimum_offer + I18n.t('.offers.form.minimum_offer', minimum: Money.new(Setting.find_by(code: 'auction_minimum_offer').retrieve, + Setting.find_by(code: 'auction_currency').retrieve)) + end + + def offer_form_properties + { + model: @offer, + url:, + id: 'english_offer_form', + data: { + turbo: false, + controller: 'autotax-counter', + autotax_counter_template_value: t('english_offers.price_with_wat_template'), + autotax_counter_tax_value: "#{offer.billing_profile.present? ? offer.billing_profile.vat_rate : 0.0}", + autotax_counter_defaulttemplate_value: t('offers.price_is_without_vat') + } + } + end + + def billing_profile_dropdown_properties + { + attribute: :billing_profile_id, + enum: billing_profiles = BillingProfile.where(user_id: offer.user_id).collect do |b| + [b.name, b.id, { 'data-vat-rate' => b.vat_rate }] + end, + first_options: {}, + second_options: { + class: billing_profiles.size == 1 ? 'disabled' : '', + data: { action: 'change->autotax-counter#updateTax' } + } + } + end + + def delete_action_button_properties + { + type: 'delete', + href: offer_path(@auction.offer_from_user(current_user).uuid), + options: { + method: :delete, + form: { + data: { + turbo_confirm: t('.confirm_delete') + } + }, + target: '_top' + } + } + end + + def submit_form_button_properties + { + class: "c-btn c-btn--green", + id: 'bid_action', + form: 'english_offer_form', + style: 'cursor: pointer;', + data: { + turbo: false + } + } + end + end + end +end diff --git a/app/components/modals/delete_offer/component.html.erb b/app/components/modals/delete_offer/component.html.erb new file mode 100644 index 000000000..3e3011d8c --- /dev/null +++ b/app/components/modals/delete_offer/component.html.erb @@ -0,0 +1,25 @@ +
+
+
+
+ + <%= t('offers.delete_title') %> +
+
+ <%= @offer.auction.domain_name %> + +
+
+
+

<%= t(:are_you_sure) %>

+
+ +
+
diff --git a/app/components/modals/delete_offer/component.rb b/app/components/modals/delete_offer/component.rb new file mode 100644 index 000000000..69cb0c9e8 --- /dev/null +++ b/app/components/modals/delete_offer/component.rb @@ -0,0 +1,13 @@ +module Modals + module DeleteOffer + class Component < ApplicationViewComponent + attr_reader :offer + + def initialize(offer:) + super + + @offer = offer + end + end + end +end \ No newline at end of file diff --git a/app/components/modals/deposit/component.html.erb b/app/components/modals/deposit/component.html.erb new file mode 100644 index 000000000..2067cf8e4 --- /dev/null +++ b/app/components/modals/deposit/component.html.erb @@ -0,0 +1,58 @@ + +
+
+
+
+ +
+
+ + <%= t('offers.new.make_deposit') %> +
+
+ <%= @auction.domain_name %> +
+
+ <%= t('english_offers.timer') %> <%= component 'common/timer', auction: @auction %> +
+
+
+
+
+ <%= t('english_offers.show.min_bid') %> +

+ <%= number_with_precision(Money.from_amount(@auction.min_bids_step.to_f, Setting.find_by(code: 'auction_currency').retrieve, precision: 2)) %> € +

+
+
+ <%= t('english_offers.show.current_price') %> +

<%= @auction&.currently_winning_offer&.price || Money.new(0) %> €

+
+
+
+ <%= t('offers.new.deposit_description_html', deposit: number_with_precision(@auction.deposit, precision: 2)) %> + +
+
+
+
<%= t('offers.new.deposit_enable') %> +

<%= "#{number_with_precision(@auction.deposit, precision: 2)} €" %>

+ <%#= button_to t('offers.new.make_deposit'), english_offer_deposit_auction_path(uuid: @auction.uuid, current_user: @current_user), + class: "c-btn c-btn-white c-btn--fs", method: :post, data: { turbo: false } %> + + <%= form_with url: english_offer_deposit_auction_path(uuid: @auction.uuid, current_user: @current_user), data: { turbo: false} do |f| %> + <%= component 'captcha', captcha_required: @captcha_required, show_checkbox_recaptcha: @show_checkbox_recaptcha, action: 'english_offer' %> + + <%= f.submit t('offers.new.make_deposit'), class: "c-btn c-btn-white c-btn--fs" %> + <% end %> +
+
+
+
+
+
+
+
+ diff --git a/app/components/modals/deposit/component.rb b/app/components/modals/deposit/component.rb new file mode 100644 index 000000000..c2c7abf23 --- /dev/null +++ b/app/components/modals/deposit/component.rb @@ -0,0 +1,29 @@ +module Modals + module Deposit + class Component < ApplicationViewComponent + attr_reader :auction, :current_user, :captcha_required + + def initialize(auction:, current_user:, captcha_required:, show_checkbox_recaptcha:) + super + + @auction = auction + @current_user = current_user + @captcha_required = captcha_required + @show_checkbox_recaptcha = show_checkbox_recaptcha + end + + def current_price(offer, current_user) + return unless offer + + is_user_offer = offer&.billing_profile&.user_id == current_user&.id + username = is_user_offer ? I18n.t('auctions.you').to_s : offer.username + content_tag(:span, class: 'current_price', + data: { 'user-id': offer.user_id, you: I18n.t('auctions.you') }) do + content = "#{offer.price} " + content << content_tag(:span, (username.nil? ? '' : "(#{username})"), class: 'bidder') + content.html_safe + end + end + end + end +end diff --git a/app/components/modals/forgot_password/component.html.erb b/app/components/modals/forgot_password/component.html.erb new file mode 100644 index 000000000..3aca5fdaf --- /dev/null +++ b/app/components/modals/forgot_password/component.html.erb @@ -0,0 +1,38 @@ +
+
+
+
+ +
+
+ + <%= t('profile') %> +
+
+ <%= t(:forgot_your_password) %> +
+
+ <%= t('devise.passwords.new.reset_button') %> +
+
+
+ <%= form_for(@resource, as: @resource_name, url: password_path(@resource_name), html: { method: :post, class: 'c-fp__form' }, data: { turbo: false }) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+
+ + +
+ <% end %> +
+
+
+
+
diff --git a/app/components/modals/forgot_password/component.rb b/app/components/modals/forgot_password/component.rb new file mode 100644 index 000000000..f757e9cdd --- /dev/null +++ b/app/components/modals/forgot_password/component.rb @@ -0,0 +1,16 @@ +module Modals + module ForgotPassword + class Component < ApplicationViewComponent + include Devise::Controllers::Helpers + + attr_reader :resource, :resource_name + + def initialize(resource:, resource_name:) + super + + @resource = resource + @resource_name = resource_name + end + end + end +end diff --git a/app/components/modals/no_reset_password_link/component.html.erb b/app/components/modals/no_reset_password_link/component.html.erb new file mode 100644 index 000000000..3b5b9e4eb --- /dev/null +++ b/app/components/modals/no_reset_password_link/component.html.erb @@ -0,0 +1,34 @@ +
> +
+
+
+ +
+
+ + <%= t('profile') %> +
+
+ <%= t('devise.confirmations.new.title') %> +
+
+
+ <%= form_for @resource, as: @resource_name, url: confirmation_path(@resource_name), html: { method: :post, class: "c-modal__main"} do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+
+ + +
+ <% end %> +
+
+
+
+
diff --git a/app/components/modals/no_reset_password_link/component.rb b/app/components/modals/no_reset_password_link/component.rb new file mode 100644 index 000000000..a8e8ebdde --- /dev/null +++ b/app/components/modals/no_reset_password_link/component.rb @@ -0,0 +1,16 @@ +module Modals + module NoResetPasswordLink + class Component < ApplicationViewComponent + include Devise::Controllers::Helpers + + attr_reader :resource, :resource_name + + def initialize(resource:, resource_name:) + super + + @resource = resource + @resource_name = resource_name + end + end + end +end diff --git a/app/components/modals/pay_invoice/component.html.erb b/app/components/modals/pay_invoice/component.html.erb new file mode 100644 index 000000000..e8fa2db9a --- /dev/null +++ b/app/components/modals/pay_invoice/component.html.erb @@ -0,0 +1,48 @@ +
+
+
+
+ +
+
+ + <%= t('invoices.bill') %> +
+
+ <%= @invoice.title %> +
+
+ <%= t('payment_orders.status') %> + <%- if @invoice.paid? %> + <%= component 'common/badgets', status: 'paid' %> + <%- else %> + <%= component 'common/badgets', status: 'pending' %> + <%- end %> +
+
+ + <%= turbo_frame_tag "invoice_information" do %> + <%= component 'modals/pay_invoice/invoice_information', invoice: @invoice %> + <% end %> + + + +
+ +
+
+
diff --git a/app/components/modals/pay_invoice/component.rb b/app/components/modals/pay_invoice/component.rb new file mode 100644 index 000000000..e3ebc5292 --- /dev/null +++ b/app/components/modals/pay_invoice/component.rb @@ -0,0 +1,25 @@ +module Modals + module PayInvoice + class Component < ApplicationViewComponent + attr_reader :invoice + + def initialize(invoice:) + super + + @invoice = invoice + end + + def amount_field_properties + { + attribute: :amount, + options: { + min: 0.0, + step: 0.01, + value: number_with_precision(invoice.due_amount.to_f, precision: 2, delimiter: '', separator: '.'), + disabled: false + } + } + end + end + end +end \ No newline at end of file diff --git a/app/components/modals/pay_invoice/invoice_information/component.html.erb b/app/components/modals/pay_invoice/invoice_information/component.html.erb new file mode 100644 index 000000000..32744f8b7 --- /dev/null +++ b/app/components/modals/pay_invoice/invoice_information/component.html.erb @@ -0,0 +1,110 @@ +
+
+
+ + <%= form_with model: @invoice, url: invoice_path(@invoice.uuid), data: { controller: 'form--debounce', action: 'change->form--debounce#search', form__debounce_target: 'form' } do |f| %> + <%= component 'common/form/label', form: f, attribute: :billing_profile_id, title: t('billing_profiles_name') %> + + <%= component 'common/form/dropdown_input', form: f, attribute: :billing_profile_id, + enum: options_for_select(BillingProfile.where(user_id: @invoice.user_id).pluck(:name, :id), params[:billing_profile_id].presence || @invoice.billing_profile_id), + first_options: { include_blank: false } %> + <% end %> +
+ +
+ + <%= Setting.find_by(code: 'invoice_issuer').retrieve %> +
+ +
+ +
+
+ + <%= @invoice.address %> +
+ +
+ + <%= Setting.find_by(code: 'invoice_issuer_address').retrieve %> +
+
+ + <%= Setting.find_by(code: 'invoice_issuer_reg_no').retrieve %> +
+
+ + <%= Setting.find_by(code: 'invoice_issuer_vat_number').retrieve %> +
+
+
+ +
+ + + + + + + + + + + + + + <% @invoice.items.each_with_index do |item, index| %> + + + + + + <% end %> + + + + + + + + + + + + + + <% if @invoice.enable_deposit? %> + + + + + + <% end %> + + <% if @invoice.partial_payments? && !@invoice.paid? %> + + + + + + <% end %> + + + + + + + + +
#<%= t('invoices.item') %><%= t('invoices.price') %>
<%= index += 1 %><%= I18n.t('invoice_items.name', + domain_name: item.invoice.result.auction.domain_name, + auction_end: item.invoice.result.auction.ends_at.to_date) %> + <%= item.price %>
<%= t('invoices.vat_amount') %> <%= number_to_percentage(@invoice.vat_rate * 100, precision: 0) %><%= t('offers.price_in_currency', price: @invoice.vat) %>
<%= t('invoices.total') %><%= t('offers.price_in_currency', price: @invoice.total + (@invoice.enable_deposit? ? @invoice.deposit : 0.0)) %>
<<%= t('invoices.deposit') %><%= t('offers.price_in_currency', price: @invoice.deposit) %>
<%= t('invoices.total_paid') %><%= t('offers.price_in_currency', price: Money.from_amount(@invoice.paid_amount || 0, @invoice.auction_currency)) %>
<%= t('invoices.show.total_amount') %> + <% if @invoice.partial_payments? && !@invoice.paid? %> + <%= t('offers.price_in_currency', price: @invoice.due_amount) %> + <% else %> + <%= t('offers.price_in_currency', price: @invoice.total) %> + <% end %> +
+ +
\ No newline at end of file diff --git a/app/components/modals/pay_invoice/invoice_information/component.rb b/app/components/modals/pay_invoice/invoice_information/component.rb new file mode 100644 index 000000000..a80d1d2a0 --- /dev/null +++ b/app/components/modals/pay_invoice/invoice_information/component.rb @@ -0,0 +1,15 @@ +module Modals + module PayInvoice + module InvoiceInformation + class Component < ApplicationViewComponent + attr_reader :invoice + + def initialize(invoice:) + super + + @invoice = invoice + end + end + end + end +end diff --git a/app/components/modals/winner_domain/component.html.erb b/app/components/modals/winner_domain/component.html.erb new file mode 100644 index 000000000..e20b7d52d --- /dev/null +++ b/app/components/modals/winner_domain/component.html.erb @@ -0,0 +1,46 @@ +
+
+
+
+ +
+
+ + <%= t('results.show.title') %> +
+
+ <%= @result.auction.domain_name %> + +
+
+ <%= t('results.status') %> <%= @result.status.humanize %> +
+
+
+
+ + + <%= t('results.show.description') %> +
+
+
+

<%= t('results.pay_invoice_before') %>

+ <%= pay_invoice_before %> +
+
+

<%= t('results.register_before') %>

+ <%= @result.registration_due_date %> +
+
+
+ +
+
+
+
diff --git a/app/components/modals/winner_domain/component.rb b/app/components/modals/winner_domain/component.rb new file mode 100644 index 000000000..e1a8fd655 --- /dev/null +++ b/app/components/modals/winner_domain/component.rb @@ -0,0 +1,34 @@ +module Modals + module WinnerDomain + class Component < ApplicationViewComponent + attr_reader :result + + def initialize(result:) + super + + @result = result + end + + def registration_code_or_information + case result.status + when Result.statuses[:payment_received] + result.registration_code + when Result.statuses[:domain_registered] + I18n.t('results.show.domain_already_registered') + else + I18n.t('results.show.only_available_after_paying_invoice') + end + end + + def pay_invoice_before + if result.payment_received? + I18n.t('.paid') + elsif result&.invoice + result.invoice.due_date + else + I18n.t('invoices.is_being_generated') + end + end + end + end +end diff --git a/app/components/pages/auction/auction_action_button/component.html.erb b/app/components/pages/auction/auction_action_button/component.html.erb new file mode 100644 index 000000000..0a2951ea8 --- /dev/null +++ b/app/components/pages/auction/auction_action_button/component.html.erb @@ -0,0 +1,23 @@ +<% if auction.english? %> + + <% if auction.users_offer_uuid || updated %> + <%# if need to edit offer for english auction %> + <%= component '.edit_english_offer', auction: @auction %> + <% else %> + <%# if need to create new offer for english auction %> + <%= component 'common/links/link_button', link_title: deposit_value[:link_title], href: new_auction_english_offer_path(auction_uuid: auction.uuid), + color: deposit_value[:color], options: { data: { turbo_frame: 'modal' } } %> + <% end %> + +<% else %> + + <% if auction.users_offer_uuid || updated %> + <%# if need to edit or remove offer for blind auction %> + <%= component '.edit_and_remove_blind_offer', auction: @auction, user: @user %> + <% else %> + <%# if need to create new offer for blind auction %> + <%= component 'common/links/link_button', link_title: t('auctions.bid'), href: new_auction_offer_path(auction_uuid: auction.uuid), + color: 'green', options: { data: { turbo_frame: 'modal' } } %> + <% end %> + +<% end %> diff --git a/app/components/pages/auction/auction_action_button/component.rb b/app/components/pages/auction/auction_action_button/component.rb new file mode 100644 index 000000000..badbaba36 --- /dev/null +++ b/app/components/pages/auction/auction_action_button/component.rb @@ -0,0 +1,32 @@ +module Pages + module Auction + module AuctionActionButton + class Component < ApplicationViewComponent + attr_reader :user, :auction, :updated + + def initialize(user:, auction:, updated: false) + super + + @user = user + @auction = auction + @updated = updated + end + + def deposit_value + if auction.allow_to_set_bid?(user) + return { + link_title: I18n.t('auctions.bid'), + color: 'green' + } + + end + + { + link_title: I18n.t('auctions.deposit'), + color: 'orange' + } + end + end + end + end +end diff --git a/app/components/pages/auction/auction_action_button/edit_and_remove_blind_offer/component.html.erb b/app/components/pages/auction/auction_action_button/edit_and_remove_blind_offer/component.html.erb new file mode 100644 index 000000000..97d99a731 --- /dev/null +++ b/app/components/pages/auction/auction_action_button/edit_and_remove_blind_offer/component.html.erb @@ -0,0 +1,10 @@ +
+ <%= component 'common/links/link_button', link_title: nil, href: edit_offer_path(@auction.users_offer_uuid), + color: 'ghost', options: { data: { turbo_frame: 'modal' } } do %> + + <% end %> + <%= component 'common/buttons/button_to', title_caption: nil, href: offer_path(@auction.offer_from_user(@user).uuid), color: 'ghost', + options: { method: :delete, form: { data: { turbo_confirm: t(".confirm_delete") } } } do %> + + <% end %> +
diff --git a/app/components/pages/auction/auction_action_button/edit_and_remove_blind_offer/component.rb b/app/components/pages/auction/auction_action_button/edit_and_remove_blind_offer/component.rb new file mode 100644 index 000000000..3959d68a1 --- /dev/null +++ b/app/components/pages/auction/auction_action_button/edit_and_remove_blind_offer/component.rb @@ -0,0 +1,18 @@ +module Pages + module Auction + module AuctionActionButton + module EditAndRemoveBlindOffer + class Component < ApplicationViewComponent + attr_reader :user, :auction + + def initialize(user:, auction:) + super + + @user = user + @auction = auction + end + end + end + end + end +end diff --git a/app/components/pages/auction/auction_action_button/edit_english_offer/component.html.erb b/app/components/pages/auction/auction_action_button/edit_english_offer/component.html.erb new file mode 100644 index 000000000..f35ad3610 --- /dev/null +++ b/app/components/pages/auction/auction_action_button/edit_english_offer/component.html.erb @@ -0,0 +1,6 @@ +
+ <%= component 'common/links/link_button', link_title: nil, href: edit_english_offer_path(@auction.users_offer_uuid), + color: 'ghost', options: { data: { turbo_frame: 'modal' } } do %> + + <% end %> +
diff --git a/app/components/pages/auction/auction_action_button/edit_english_offer/component.rb b/app/components/pages/auction/auction_action_button/edit_english_offer/component.rb new file mode 100644 index 000000000..2198372c6 --- /dev/null +++ b/app/components/pages/auction/auction_action_button/edit_english_offer/component.rb @@ -0,0 +1,17 @@ +module Pages + module Auction + module AuctionActionButton + module EditEnglishOffer + class Component < ApplicationViewComponent + attr_reader :auction + + def initialize(auction:) + super + + @auction = auction + end + end + end + end + end +end \ No newline at end of file diff --git a/app/components/pages/auction/cards/component.html.erb b/app/components/pages/auction/cards/component.html.erb new file mode 100644 index 000000000..3d4f61f74 --- /dev/null +++ b/app/components/pages/auction/cards/component.html.erb @@ -0,0 +1,67 @@ + +
+
+
+
+
+

+ + + + <%= t('auctions.top_grid.1_step_header') %> +

+
+
+
<%= t('auctions.top_grid.1_step_explanation') %>
+
+
+
+
+
+
+

+ + + + + + + + + + + + + <%= t('auctions.top_grid.2_step_header') %> +

+
+
+
<%= t('auctions.top_grid.2_step_explanation') %>
+
+
+
+
+
+
+

+ + + + <%= t('auctions.top_grid.3_step_header') %> +

+
+
+
<%= t('auctions.top_grid.3_step_explanation') %>
+
+
+
+
+
+
+
+ diff --git a/app/components/pages/auction/cards/component.rb b/app/components/pages/auction/cards/component.rb new file mode 100644 index 000000000..1aae55710 --- /dev/null +++ b/app/components/pages/auction/cards/component.rb @@ -0,0 +1,7 @@ +module Pages + module Auction + module Cards + class Component < ApplicationViewComponent; end + end + end +end diff --git a/app/components/pages/auction/filter/component.html.erb b/app/components/pages/auction/filter/component.html.erb new file mode 100644 index 000000000..ff56f8f2a --- /dev/null +++ b/app/components/pages/auction/filter/component.html.erb @@ -0,0 +1,29 @@ +<%= form_with url: auctions_path, method: :get, data: { **form_data_attributes } do |f| %> +
+
+ <%= component 'common/form/radio_button', form: f, title: 'all', attribute: :type, + data_attributes: { **form_filter_target }, options: { **radio_btn_style, id: 'filter_all_button', checked: true} %> + <%= f.label :type_all, t('auction.all'), class: "c-table__filters__btn is-active js-table-filter-btn", style: 'cursor: pointer;', for: 'filter_all_button', data: { **form_filter_data_attrs } %> + + <%= component 'common/form/radio_button', form: f, title: 'english', attribute: :type, data_attributes: { **form_filter_target }, options: { **radio_btn_style, id: 'filter_IO_button', checked: false} %> + <%= f.label :type_english, class: "c-table__filters__btn js-table-filter-btn", style: 'cursor: pointer;', for: 'filter_IO_button', data: { **form_filter_data_attrs } do %> + <%= t('auctions.english_auction') %> + <% end %> + + <%= component 'common/form/radio_button', form: f, title: 'blind', attribute: :type, + data_attributes: { **form_filter_target }, options: { **radio_btn_style, id: 'filter_PO_button', checked: false} %> + <%= f.label :type_blind, class: "c-table__filters__btn js-table-filter-btn", style: 'cursor: pointer;', for: 'filter_PO_button', data: { **form_filter_data_attrs } do %> + <%= t('auctions.blind_auction') %> + <% end %> +
+ + + +
+
<%= t('auctions.english_auction').downcase %>
+
<%= t('auctions.blind_auction').downcase %>
+
+
+<% end %> diff --git a/app/components/pages/auction/filter/component.rb b/app/components/pages/auction/filter/component.rb new file mode 100644 index 000000000..9f22d0346 --- /dev/null +++ b/app/components/pages/auction/filter/component.rb @@ -0,0 +1,44 @@ +module Pages + module Auction + module Filter + class Component < ApplicationViewComponent + attr_reader :current_user + + def initialize(current_user:) + super + + @current_user = current_user + end + + def form_data_attributes + { + controller: 'form--debounce form--filter', + form__debounce_target: 'form', + form__filter_target: 'form', + turbo_action: 'advance', + turbo_frame: 'results', + action: 'input->form--debounce#search' + } + end + + def radio_btn_style + { + style: 'visibility: hidden; position: absolute;' + } + end + + def form_filter_target + { + form__filter_target: 'button' + } + end + + def form_filter_data_attrs + { + form__filter_target: 'label', action: "click->form--filter#click" + } + end + end + end + end +end diff --git a/app/components/pages/auction/notification_subscribe/component.html.erb b/app/components/pages/auction/notification_subscribe/component.html.erb new file mode 100644 index 000000000..0e3120e9c --- /dev/null +++ b/app/components/pages/auction/notification_subscribe/component.html.erb @@ -0,0 +1,6 @@ +<% if @current_user.daily_summary %> + <%= component 'common/links/link_button', link_title: t('auctions.unsubscribe_for_notifications'), href: user_toggle_sub_path, color: 'ghost' %> +<% else %> + <%= component 'common/links/link_button', link_title: t('auctions.subscribe_for_notifications'), href: user_toggle_sub_path(@current_user), color: 'ghost' %> +<% end %> + diff --git a/app/components/pages/auction/notification_subscribe/component.rb b/app/components/pages/auction/notification_subscribe/component.rb new file mode 100644 index 000000000..cd0db0b3a --- /dev/null +++ b/app/components/pages/auction/notification_subscribe/component.rb @@ -0,0 +1,15 @@ +module Pages + module Auction + module NotificationSubscribe + class Component < ApplicationViewComponent + attr_reader :current_user + + def initialize(current_user:) + super + + @current_user = current_user + end + end + end + end +end \ No newline at end of file diff --git a/app/components/pages/invoices/outstanding_invoices/component.html.erb b/app/components/pages/invoices/outstanding_invoices/component.html.erb new file mode 100644 index 000000000..6ed4093b8 --- /dev/null +++ b/app/components/pages/invoices/outstanding_invoices/component.html.erb @@ -0,0 +1,83 @@ +
+

<%= t('invoices.outstanding') %>

+
+
+ + + + +
+ <%= tag.div class: 'c-tabs__nav__btn--ind', style:'margin-right: auto' do %> + <%= button_to pay_all_issued_invoices_path, method: :post, style: 'background-color: inherit; color: inherit; text-decoration: none; border: none; box-shadow: none;' do %> + <%= t('invoices.pay_all') %> (<%= @issued_invoices.count %>) + <% end %> + <% end if @issued_invoices.count.positive? %> +
+ +
+ <%= tag.div class: 'c-tabs__nav__btn--ind' do %> + <%= button_to pay_all_cancelled_payable_invoices_path, method: :post, style: 'background-color: inherit; color: inherit; text-decoration: none; border: none; box-shadow: none;' do %> + <%= t('invoices.pay_all') %> (<%= @cancelled_payable_invoices.count %>) + <% end %> + <% end if @cancelled_payable_invoices.count.positive? %> +
+ +
+
+
+
+ + + <%= component 'common/table', header_collection: issued_invoice_table_headers, options: { class: '' } do %> + + <% @issued_invoices.each do |invoice| %> + + <%= link_to t('invoice_items.name', domain_name: invoice&.result&.auction&.domain_name, auction_end: invoice&.result&.auction&.ends_at&.to_date), invoice_path(invoice.uuid), title: "Invoice", data: { turbo_frame: 'modal' }, style: "color: black;" %> + <%= I18n.l(invoice.due_date) %> + <%= t('offers.price_in_currency', price: invoice.total) %> + <%= link_to t('invoices.show.pay'), invoice_path(invoice.uuid), class: "c-btn c-btn--green", title: "Invoice", data: { turbo_frame: 'modal' } %> + + <% end %> + <% end %> +
+
+ +
+
+ <%= component 'common/table', header_collection: overdue_invoice_table_headers, options: { class: '' } do %> + <% @cancelled_payable_invoices.each do |invoice| %> + + <%= link_to t('invoice_items.name', domain_name: invoice&.result&.auction&.domain_name, auction_end: invoice&.result&.auction&.ends_at&.to_date), invoice_path(invoice.uuid), title: "Invoice", data: { turbo_frame: 'modal' }, style: "color: black;" %> + <%= I18n.l(invoice.due_date) %> + <%= t('offers.price_in_currency', price: invoice.total) %> + <%= link_to t('invoices.show.pay'), invoice_path(invoice.uuid), class: "c-btn c-btn--green", title: "Invoice", data: { turbo_frame: 'modal' } %> + + <% end %> + <% end %> +
+
+
+
+ <%= component 'common/table', header_collection: cancel_invoice_table_headers, options: { class: '' } do %> + <% @cancelled_expired_invoices.each do |invoice| %> + + <%= link_to t('invoice_items.name', domain_name: invoice&.result&.auction&.domain_name, auction_end: invoice&.result&.auction&.ends_at&.to_date), invoice_path(invoice.uuid), title: "Invoice", data: { turbo_frame: 'modal' }, style: "color: black;" %> + <%= I18n.l(invoice.due_date) %> + <%= t('offers.price_in_currency', price: invoice.total) %> + <%= link_to t('invoices.show_invoice'), invoice_path(invoice.uuid), class: "c-btn c-btn--ghost js-modal-toggle", title: "Invoice", data: { turbo_frame: 'modal' } %> + + <% end %> + <% end %> +
+
+
+
+
diff --git a/app/components/pages/invoices/outstanding_invoices/component.rb b/app/components/pages/invoices/outstanding_invoices/component.rb new file mode 100644 index 000000000..111e54487 --- /dev/null +++ b/app/components/pages/invoices/outstanding_invoices/component.rb @@ -0,0 +1,47 @@ +module Pages + module Invoices + module OutstandingInvoices + class Component < ApplicationViewComponent + attr_reader :issued_invoices, :cancelled_payable_invoices, :cancelled_expired_invoices, + :unpaid_invoices_count + + def initialize(issued_invoices:, cancelled_payable_invoices:, cancelled_expired_invoices:, + unpaid_invoices_count:) + super + + @issued_invoices = issued_invoices + @cancelled_payable_invoices = cancelled_payable_invoices + @cancelled_expired_invoices = cancelled_expired_invoices + @unpaid_invoices_count = unpaid_invoices_count + end + + def issued_invoice_table_headers + # NB! if you want make columns as sortable, don't forget to wrap the table content within the turbo_frame_tag + # with the name "results". The name "results" is assigned as a value for the orderable Stimulus controller + # in the table component. + [{ column: nil, caption: t('invoices.item'), options: { class: '' } }, + { column: nil, caption: t('invoices.due_date'), options: { class: '' } }, + { column: nil, caption: t('invoices.total'), options: { class: '' } }, + { column: nil, caption: t('actions_name'), + options: { class: 'u-text-center-l' } }] + end + + def overdue_invoice_table_headers + [{ column: nil, caption: t('invoices.item'), options: { class: '' } }, + { column: nil, caption: t('invoices.due_date'), options: { class: '' } }, + { column: nil, caption: t('invoices.total'), options: { class: '' } }, + { column: nil, caption: t('actions_name'), + options: { class: 'u-text-center-l' } }] + end + + def cancel_invoice_table_headers + [{ column: nil, caption: t('invoices.item'), options: { class: '' } }, + { column: nil, caption: t('invoices.due_date'), options: { class: '' } }, + { column: nil, caption: t('invoices.total'), options: { class: '' } }, + { column: nil, caption: t('actions_name'), + options: { class: 'u-text-center-l' } }] + end + end + end + end +end diff --git a/app/components/pages/invoices/paid_invoices/component.html.erb b/app/components/pages/invoices/paid_invoices/component.html.erb new file mode 100644 index 000000000..b373464f3 --- /dev/null +++ b/app/components/pages/invoices/paid_invoices/component.html.erb @@ -0,0 +1,48 @@ +
+

<%= t('invoices.paid') %>

+
+
+ + + +
+ +
+
+
+ <%= component 'common/table', header_collection: paid_invoices_headers, options: { class: '' } do %> + <% @paid_invoices.each do |invoice| %> + + <%= link_to t('invoice_items.name', domain_name: invoice&.result&.auction&.domain_name, auction_end: invoice&.result&.auction&.ends_at&.to_date), invoice_path(invoice.uuid), title: "Invoice", data: { turbo_frame: 'modal' }, style: "color: black;" %> + <%= I18n.l(invoice.due_date) %> + <%= invoice.total %> + <%= link_to t('invoices.show_invoice'), invoice_path(invoice.uuid), class: "c-btn c-btn--ghost js-modal-toggle", title: "Invoice", data: { turbo_frame: 'modal' } %> + + <% end %> + <% end %> +
+
+ +
+
+ <%= component 'common/table', header_collection: deposit_paid_headers, options: { class: '' } do %> + <% @deposit_paid.each do |deposit| %> + + <%= deposit.created_at.strftime("%Y-%m-%d") %> + <%= deposit.auction.deposit %> € + <%= deposit.auction.domain_name %> + <%= component 'common/badgets', status: deposit.status %> <%#= t("invoices.paid_deposit.#{deposit.status}") %> + <%= deposit.refund_time ? I18n.l(deposit.refund_time) : 'N/A' %> + + <% end %> + <% end %> +
+
+ +
+
+
diff --git a/app/components/pages/invoices/paid_invoices/component.rb b/app/components/pages/invoices/paid_invoices/component.rb new file mode 100644 index 000000000..799311924 --- /dev/null +++ b/app/components/pages/invoices/paid_invoices/component.rb @@ -0,0 +1,31 @@ +module Pages + module Invoices + module PaidInvoices + class Component < ApplicationViewComponent + attr_reader :paid_invoices, :deposit_paid + + def initialize(paid_invoices:, deposit_paid:) + super + + @paid_invoices = paid_invoices + @deposit_paid = deposit_paid + end + + def paid_invoices_headers + [{ column: nil, caption: t('invoices.item'), options: { class: '' } }, + { column: nil, caption: t('invoices.due_date'), options: { class: '' } }, + { column: nil, caption: t('invoices.total'), options: { class: '' } }, + { column: nil, caption: t('actions_name'), options: { class: 'u-text-center-l' } }] + end + + def deposit_paid_headers + [{ column: nil, caption: t('invoices.paid_deposit.date'), options: { class: '' } }, + { column: nil, caption: t('invoices.paid_deposit.sum'), options: { class: '' } }, + { column: nil, caption: t('invoices.paid_deposit.auction_name'), options: { class: '' } }, + { column: nil, caption: t('invoices.paid_deposit.status'), options: { class: '' } }, + { column: nil, caption: t('invoices.paid_deposit.refund_time'), options: { class: '' } }] + end + end + end + end +end diff --git a/app/components/pages/offers/blind_auction_offer_table/component.html.erb b/app/components/pages/offers/blind_auction_offer_table/component.html.erb new file mode 100644 index 000000000..86e477c4d --- /dev/null +++ b/app/components/pages/offers/blind_auction_offer_table/component.html.erb @@ -0,0 +1,10 @@ + <%= component 'common/table', header_collection: header_collection, options: { class: 'table--black' } do %> + <%= tag.tbody do %> + +

<%= @offer.auction.domain_name %>

+ <%= @offer.auction.ends_at %> + <%= t('offers.price_in_currency', price: @offer.price) %> + <%= t('offers.price_in_currency', price: @offer.total) %> + + <% end %> + <% end %> diff --git a/app/components/pages/offers/blind_auction_offer_table/component.rb b/app/components/pages/offers/blind_auction_offer_table/component.rb new file mode 100644 index 000000000..beb62d87c --- /dev/null +++ b/app/components/pages/offers/blind_auction_offer_table/component.rb @@ -0,0 +1,22 @@ +module Pages + module Offers + module BlindAuctionOfferTable + class Component < ApplicationViewComponent + attr_reader :offer + + def initialize(offer:) + super + + @offer = offer + end + + def header_collection + [{ column: nil, caption: t('auctions.domain_name'), options: { class: '' } }, + { column: nil, caption: t('auctions.ends_at'), options: { class: '' } }, + { column: nil, caption: t('offers.price'), options: { class: '' } }, + { column: nil, caption: t('offers.total'), options: { class: '' } }] + end + end + end + end +end diff --git a/app/components/pages/offers/english_auction_offer_table/component.html.erb b/app/components/pages/offers/english_auction_offer_table/component.html.erb new file mode 100644 index 000000000..3ad254527 --- /dev/null +++ b/app/components/pages/offers/english_auction_offer_table/component.html.erb @@ -0,0 +1,58 @@ + +
+
+

<%= t('offers.overview') %>

+ <%= component 'common/table', header_collection: overview_table_headers, options: { class: 'table--black' } do %> + <%= tag.tbody do %> + +

<%= @offer.auction.domain_name %>

+ <%= @offer.auction.ends_at %> + <%= t('offers.price_in_currency', price: @offer.price) %> + <%= t('offers.price_in_currency', price: @offer.total) %> + + <% end %> + <% end %> +
+
+ + + +
+
+
+
+

<%= t('offers.show.participants_title') %>

+ <%= component 'common/table', header_collection: bidder_table_headers, options: { class: '' } do %> + <%= tag.tbody do %> + <% @offer.auction.offers.order(updated_at: :desc).each do |offer| %> + + + <%= offer&.billing_profile&.user_id == current_user.id ? "#{offer.username} (You)" : offer.username %> + + <%= t('offers.price_in_currency', price: offer.price) %> + <%= I18n.l(offer.updated_at) %> + + <% end %> + <% end %> + <% end %> +
+
+

<%= t('offers.show.deposit_title') %>

+ <%= component 'common/table', header_collection: deposit_payment_table_headers, options: { class: '' } do %> + <%= tag.tbody do %> + <% @offer.auction.domain_participate_auctions.each do |d| %> + + + <%= d.user.display_name %> + + <%= I18n.l(d.created_at) %> + + <% end %> + <% end %> + <% end %> + +
+
+
+
+ diff --git a/app/components/pages/offers/english_auction_offer_table/component.rb b/app/components/pages/offers/english_auction_offer_table/component.rb new file mode 100644 index 000000000..8ffe57b8d --- /dev/null +++ b/app/components/pages/offers/english_auction_offer_table/component.rb @@ -0,0 +1,33 @@ +module Pages + module Offers + module EnglishAuctionOfferTable + class Component < ApplicationViewComponent + attr_reader :offer + + def initialize(offer:) + super + + @offer = offer + end + + def overview_table_headers + [{ column: nil, caption: t('auctions.domain_name'), options: { class: '' } }, + { column: nil, caption: t('auctions.ends_at'), options: { class: '' } }, + { column: nil, caption: t('offers.price'), options: { class: '' } }, + { column: nil, caption: t('offers.total'), options: { class: '' } }] + end + + def bidder_table_headers + [{ column: nil, caption: t('offers.show.participants'), options: { class: '' } }, + { column: nil, caption: t('offers.show.your_last_offer'), options: { class: '' } }, + { column: nil, caption: t('offers.show.offers_time'), options: { class: '' } }] + end + + def deposit_payment_table_headers + [{ column: nil, caption: t('offers.show.deposit_participants'), options: { class: '' } }, + { column: nil, caption: t('offers.show.offers_time'), options: { class: '' } }] + end + end + end + end +end diff --git a/app/components/pages/offers/offers_table/component.html.erb b/app/components/pages/offers/offers_table/component.html.erb new file mode 100644 index 000000000..69cae5e0f --- /dev/null +++ b/app/components/pages/offers/offers_table/component.html.erb @@ -0,0 +1,31 @@ + + +
+ <%= form_with url: offers_path, + method: :get, data: { controller: 'form--debounce form--filter', + form__debounce_target: 'form', + form__filter_target: 'form', + turbo_action: "advance", + turbo_frame: "results", + action: 'input->form--debounce#search' + } do |f| %> + + <%# TODO: Add filter and search feature %> + + <% end %> + +
+ + + <%= turbo_frame_tag "results" do %> + + + <%= component 'common/table', header_collection: header_collection, options: { class: 'js-table-dt dataTable no-footer' } do %> + + <%= render @offers %> + <% end %> + <% end %> +
+
diff --git a/app/components/pages/offers/offers_table/component.rb b/app/components/pages/offers/offers_table/component.rb new file mode 100644 index 000000000..427337c4f --- /dev/null +++ b/app/components/pages/offers/offers_table/component.rb @@ -0,0 +1,26 @@ +module Pages + module Offers + module OffersTable + class Component < ApplicationViewComponent + attr_reader :offers + + def initialize(offers:) + super + + @offers = offers + end + + def header_collection + [{ column: 'auctions.domain_name', caption: t('auctions.domain_name'), options: { class: 'sorting' } }, + { column: 'auctions.platform', caption: t('auctions.type'), options: { class: 'sorting' } }, + { column: nil, caption: t('offers.show.your_last_offer'), options: { class: '' } }, + { column: nil, caption: t('offers.total'), options: { class: '' } }, + { column: 'auctions.ends_at', caption: t('auctions.ends_at'), + options: { class: 'sorting' } }, + { column: nil, caption: t('result_name'), options: {} }, + { column: nil, caption: t('auctions.offer_actions'), options: {} }] + end + end + end + end +end diff --git a/app/controllers/admin/auctions_controller.rb b/app/controllers/admin/auctions_controller.rb index 40f6962d2..db455c920 100644 --- a/app/controllers/admin/auctions_controller.rb +++ b/app/controllers/admin/auctions_controller.rb @@ -56,6 +56,8 @@ def destroy end end + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength def bulk_starts_at results = AdminBulkActionService.apply_for_english_auction(auction_elements: params[:auction_elements]) skipped_auctions = results[0] @@ -89,12 +91,10 @@ def apply_auction_participants def validate_enable_and_disable_option_in_same_action auction_elements = params[:auction_elements] - if auction_elements[:enable_deposit] == 'true' && - auction_elements[:disable_deposit] == 'true' + return unless auction_elements[:enable_deposit] == 'true' && auction_elements[:disable_deposit] == 'true' - flash[:alert] = 'it cannot be enable and disable deposit in same action' - redirect_to admin_auctions_path and return - end + flash[:alert] = 'it cannot be enable and disable deposit in same action' + redirect_to admin_auctions_path and return end def create_params diff --git a/app/controllers/admin/invoices_controller.rb b/app/controllers/admin/invoices_controller.rb index 2818a554a..d1e429d7d 100644 --- a/app/controllers/admin/invoices_controller.rb +++ b/app/controllers/admin/invoices_controller.rb @@ -4,8 +4,8 @@ module Admin class InvoicesController < BaseController before_action :authorize_user - before_action :create_invoice_if_needed - before_action :set_invoice, only: %i[show download update edit] + before_action :create_invoice_if_needed, except: :toggle_partial_payments + before_action :set_invoice, only: %i[show download update edit toggle_partial_payments] before_action :authorize_for_update, only: %i[edit update] # GET /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b @@ -67,6 +67,22 @@ def update end end + # POST /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/toggle_partial_payments + def toggle_partial_payments + respond_to do |format| + if @invoice.toggle(:partial_payments).save + format.html do + action = @invoice.partial_payments? ? 'activated' : 'deactivated' + redirect_to admin_invoice_path(@invoice), notice: t("invoices.partial_payments_#{action}") + end + format.json { render :show, status: :ok, location: @invoice } + else + format.html { redirect_to admin_invoice_path(@invoice), notice: t(:something_went_wrong) } + format.json { render json: @invoice.errors, status: :unprocessable_entity } + end + end + end + private def set_invoice diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d29ebe7cb..2a6b07b5f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,25 +1,24 @@ class ApplicationController < ActionController::Base include Pagy::Backend + helper_method :turbo_frame_request? + protect_from_forgery with: :exception - before_action :set_locale + before_action :set_locale, :clear_flash + before_action :set_notifications content_security_policy do |policy| policy.style_src :self, 'www.gstatic.com', :unsafe_inline end - rescue_from CanCan::AccessDenied do |_exception| + rescue_from CanCan::AccessDenied do |exception| flash[:alert] = I18n.t('unauthorized.message') - redirect_to root_url - end - def set_locale - if params[:localize].present? && I18n.available_locales.include?(params[:localize].to_sym) - cookies[:locale] = params[:localize] + if turbo_frame_request? + render turbo_stream: turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + else + redirect_to root_path end - - I18n.locale = current_user&.locale || cookies[:locale] || I18n.default_locale - @pagy_locale = I18n.locale.to_s end def store_location @@ -36,4 +35,44 @@ def merge_updated_by(update_params) update_params end end + + def after_sign_in_path_for(_resource) + root_path + end + + def set_notifications + # don't change the name, it's used in the header and can be conflict with notification variable in notifications page + @notifications_for_header = current_user&.notifications&.unread&.order(created_at: :desc)&.limit(5) + end + + private + + def set_locale + if params[:localize].present? && I18n.available_locales.include?(params[:localize].to_sym) + cookies[:locale] = params[:localize] + end + + I18n.locale = current_user&.locale || cookies[:locale] || I18n.default_locale + @pagy_locale = I18n.locale.to_s + end + + def clear_flash + flash.clear if turbo_frame_request? + end + + protected + + def authenticate_user! + return if user_signed_in? + + sign_out @user + + flash[:alert] = t('devise.failure.unauthenticated') + + if turbo_frame_request? + render turbo_stream: turbo_stream.action(:redirect, root_path) + else + redirect_to root_path + end + end end diff --git a/app/controllers/auctions_controller.rb b/app/controllers/auctions_controller.rb index c5d564795..fd9b0bd90 100644 --- a/app/controllers/auctions_controller.rb +++ b/app/controllers/auctions_controller.rb @@ -19,13 +19,6 @@ def cors_preflight_check render plain: '' end - # GET /auctions/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b - def show - @auction = Auction.with_user_offers(current_user) - .accessible_by(current_ability) - .find_by!(uuid: params[:uuid]) - end - private def fetch_auctions_list @@ -37,7 +30,7 @@ def fetch_auctions_list end def should_sort_auctions? - params[:sort].blank? && params[:direction].blank? + params[:sort_by].blank? && params[:sort_direction].blank? end def per_page_count diff --git a/app/controllers/autobider_controller.rb b/app/controllers/autobider_controller.rb index 21c9d7755..80cf4785e 100644 --- a/app/controllers/autobider_controller.rb +++ b/app/controllers/autobider_controller.rb @@ -2,14 +2,16 @@ class AutobiderController < ApplicationController before_action :authenticate_user! before_action :set_auction, only: %i[new edit update create] before_action :allow_any_action_with_autobider + include RecaptchaValidatable + recaptcha_action 'autobidder' def update @autobider = Autobider.find_by(uuid: params[:uuid]) if recaptcha_valid if @autobider.update(strong_params) - auction = Auction.where(domain_name: @autobider.domain_name).order(:created_at).last + auction = Auction.where(domain_name: @autobider.domain_name).last AutobiderService.autobid(auction) unless skip_autobid(auction) flash[:notice] = I18n.t('english_offers.form.autobidder_updated') @@ -17,9 +19,14 @@ def update flash[:alert] = I18n.t('something_went_wrong') end else + @show_checkbox_recaptcha = true unless @success flash[:alert] = t('english_offers.form.captcha_verification') end - redirect_to request.referer + + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }), + turbo_stream.update('autobider-status', html: @autobider.enable ? I18n.t('english_offers.form.yep') : I18n.t('english_offers.form.nope')) + ] end def edit @@ -40,13 +47,16 @@ def create auction = Auction.where(domain_name: @autobider.domain_name).order(:created_at).last AutobiderService.autobid(auction) - redirect_to request.referer, notice: I18n.t('english_offers.form.autobidder_created') + flash[:notice] = t('english_offers.form.autobidder_created') else - redirect_to request.referer, notice: t(:something_went_wrong) + flash[:alert] = t('something_went_wrong') end else - redirect_to request.referer, alert: t('english_offers.form.captcha_verification') + @show_checkbox_recaptcha = true unless @success + flash[:alert] = t('english_offers.form.captcha_verification') end + + render turbo_stream: turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) end private @@ -81,7 +91,7 @@ def restrict_for_banned_user(domain_name) end def strong_params - params.require(:autobider).permit(:user_id, :domain_name, :price) + params.require(:autobider).permit(:user_id, :domain_name, :price, :enable) end def create_predicate diff --git a/app/controllers/billing_profiles_controller.rb b/app/controllers/billing_profiles_controller.rb index db27a29fe..e85b26cf8 100644 --- a/app/controllers/billing_profiles_controller.rb +++ b/app/controllers/billing_profiles_controller.rb @@ -1,9 +1,15 @@ +# rubocop:disable Metrics class BillingProfilesController < ApplicationController before_action :authenticate_user! before_action :set_billing_profile, only: %i[show edit update destroy] before_action :authorize_billing_profile_for_user, except: %i[new index create] before_action :store_location, only: :new + rescue_from ActiveRecord::RecordNotUnique do |_exception| + flash[:alert] = t('billing_profiles.vat_code_already_exists') + render turbo_stream: turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + end + # GET /billing_profiles def index @billing_profiles = BillingProfile.accessible_by(current_ability) @@ -13,9 +19,12 @@ def index # GET /billing_profiles/new def new @billing_profile = BillingProfile.new(user_id: current_user.id) + + return unless turbo_frame_request? + + render partial: 'new_form', locals: { billing_profile: @billing_profile } end - # POST /billing_profiles def create @billing_profile = BillingProfile.new(create_params) authorize! :manage, @billing_profile @@ -23,10 +32,27 @@ def create respond_to do |format| if create_predicate redirect_uri = "#{session[:return_to]}?billing_profile_id=#{@billing_profile.id}" || billing_profile_path(@billing_profile.uuid) - format.html { redirect_to redirect_uri, notice: t('.created') } + flash[:notice] = t(:created) + + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }), + turbo_stream.prepend('billing_profile_containers', partial: 'billing_profiles/billing_info', + locals: { billing_profile: @billing_profile }), + turbo_stream.remove('new_form') + ] + end + format.html { redirect_to redirect_uri } format.json { render :show, status: :created, location: @billing_profile } else - format.html { render :new } + flash.now[:alert] = @billing_profile.errors.full_messages.to_sentence + + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + ] + end + format.html { render :new, status: :unprocessable_entity } format.json { render json: @billing_profile.errors, status: :unprocessable_entity } end end @@ -36,16 +62,40 @@ def create def show; end # GET /billing_profiles/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit - def edit; end + def edit + if turbo_frame_request? + render turbo_frame: 'billing_profile_form', partial: 'form', locals: { billing_profile: @billing_profile } + else + render partial: 'form', locals: { billing_profile: @billing_profile } + end + end # PUT /billing_profiles/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def update respond_to do |format| if update_predicate - format.html { redirect_to billing_profile_path(@billing_profile.uuid), notice: t('.updated') } + flash[:notice] = t(:updated) + + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }), + turbo_stream.update(@billing_profile, partial: 'billing_profiles/billing_info', + locals: { billing_profile: @billing_profile }) + ] + end + + format.html { redirect_to billing_profiles_path, notice: t(:updated) } format.json { render :show, status: :ok, location: @billing_profile } else - format.html { render :edit } + flash[:alert] = @billing_profile.errors.full_messages.to_sentence + + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + ] + end + + format.html { render :edit, status: :unprocessable_entity } format.json { render json: @billing_profile.errors, status: :unprocessable_entity } end end @@ -55,9 +105,17 @@ def update def destroy if @billing_profile.deletable? @billing_profile.destroy! - redirect_to billing_profiles_path, notice: t('.deleted') + + flash[:notice] = t(:deleted) + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }), + turbo_stream.remove(@billing_profile) + ] else - redirect_to billing_profiles_path, notice: @billing_profile.errors[:base].to_sentence + flash[:alert] = @billing_profile.errors.full_messages.to_sentence + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + ] end end diff --git a/app/controllers/concerns/english_offers/offerable.rb b/app/controllers/concerns/english_offers/offerable.rb new file mode 100644 index 000000000..f1dc6e388 --- /dev/null +++ b/app/controllers/concerns/english_offers/offerable.rb @@ -0,0 +1,85 @@ +module EnglishOffers + module Offerable + extend ActiveSupport::Concern + + included do + before_render :find_or_initialize_autobidder, only: %i[new create edit update] + before_action :prevent_check_for_invalid_bid, only: [:update] + end + + protected + + def broadcast_update_auction_offer(auction) = Offers::UpdateBroadcastService.call({ auction: }) + + def update_auction_values(auction, message_text) + AutobiderService.autobid(auction) + auction.update_ends_at(@offer) + + flash[:notice] = message_text + redirect_to root_path + end + + def inform_about_invalid_bid_amount + flash[:alert] = t('english_offers.create.bid_must_be', minimum: formatted_starting_price) + + if turbo_frame_request? + render turbo_stream: turbo_stream.action(:redirect, root_path) + else + redirect_to root_path, status: :see_other + end + end + + def find_or_initialize_autobidder + @autobider = current_user&.autobiders&.find_or_initialize_by(domain_name: @auction.domain_name) + end + + # rubocop:disable Metrics/AbcSize + def prevent_check_for_invalid_bid + auction = Auction.with_user_offers(current_user.id).find_by(uuid: @offer.auction.uuid) + return unless bid_is_bad?(auction:, update_params:) + + flash[:alert] = I18n.t('english_offers.show.bid_failed', price: auction_highest_prrice_message(auction)) + + if turbo_frame_request? + render turbo_stream: turbo_stream.action(:redirect, root_path) + else + redirect_to root_path, status: :see_other + end + end + + def check_first_bid_for_english_auction(params, auction) + return true if auction.blind? + + starting_price = auction.starting_price + price = params[:price].to_f + + price.to_f >= starting_price.to_f + end + + private + + def auction_highest_prrice_message(auction) = format('%.2f', auction.highest_price.to_f).tr('.', ',') + + def formatted_starting_price = format('%.2f', @auction.starting_price) + + def bid_is_bad?(auction:, update_params:) + !additional_check_for_bids(auction, update_params[:price]) || + !check_bids_for_english_auction(update_params, auction) + end + + def additional_check_for_bids(auction, current_bid) + order = auction.offers.order(updated_at: :desc).first + + Money.new(order.cents).to_f < current_bid.to_f + end + + def check_bids_for_english_auction(params, auction) + return true if auction.blind? + + minimum = auction.min_bids_step.to_f + price = params[:price].to_f + + price >= minimum + end + end +end diff --git a/app/controllers/concerns/offer_notifable.rb b/app/controllers/concerns/offer_notifable.rb index 39e0e96ff..9adb4c469 100644 --- a/app/controllers/concerns/offer_notifable.rb +++ b/app/controllers/concerns/offer_notifable.rb @@ -2,22 +2,32 @@ module OfferNotifable extend ActiveSupport::Concern def send_outbided_notification(auction:, offer:, flash:) - participant_id = last_higher_bidder(auction) - participant = User.find(participant_id) + participant_ids = last_higher_bidder(auction) + participants = User.where(id: participant_ids) - OfferNotification.with(offer: offer).deliver_later(participant) if offer.present? - - flash[:notice] = "websocket_domain_name, #{auction.domain_name}" - broadcast_outbid_to_notifications(participant: participant, flash: flash) + participants.each do |participant| + prepend_data(participant:, auction:, offer:, flash:) + end end + private + def last_higher_bidder(auction) auction.offers.order(cents: :desc).limit(2).pluck(:user_id) - [current_user.id] end + def prepend_data(participant:, auction:, offer:, flash:) + I18n.with_locale(participant.locale || I18n.default_locale) do + OfferNotification.with(offer:).deliver_later(participant) if offer.present? + + flash[:notice] = I18n.t('.participant_outbid', domain_name: auction.domain_name) + broadcast_outbid_to_notifications(participant:, flash:) + end + end + def broadcast_outbid_to_notifications(participant:, flash:) Turbo::StreamsChannel.broadcast_update_to( - [participant, :flash], target: 'flash-notice', partial: 'common/flash', locals: { flash: flash} + [participant, :flash], target: 'flash-notice', partial: 'common/flash', locals: { flash: } ) end end diff --git a/app/controllers/concerns/offerable.rb b/app/controllers/concerns/offerable.rb new file mode 100644 index 000000000..e72d45e94 --- /dev/null +++ b/app/controllers/concerns/offerable.rb @@ -0,0 +1,65 @@ +module Offerable + extend ActiveSupport::Concern + + included do + before_action :authenticate_user! + before_action :find_auction, only: %i[new create] + before_action :check_for_ban, only: :create + before_action :authorize_phone_confirmation + end + + protected + + def check_for_ban + is_ban = Ban.valid.where(user_id: current_user).where(domain_name: @auction.domain_name).any? + is_ban ||= current_user.completely_banned? + return unless is_ban + + redirect_to root_path, flash: { alert: I18n.t("#{params[:controller]}.create.ban") } and return + end + + def inform_invalid_captcha + @show_checkbox_recaptcha = true unless @success + flash[:alert] = t('offers.form.captcha_verification') + redirect_to root_path, status: :see_other + end + + private + + def authorize_offer_for_user = authorize!(:manage, @offer) + + def update_not_allowed(auction) = if params[:controller] == 'english_offers' + !auction.english? || !auction.in_progress? + else + auction.english? || !auction.in_progress? + end + + def prevent_check_for_existed_offer = if turbo_frame_request? + render turbo_stream: turbo_stream.action(:redirect, root_path), + notice: t('offers.already_exists') + else + redirect_to root_path, status: :see_other, + notice: t('offers.already_exists') + end + + def set_offer = @offer = current_user.offers.find_by!(uuid: params[:uuid]) + + def find_auction + controller_name = params[:controller] + platform = controller_name == 'english_offers' ? 'english' : ['blind', nil] + + @auction = Auction.find_by!(uuid: params[:auction_uuid], platform:) + end + + def authorize_phone_confirmation + return unless current_user.requires_phone_number_confirmation? + + flash[:notice] = t('phone_confirmations.confirmation_required') + + if turbo_frame_request? + render turbo_stream: turbo_stream.action(:redirect, new_user_phone_confirmation_path(current_user.uuid)) + else + redirect_to new_user_phone_confirmation_path(current_user.uuid), status: :see_other + end + end +end diff --git a/app/controllers/concerns/recaptcha_validatable.rb b/app/controllers/concerns/recaptcha_validatable.rb index 910d54a9b..c4585d463 100644 --- a/app/controllers/concerns/recaptcha_validatable.rb +++ b/app/controllers/concerns/recaptcha_validatable.rb @@ -3,7 +3,7 @@ module RecaptchaValidatable included do before_action :set_captcha_required - before_action :check_recaptcha, only: %i[create update] + before_action :check_recaptcha, only: %i[create update], unless: -> { Rails.env.development? || Rails.env.test? } end module ClassMethods @@ -17,9 +17,7 @@ def recaptcha_action(action) end def set_captcha_required - # return if Rails.env.development? - - @captcha_required = current_user.requires_captcha? + @captcha_required = Rails.env.development? ? false : current_user.requires_captcha? @recaptcha2_site_key = AuctionCenter::Application.config.customization.dig(:recaptcha, :recaptcha2_site_key) @recaptcha3_site_key = AuctionCenter::Application.config.customization.dig(:recaptcha, :recaptcha3_site_key) end @@ -37,7 +35,7 @@ def check_recaptcha def recaptcha_valid return true unless @captcha_required - @success || @checkbox_success + @success || @checkbox_success || Rails.env.development? || Rails.env.test? end def min_score diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index 14ffa22a2..81c7fa771 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -41,6 +41,8 @@ def state_machine when 'unpaid' @invoice.update(status: 'issued', paid_at: nil) when 'paid' + return if @invoice.paid? + @invoice.payable? ? @invoice.mark_as_paid_at(Time.zone.now) : @invoice.errors.add(:base, 'Invoice is not payable') when 'cancelled' @invoice.update(status: 'cancelled', paid_at: nil) diff --git a/app/controllers/eis_billing/payment_status_controller.rb b/app/controllers/eis_billing/payment_status_controller.rb index f8341230c..7412469a2 100644 --- a/app/controllers/eis_billing/payment_status_controller.rb +++ b/app/controllers/eis_billing/payment_status_controller.rb @@ -47,7 +47,8 @@ def pay_mulitply(data) end def payment_process(invoice:) - payment_order = PaymentOrder.find_by(invoice_id: invoice.id) || + existing_po = invoice.partial_payments? ? nil : PaymentOrder.find_by(invoice_id: invoice.id) + payment_order = existing_po || PaymentOrders::EveryPay.create(invoices: [invoice], user: invoice.user) payment_order.response = params diff --git a/app/controllers/email_confirmations_controller.rb b/app/controllers/email_confirmations_controller.rb index ae8fac100..92426dc7b 100644 --- a/app/controllers/email_confirmations_controller.rb +++ b/app/controllers/email_confirmations_controller.rb @@ -1,6 +1,19 @@ # frozen_string_literal: true class EmailConfirmationsController < Devise::ConfirmationsController + def create + self.resource = resource_class.send_confirmation_instructions(resource_params) + yield resource if block_given? + + if successfully_sent?(resource) + flash[:notice] = t('devise.confirmations.send_instructions') + respond_with({}, location: after_resending_confirmation_instructions_path_for(resource_name)) + else + flash[:alert] = resource.errors.full_messages.join(', ') + redirect_to root_path, status: :see_other + end + end + protected # The path used after confirmation. diff --git a/app/controllers/english_offers_controller.rb b/app/controllers/english_offers_controller.rb index e9e14d175..a8e87137b 100644 --- a/app/controllers/english_offers_controller.rb +++ b/app/controllers/english_offers_controller.rb @@ -1,16 +1,13 @@ +# frozen_string_literal: true + class EnglishOffersController < ApplicationController include BeforeRender + include Offerable protect_from_forgery with: :null_session - before_action :authenticate_user! - before_action :find_auction, only: %i[new create] - before_action :check_for_ban, only: :create + # order is important before_action :set_offer, only: %i[show edit update] - before_render :find_or_initialize_autobidder, only: %i[new create edit update] - before_action :authorize_phone_confirmation - before_action :authorize_offer_for_user, except: %i[new create] - before_action :prevent_check_for_invalid_bid, only: [:update] - before_render :find_or_initialize_autobidder, only: %i[new create edit update] + include EnglishOffers::Offerable include RecaptchaValidatable recaptcha_action 'english_offer' @@ -18,8 +15,7 @@ class EnglishOffersController < ApplicationController # GET /auctions/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/offers/new def new - offer = @auction.offer_from_user(current_user.id) - redirect_to edit_english_offer_path(offer.uuid) if offer + prevent_check_for_existed_offer and return if @auction.offer_from_user(current_user.id) BillingProfile.create_default_for_user(current_user.id) @offer = Offer.new(auction_id: @auction.id, user_id: current_user.id) @@ -27,41 +23,31 @@ def new # POST /auctions/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/offers def create - unless check_first_bid_for_english_auction(create_params, @auction) - formatted_starting_price = format('%.2f', @auction.starting_price) - flash[:alert] = t('english_offers.create.bid_must_be', minimum: formatted_starting_price) - redirect_to new_auction_english_offer_path(auction_uuid: @auction.uuid) and return - end + inform_about_invalid_bid_amount and return unless check_first_bid_for_english_auction(create_params, @auction) + inform_invalid_captcha and return unless recaptcha_valid @offer = Offer.new(create_params) @offer.username = Username::GenerateUsernameService.new.call authorize! :manage, @offer - if recaptcha_valid - if create_predicate(@auction) - broadcast_update_auction_offer(@auction) - send_outbided_notification(auction: @auction, offer: @offer, flash: flash) - update_auction_values(@auction, t('english_offers.create.created')) - else - flash[:alert] = if @offer.errors.full_messages_for(:cents).present? - @offer.errors.full_messages_for(:cents).join - else - @offer.errors.full_messages.join('; ') - end - - redirect_to root_path - end + if create_predicate(@auction) + broadcast_update_auction_offer(@auction) + send_outbided_notification(auction: @auction, offer: @offer, flash:) + update_auction_values(@auction, t('english_offers.create.created')) else - @show_checkbox_recaptcha = true unless @success - flash.now[:alert] = t('english_offers.form.captcha_verification') - render :new, status: :unprocessable_entity + flash[:alert] = if @offer.errors.full_messages_for(:cents).present? + @offer.errors.full_messages_for(:cents).join + else + @offer.errors.full_messages.join('; ') + end + + redirect_to root_path and return end end # GET /english_offers/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def show @auction = @offer.auction - render template: 'offers/show' end # GET /english_offers/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit @@ -74,79 +60,24 @@ def edit def update @auction = Auction.english.with_user_offers(current_user.id).find_by(uuid: @offer.auction.uuid) redirect_to auction_path(@auction.uuid) and return if update_not_allowed(@auction) + inform_invalid_captcha and return unless recaptcha_valid - if recaptcha_valid - if update_predicate(@auction) - broadcast_update_auction_offer(@auction) - send_outbided_notification(auction: @auction, offer: @offer, flash: flash) - update_auction_values(@auction, t('english_offers.edit.bid_updated')) - else - flash[:alert] = if @offer.errors.full_messages_for(:cents).present? - @offer.errors.full_messages_for(:cents).join - else - @offer.errors.full_messages.join('; ') - end - - redirect_to root_path - end + if update_predicate(@auction) + broadcast_update_auction_offer(@auction) + send_outbided_notification(auction: @auction, offer: @offer, flash:) + update_auction_values(@auction, t('english_offers.edit.bid_updated')) else - @show_checkbox_recaptcha = true unless @success - flash.now[:alert] = t('english_offers.form.captcha_verification') - render :edit, status: :unprocessable_entity - end - end - - private - - def find_auction - @auction = Auction.english.find_by!(uuid: params[:auction_uuid]) - end - - def find_or_initialize_autobidder - @autobider = current_user.autobiders.find_or_initialize_by(domain_name: @auction.domain_name) - end + flash[:alert] = if @offer.errors.full_messages_for(:cents).present? + @offer.errors.full_messages_for(:cents).join + else + @offer.errors.full_messages.join('; ') + end - def update_not_allowed(auction) - !auction.english? || !auction.in_progress? - end - - def check_for_ban - if Ban.valid.where(user_id: current_user).where(domain_name: @auction.domain_name).any? || current_user.completely_banned? - redirect_to root_path, flash: { alert: I18n.t('.english_offers.create.ban') } and return + redirect_to root_path end end - def broadcast_update_auction_offer(auction) - Offers::UpdateBroadcastService.call({ auction: auction }) - end - - def update_auction_values(auction, message_text) - AutobiderService.autobid(auction) - auction.update_ends_at(@offer) - - flash[:notice] = message_text - redirect_to edit_english_offer_path(@offer.uuid) - end - - def prevent_check_for_invalid_bid - auction = Auction.with_user_offers(current_user.id).find_by(uuid: @offer.auction.uuid) - return unless bid_is_bad?(auction: auction, update_params: update_params) - - flash[:alert] = - "#{t('english_offers.show.bid_failed', price: format('%.2f', auction.highest_price.to_f).tr('.', ','))}" - redirect_to edit_english_offer_path(auction.users_offer_uuid) and return - end - - def bid_is_bad?(auction:, update_params:) - !additional_check_for_bids(auction, update_params[:price]) || - !check_bids_for_english_auction(update_params, auction) - end - - def additional_check_for_bids(auction, current_bid) - order = auction.offers.order(updated_at: :desc).first - - Money.new(order.cents).to_f < current_bid.to_f - end + private def create_predicate(auction) @offer.save && auction.update_minimum_bid_step(create_params[:price].to_f) && @offer.reload @@ -156,24 +87,6 @@ def create_params params.require(:offer).permit(:auction_id, :user_id, :price, :billing_profile_id, :username) end - def check_first_bid_for_english_auction(params, auction) - return true if auction.blind? - - starting_price = auction.starting_price - price = params[:price].to_f - - price.to_f >= starting_price.to_f - end - - def check_bids_for_english_auction(params, auction) - return true if auction.blind? - - minimum = auction.min_bids_step.to_f - price = params[:price].to_f - - price >= minimum - end - def update_predicate(auction) @offer.update(update_params) && auction.update_minimum_bid_step(create_params[:price].to_f) && @@ -184,19 +97,4 @@ def update_params update_params = params.require(:offer).permit(:price, :billing_profile_id) merge_updated_by(update_params) end - - def set_offer - @offer = current_user.offers.find_by!(uuid: params[:uuid]) - end - - def authorize_phone_confirmation - return unless current_user.requires_phone_number_confirmation? - - redirect_to new_user_phone_confirmation_path(current_user.uuid), - notice: t('phone_confirmations.confirmation_required') - end - - def authorize_offer_for_user - authorize! :manage, @offer - end end diff --git a/app/controllers/invoices/pay_all_cancelled_payable_invoices_controller.rb b/app/controllers/invoices/pay_all_cancelled_payable_invoices_controller.rb new file mode 100644 index 000000000..80dff1f3d --- /dev/null +++ b/app/controllers/invoices/pay_all_cancelled_payable_invoices_controller.rb @@ -0,0 +1,24 @@ +class Invoices::PayAllCancelledPayableInvoicesController < ApplicationController + before_action :authenticate_user! + before_action :authorize_user + + def create + cancelled_payable_invoices = Invoice.accessible_by(current_ability) + .where(user_id: current_user.id).to_a.select(&:cancelled_and_have_valid_ban?) + + response = EisBilling::BulkInvoicesService.call(invoices: cancelled_payable_invoices, + customer_url: linkpay_callback_url) + + if response.result? + redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true + else + flash.alert = response.errors['message'] + redirect_to invoices_path and return + end + end + + def authorize_user + authorize! :read, Invoice + authorize! :update, Invoice + end +end diff --git a/app/controllers/invoices/pay_all_issued_invoices_controller.rb b/app/controllers/invoices/pay_all_issued_invoices_controller.rb new file mode 100644 index 000000000..6a5e089be --- /dev/null +++ b/app/controllers/invoices/pay_all_issued_invoices_controller.rb @@ -0,0 +1,24 @@ +class Invoices::PayAllIssuedInvoicesController < ApplicationController + before_action :authenticate_user! + before_action :authorize_user + + def create + issued_invoices = Invoice.accessible_by(current_ability) + .where(user_id: current_user.id).to_a.select(&:issued?) + + response = EisBilling::BulkInvoicesService.call(invoices: issued_invoices, + customer_url: linkpay_callback_url) + + if response.result? + redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true + else + flash.alert = response.errors + redirect_to invoices_path and return + end + end + + def authorize_user + authorize! :read, Invoice + authorize! :update, Invoice + end +end diff --git a/app/controllers/invoices_controller.rb b/app/controllers/invoices_controller.rb index 2bc31662a..a2ff7a708 100644 --- a/app/controllers/invoices_controller.rb +++ b/app/controllers/invoices_controller.rb @@ -1,7 +1,8 @@ class InvoicesController < ApplicationController before_action :authenticate_user! before_action :authorize_user - before_action :set_invoice, except: %i[index pay_all_bills oneoff pay_deposit] + before_action :set_invoice, except: %i[index pay_all_bills pay_deposit] + before_action :validate_amount, only: :oneoff # GET /invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit def edit; end @@ -10,10 +11,28 @@ def edit; end def update respond_to do |format| if update_predicate - format.html { redirect_to invoice_path(@invoice.uuid), notice: t(:updated) } + format.turbo_stream do + render turbo_stream: [ + turbo_stream.update('invoice_information', + Modals::PayInvoice::InvoiceInformation::Component.new(invoice: @invoice)), + turbo_stream.toast(t(:updated), position: "right", background: 'linear-gradient(to right, #11998e, #38ef7d)') + ] + end + + format.html { redirect_to invoices_path, notice: t(:updated) } format.json { render :show, status: :ok, location: @invoice } else - format.html { redirect_to invoice_path(@invoice.uuid), notice: t(:something_went_wrong) } + error_str = if @invoice.errors.empty? + @invoice.payable? ? t(:something_went_wrong) : t('invoices.invoice_already_paid') + else + @invoice.errors.full_messages.join('; ') + end + + format.turbo_stream do + render turbo_stream: turbo_stream.toast(error_str, position: "right", background: 'linear-gradient(to right, #93291E, #ED213A)') + end + + format.html { redirect_to invoices_path, status: :see_other, alert: error_str } format.json { render json: @invoice.errors, status: :unprocessable_entity } end end @@ -22,12 +41,15 @@ def update # GET /invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def show; end - # GET /invoices + # rubocop:disable Metrics/AbcSize def index @issued_invoices = invoices_list_by_status(Invoice.statuses[:issued]) - @paid_invoices = invoices_list_by_status(Invoice.statuses[:paid]) @cancelled_payable_invoices = invoices_list_by_status(Invoice.statuses[:cancelled]).with_ban @cancelled_expired_invoices = invoices_list_by_status(Invoice.statuses[:cancelled]).without_ban + + # @unpaid_invoices_count = @issued_invoices.count + @cancelled_payable_invoices.count + + @paid_invoices = invoices_list_by_status(Invoice.statuses[:paid]) @deposit_paid = current_user.domain_participate_auctions.order(created_at: :desc) return unless params[:state] == 'payment' @@ -43,16 +65,11 @@ def download send_data(raw_pdf, filename: @invoice.filename) end - def invoices_list_by_status(status) - Invoice.accessible_by(current_ability) - .where(user_id: current_user.id) - .where(status: status) - .order(due_date: :desc) - end - + # TODO: Remove. It is deprecated def pay_all_bills - issued_invoices = invoices_list_by_status(Invoice.statuses[:issued]) - response = EisBilling::BulkInvoicesService.call(invoices: issued_invoices, + payable_invoices = Invoice.accessible_by(current_ability) + .where(user_id: current_user.id).to_a.select(&:payable?) + response = EisBilling::BulkInvoicesService.call(invoices: payable_invoices, customer_url: linkpay_callback_url) if response.result? @@ -70,21 +87,24 @@ def pay_deposit "user_email #{current_user.email}" response = EisBilling::PayDepositService.call(amount: auction.deposit, customer_url: deposit_callback_url, - description: description) + description:) + if response.result? - redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true + redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true, format: :html else - flash.alert = response.errors + flash.alert = response.errors['message'] redirect_to invoices_path end end def oneoff - invoice = Invoice.accessible_by(current_ability).find_by!(uuid: params[:uuid]) - response = EisBilling::OneoffService.call(invoice_number: invoice.number.to_s, - customer_url: linkpay_callback_url) + response = EisBilling::OneoffService.call(invoice_number: @invoice.number.to_s, + customer_url: linkpay_callback_url, + amount: params[:amount]) + + if response.result? - redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true + redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true, format: :html else flash.alert = response.errors['message'] redirect_to invoices_path @@ -92,18 +112,26 @@ def oneoff end def send_e_invoice - invoice = Invoice.accessible_by(current_ability).find_by!(uuid: params[:uuid]) - response = EisBilling::SendEInvoice.call(invoice: invoice, payable: !invoice.paid?) + response = EisBilling::SendEInvoice.call(invoice: @invoice, payable: !@invoice.paid?) if response.result? - redirect_to invoice_path(invoice.uuid), notice: t('.sent_to_omniva') + redirect_to invoice_path(@invoice.uuid), notice: t('.sent_to_omniva') else - redirect_to invoice_path(invoice.uuid), alert: response.errors + redirect_to invoice_path(@invoice.uuid), alert: response.errors end end + private + + def invoices_list_by_status(status) + Invoice.accessible_by(current_ability) + .where(user_id: current_user.id) + .where(status:) + .order(due_date: :desc) + end + def update_predicate - @invoice.issued? && @invoice.update(update_params) + @invoice.payable? && @invoice.update(update_params) end def update_params @@ -119,4 +147,13 @@ def authorize_user authorize! :read, Invoice authorize! :update, Invoice end + + def validate_amount + return if params[:amount].nil? + + alert = I18n.t('invoices.amount_must_be_positive') if params[:amount].to_f <= 0 + alert = I18n.t('invoices.amount_is_too_big') if params[:amount].to_f > @invoice.due_amount.to_f + + redirect_to invoice_path(@invoice.uuid), alert: alert if alert + end end diff --git a/app/controllers/notifications/mark_as_read_controller.rb b/app/controllers/notifications/mark_as_read_controller.rb index 4a6259305..07f261ab0 100644 --- a/app/controllers/notifications/mark_as_read_controller.rb +++ b/app/controllers/notifications/mark_as_read_controller.rb @@ -1,13 +1,12 @@ -class Notifications::MarkAsReadController < ApplicationController - def update - current_user&.notifications&.map(&:mark_as_read!) +# frozen_string_literal: true - respond_to do |format| - format.turbo_stream do - render turbo_stream: [ - turbo_stream.update('bell-broadcast', partial: 'notifications/bell', locals: { any_unread: false }), - ] - end +module Notifications + class MarkAsReadController < ApplicationController + def update + current_user&.notifications&.unread&.each(&:mark_as_read!) + + flash[:notice] = t('notifications.all_marked_as_read') + redirect_to notifications_path, status: :see_other end end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index fd54311b7..f8bcd5212 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -1,5 +1,12 @@ class NotificationsController < ApplicationController + before_action :authenticate_user! + + DEFAULT_PAGE_LIMIT = 15 + def index - @notifications = current_user&.notifications&.limit(6) + @notifications = current_user&.notifications + + @unread_pagy, @unread_notifications = pagy(@notifications&.unread, page_param: :unread_page, items: DEFAULT_PAGE_LIMIT) + @read_pagy, @read_notifications = pagy(@notifications&.read, page_param: :read_page, items: DEFAULT_PAGE_LIMIT) end end diff --git a/app/controllers/offers_controller.rb b/app/controllers/offers_controller.rb index f6030f3ff..f6b4245f1 100644 --- a/app/controllers/offers_controller.rb +++ b/app/controllers/offers_controller.rb @@ -1,19 +1,16 @@ +# frozen_string_literal: true + class OffersController < ApplicationController - before_action :authenticate_user! - before_action :find_auction, only: %i[new create] + include Offerable + before_action :set_offer, only: %i[show edit update destroy] - before_action :check_for_ban, only: :create - before_action :authorize_phone_confirmation - before_action :authorize_offer_for_user, except: %i[new index create] + before_action :authorize_offer_for_user, except: %i[new index create delete] include RecaptchaValidatable recaptcha_action 'offer' # GET /auctions/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/offers/new def new - offer = @auction.offer_from_user(current_user.id) - redirect_to edit_offer_path(offer.uuid), notice: t('offers.already_exists') if offer - BillingProfile.create_default_for_user(current_user.id) @offer = Offer.new(auction_id: @auction.id, user_id: current_user.id) end @@ -21,21 +18,22 @@ def new # POST /auctions/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/offers def create existing_offer = @auction.offer_from_user(current_user.id) - @offer = Offer.new(create_params) authorize! :manage, @offer + inform_invalid_captcha and return unless recaptcha_valid + respond_to do |format| if existing_offer format.html do - redirect_to offer_path(existing_offer.uuid), notice: t('offers.already_exists') + redirect_to root_path, notice: t('offers.already_exists') end elsif create_predicate - format.html { redirect_to offer_path(@offer.uuid), notice: t('.created') } + format.html { redirect_to root_path, notice: t('.created') } format.json { render :show, status: :created, location: @offer } else - @show_checkbox_recaptcha = true unless @success - format.html { render :new, status: :unprocessable_entity } + flash[:alert] = @offer.errors.full_messages.join('; ') + format.html { redirect_to root_path, status: :see_other } format.json { render json: @offer.errors, status: :unprocessable_entity } end end @@ -43,11 +41,7 @@ def create # GET /offers def index - offers = Offer.includes(:auction) - .includes(:result) - .where(user_id: current_user) - .order('auctions.ends_at DESC') - + offers = current_user.offers.search(params) @pagy, @offers = pagy(offers, items: params[:per_page] ||= 15) end @@ -59,21 +53,21 @@ def show # GET /offers/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit def edit auction = @offer.auction - redirect_to auction_path(auction.uuid) and return if update_not_allowed(auction) + redirect_to root_path and return if update_not_allowed(auction) end # PUT /offers/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def update - auction = @offer.auction - redirect_to auction_path(auction.uuid) and return if update_not_allowed(auction) - respond_to do |format| + inform_invalid_captcha and return unless recaptcha_valid + redirect_to root_path and return if update_not_allowed(@offer.auction) + if update_predicate - format.html { redirect_to offer_path(@offer.uuid), notice: t(:updated) } + format.html { redirect_to root_path, notice: t(:updated), status: :see_other } format.json { render :show, status: :ok, location: @offer } else - @show_checkbox_recaptcha = true unless @success - format.html { render :edit, status: :unprocessable_entity } + flash[:alert] = @offer.errors.full_messages.join('; ') + format.html { redirect_to root_path, status: :see_other } format.json { render json: @offer.errors, status: :unprocessable_entity } end end @@ -82,29 +76,26 @@ def update # DELETE /offers/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def destroy return if @offer.auction.english? - return unless @offer.can_be_modified? && @offer.destroy - respond_to do |format| - format.html { redirect_to auction_path(@offer.auction.uuid), notice: t(:deleted) } - format.json { head :no_content } + if @offer.can_be_modified? && @offer.destroy + respond_to do |format| + format.html { redirect_to auctions_path, notice: t(:deleted), status: :see_other } + format.json { head :no_content } + end + else + respond_to do |format| + format.html { redirect_to offer_path(@offer.uuid), notice: t(:not_deleted), status: :see_other } + format.json { head :no_content } + end end end - private - - def find_auction - @auction = Auction.find_by!(uuid: params[:auction_uuid]) - end - - def update_not_allowed(auction) - auction.english? || !auction.in_progress? + def delete + @offer = current_user.offers.find_by!(uuid: params[:offer_uuid]) + authorize! :manage, @offer end - def check_for_ban - if Ban.valid.where(user_id: current_user).where(domain_name: @auction.domain_name).any? || current_user.completely_banned? - redirect_to root_path, flash: { alert: I18n.t('.offers.create.ban') } and return - end - end + private def create_predicate recaptcha_valid && @offer.save && @offer.reload @@ -122,19 +113,4 @@ def update_params update_params = params.require(:offer).permit(:price, :billing_profile_id) merge_updated_by(update_params) end - - def set_offer - @offer = current_user.offers.find_by!(uuid: params[:uuid]) - end - - def authorize_phone_confirmation - return unless current_user.requires_phone_number_confirmation? - - redirect_to new_user_phone_confirmation_path(current_user.uuid), - notice: t('phone_confirmations.confirmation_required') - end - - def authorize_offer_for_user - authorize! :manage, @offer - end end diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb new file mode 100644 index 000000000..47672e80d --- /dev/null +++ b/app/controllers/passwords_controller.rb @@ -0,0 +1,12 @@ +class PasswordsController < Devise::PasswordsController + def create + self.resource = resource_class.send_reset_password_instructions(resource_params) + yield resource if block_given? + + if successfully_sent?(resource) + respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) + else + redirect_to root_path, alert: resource.errors.full_messages.join(', '), status: :see_other + end + end +end diff --git a/app/controllers/payment_orders_controller.rb b/app/controllers/payment_orders_controller.rb index 9933cc9e6..234d58dea 100644 --- a/app/controllers/payment_orders_controller.rb +++ b/app/controllers/payment_orders_controller.rb @@ -25,38 +25,6 @@ def show @payment_order.callback_url = callback_payment_order_url(@payment_order.uuid) end - # # ANY /payment_orders/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/return - # def return - # @payment_order = PaymentOrder.find_by!(uuid: params[:uuid]) - - # if @payment_order.paid? - # respond_to do |format| - # format.html do - # redirect_to invoices_path, notice: t('.already_paid') and return - # end - - # format.json { render json: @payment_order.errors, status: :unprocessable_entity and return } - # end - # end - - # @payment_order.update!(response: params.to_unsafe_h) - - # ResultStatusUpdateJob.perform_later - - # respond_to do |format| - # if @payment_order.mark_invoice_as_paid - # format.html { redirect_to invoices_path, notice: successful_update_notice } - # format.json { redirect_to invoices_path, notice: successful_update_notice } - # else - # format.html do - # redirect_to invoices_path, - # notice: t('.not_successful') - # end - # format.json { render json: @payment_order.errors, status: :unprocessable_entity } - # end - # end - # end - # POST /payment_orders/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/callback def callback render status: :ok, json: { status: 'ok' } @@ -76,12 +44,6 @@ def create_params params.require(:payment_order).permit(:user_id, :invoice_id, :invoice_ids, :type) end - # def successful_update_notice - # invoice_ids = @payment_order.invoices.map(&:number).join(', ') - # return t('.bulk_update', ids: invoice_ids) if @payment_order.invoices.count > 1 - # t(:updated) - # end - def authorize_user authorize! :read, PaymentOrder authorize! :create, PaymentOrder diff --git a/app/controllers/phone_confirmations_controller.rb b/app/controllers/phone_confirmations_controller.rb index 7a3051d17..9c3d823b6 100644 --- a/app/controllers/phone_confirmations_controller.rb +++ b/app/controllers/phone_confirmations_controller.rb @@ -10,7 +10,7 @@ def new if @phone_confirmation.confirmed? redirect_to user_path(@phone_confirmation.user.uuid), notice: t('.already_confirmed') elsif @phone_confirmation.user.not_phone_number_confirmed_unique? - redirect_to user_path(@phone_confirmation.user.uuid) + redirect_to edit_user_path(@phone_confirmation.user.uuid) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c6efe8d81..f88af127d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,12 +1,13 @@ require 'countries' +# rubocop:disable Metrics class UsersController < ApplicationController include UserNotices - before_action :authenticate_user!, only: %i[show edit update destroy edit_authwall] + + before_action :authenticate_user!, only: %i[show edit update destroy edit_authwall toggle_subscription] before_action :set_user, only: %i[show edit update destroy] before_action :set_minimum_password_length, only: %i[new edit] - before_action :authorize_user, except: %i[new index create show edit_authwall - toggle_subscription] + before_action :authorize_user, except: %i[new index create show edit_authwall toggle_subscription] # GET /users def index; end @@ -19,7 +20,7 @@ def new # GET /profile/edit def edit_authwall - redirect_to edit_user_path(current_user.uuid) + redirect_to user_path(current_user.uuid) end # GET /profile/toggle_daily_subscription @@ -27,7 +28,12 @@ def toggle_subscription @user = current_user @user.daily_summary = !@user.daily_summary @user.save! - redirect_to :auctions, notice: t('.subscription_status_toggled_flash') + + flash[:notice] = t('.subscription_status_toggled_flash') + + # render turbo_stream: turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + # render turbo_stream: turbo_stream.toast(t(:updated), position: "right", background: 'linear-gradient(to right, #11998e, #38ef7d)') + redirect_to root_path end # POST /users/new @@ -58,29 +64,36 @@ def show end # GET /users/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit - def edit; end + def edit + return unless turbo_frame_request? + + render partial: 'form', locals: { user: @user } + end # PUT /users/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def update - if valid_password? - @user.attributes = params_for_update - email_changed = @user.email_changed? + respond_to do |format| + if valid_password? + @user.attributes = params_for_update + email_changed = @user.email_changed? - respond_to do |format| if @user.valid? @user.save! + flash[:notice] = notification_for_update(email_changed) format.html do - redirect_to user_path(@user.uuid), notice: notification_for_update(email_changed) + redirect_to user_path(@user.uuid) end format.json { render :show, status: :ok, location: @user } else - format.html { render :edit } + flash.now[:alert] = @user.errors.full_messages.join(', ') format.json { render json: @user.errors, status: :unprocessable_entity } end + else + flash.now[:alert] = t('.incorrect_password') end - else - redirect_to edit_user_path(@user.uuid), notice: t('.incorrect_password') + + format.turbo_stream end end diff --git a/app/controllers/wishlist_items_controller.rb b/app/controllers/wishlist_items_controller.rb index 517097583..b4c68e055 100644 --- a/app/controllers/wishlist_items_controller.rb +++ b/app/controllers/wishlist_items_controller.rb @@ -1,15 +1,22 @@ +# rubocop:disable Metrics class WishlistItemsController < ApplicationController include ActionView::RecordIdentifier before_action :authenticate_user! + before_action :check_for_action_restrictions, only: %i[create update] + before_action :set_wishlist_item, only: %i[edit update destroy] + before_action :set_wishlist_items, only: %i[index update edit destroy] + + rescue_from ActiveModel::RangeError, with: :out_of_range_handle def index @wishlist_item = WishlistItem.new(user: current_user) - @wishlist_items = WishlistItem.for_user(current_user.id) end def edit - @wishlist_item = WishlistItem.find_by(uuid: params[:uuid]) + return unless turbo_frame_request? + + render partial: 'editable', locals: { wishlist_items: @wishlist_items, wishlist_item: @wishlist_item } end def create @@ -20,72 +27,90 @@ def create format.html { redirect_to wishlist_items_path, notice: t(:created) } format.json { render json: @wishlist_item, status: :created } else - format.json do - render json: @wishlist_item.errors.full_messages, status: :unprocessable_entity - end - format.html { redirect_to wishlist_items_path, notice: @wishlist_item.errors.full_messages.join(', ') } + format.json { render json: @wishlist_item.errors.full_messages, status: :unprocessable_entity } + format.html { redirect_to wishlist_items_path, alert: @wishlist_item.errors.full_messages.join(', ') } end end end def destroy - @wishlist_item = WishlistItem.for_user(current_user).find_by(uuid: params[:uuid]) authorize! :delete, @wishlist_item - @wishlist_item.destroy respond_to do |format| - format.html { redirect_to wishlist_items_path, notice: t(:deleted) } - format.json { head :no_content } - end - end - - def create_predicate - authorize! :create, @wishlist_item - @wishlist_item.save - end - - def update - wishlist_item = WishlistItem.find_by(uuid: params[:uuid]) - - flash[:alert] = check_for_action_restrictions(wishlist_item.domain_name) - redirect_to wishlist_items_path and return if flash[:alert].present? - - respond_to do |format| - if wishlist_item.update(strong_params) - flash.now[:notice] = 'Updated' + if @wishlist_item.destroy format.turbo_stream do render turbo_stream: [ - turbo_stream.append(dom_id(wishlist_item), partial: 'wishlist_items/starting_price', - locals: { wishlist_item: wishlist_item }) + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }), + turbo_stream.update('wishlist-container', partial: 'wishlist_items', + locals: { wishlist_items: @wishlist_items }) ] end - format.html { redirect_to wishlist_items_path } + format.html { redirect_to wishlist_items_path, notice: t(:created) } + format.json { render json: @wishlist_item, status: :created } else - flash[:alert] = I18n.t('something_went_wrong') - format.html { redirect_to wishlist_items_path } + format.json { render json: @wishlist_item.errors.full_messages, status: :unprocessable_entity } + format.html { redirect_to wishlist_items_path, alert: @wishlist_item.errors.full_messages.join(', ') } end end end + def update + if @wishlist_item.update(strong_params) + flash[:notice] = t(:updated) + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }), + turbo_stream.update('wishlist-container', partial: 'wishlist_items', + locals: { wishlist_items: @wishlist_items }) + ] + else + flash[:alert] = @wishlist_item.errors.full_messages.join(', ') + + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + ] + end + end + # this method used by wishlist stimulus controller def domain_wishlist_availability wishlist_item = current_user.wishlist_items.build(domain_name: params[:domain_name]) - if wishlist_item.valid? - msg = { status: 'fine', domain_name: params[:domain_name] } - else - msg = { status: 'wrong', domain_name: params[:domain_name], errors: wishlist_item.errors.full_messages } - end + msg = if wishlist_item.valid? + { status: 'fine', domain_name: params[:domain_name] } + else + { status: 'wrong', domain_name: params[:domain_name], errors: wishlist_item.errors.full_messages } + end render json: msg end private - def check_for_action_restrictions(domain_name) + def create_predicate + authorize! :create, @wishlist_item + @wishlist_item.save + end + + def out_of_range_handle(exception) + flash[:alert] = exception + + render turbo_stream: [ + turbo_stream.replace('flash', partial: 'common/flash', locals: { flash: }) + ] + end + + def set_wishlist_item + @wishlist_item = WishlistItem.for_user(current_user).find_by(uuid: params[:uuid]) + end + + def set_wishlist_items + @wishlist_items = WishlistItem.for_user(current_user.id) + end + + def check_for_action_restrictions if current_user.completely_banned? I18n.t('auctions.banned_completely', valid_until: current_user.longest_ban.valid_until) - elsif current_user.bans.valid.pluck(:domain_name).include?(domain_name) - I18n.t('auctions.banned', domain_names: domain_name) + elsif current_user.bans.valid.pluck(:domain_name).include?(params[:domain_name]) + I18n.t('auctions.banned', domain_names: params[:domain_name]) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2888e7aaa..f274b004f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -11,6 +11,11 @@ def google_analytics GoogleAnalytics.new(tracking_id: tracking_id) end + def component(name, *args, **kwargs, &block) + component = name.to_s.camelize.constantize::Component + render(component.new(*args, **kwargs), &block) + end + def navigation_links(current_user) content_tag(:ul) do links(user_link_list) if current_user&.role?(User::PARTICIPANT_ROLE) @@ -31,12 +36,13 @@ def banned_banner message = ban_error_message(domains, valid_until) return unless message - content_tag(:div, class: 'ui message ban') do - result = content_tag(:div, message, class: 'header') - if eligible_violations_present?(domains: domains) - result << content_tag(:p, violation_message(domains.size), class: 'violation-message') + content_tag(:div, class: 'c-toast js-toast c-toast--error') do + content_tag(:div, class: 'c-toast__content') do + concat(content_tag(:p, message)) + if eligible_violations_present?(domains: domains) + concat(content_tag(:p, violation_message(domains.size))) + end end - result end end diff --git a/app/helpers/daily_summary_helper.rb b/app/helpers/daily_summary_helper.rb new file mode 100644 index 000000000..e8e96aba0 --- /dev/null +++ b/app/helpers/daily_summary_helper.rb @@ -0,0 +1,2 @@ +module DailySummaryHelper +end diff --git a/app/helpers/invalid_user_data_helper.rb b/app/helpers/invalid_user_data_helper.rb index 6420fddfd..94820f4a2 100644 --- a/app/helpers/invalid_user_data_helper.rb +++ b/app/helpers/invalid_user_data_helper.rb @@ -9,12 +9,12 @@ def invalid_data_banner return unless session['user.invalid_user_data'] if current_user.not_phone_number_confirmed_unique? - content_tag(:div, class: 'ui message flash') do - content_tag(:div, t('already_confirmed'), class: 'header') + content_tag(:div, class: 'c-toast js-toast c-toast--error') do + content_tag(:div, t('already_confirmed'), class: 'c-toast__content') end else - content_tag(:div, class: 'ui message ban') do - content_tag(:div, t('users.invalid_user_data'), class: 'header') + content_tag(:div, class: 'c-toast js-toast c-toast--error') do + content_tag(:div, t('users.invalid_user_data'), class: 'c-toast__content') end end end diff --git a/app/helpers/invoices_helper.rb b/app/helpers/invoices_helper.rb index 6e050a32c..fbca4b03f 100644 --- a/app/helpers/invoices_helper.rb +++ b/app/helpers/invoices_helper.rb @@ -7,8 +7,6 @@ def fetch_errors_from_response(channel:, response:) return 'OK' if response['transaction_result'] == 'completed' fetch_everypay_errors(response) - when 'SEB', 'LHV', 'Swedbank' - fetch_banklink_errors(response) end end diff --git a/app/helpers/orderable_helper.rb b/app/helpers/orderable_helper.rb index fe6ed5c0d..5ca569702 100644 --- a/app/helpers/orderable_helper.rb +++ b/app/helpers/orderable_helper.rb @@ -1,35 +1,23 @@ module OrderableHelper - def sort_link_to(name, column) - if direction_indicator(column).nil? - dir = content_tag(:i, nil, class: 'sort alphabet up icon') - name = raw("#{name} #{dir}") - else - name = raw("#{name} #{direction_indicator(column)}") - end + # def sortable_column(_name, column, caption) + # tag.th caption, class: 'sorting', data: { controller: 'table--sort-link', turbo_action: 'advance', + # action: 'click->table--sort-link#resortTable', + # 'table--sort-link-direction-value': next_direction(column), + # 'table--sort-link-column-value': column, + # 'table--sort-link-asc-class': 'sorting_asc', + # 'table--sort-link-desc-class': 'sorting_desc', + # 'table--sort-link-target': 'th' } + # end - params = request.params.merge(sort: column, direction: next_direction(column)) + # private - link_to name, params, data: { - turbo_action: 'advance', - action: 'turbo:click->sort-link#updateForm' - } - end + # def currently_sorted?(column) + # params[:sort] == column.to_s + # end - def next_direction(column) - if currently_sorted?(column) - params[:direction] == 'asc' ? 'desc' : 'asc' - else - 'asc' - end - end + # def next_direction(column) + # return unless currently_sorted?(column) - def direction_indicator(column) - if currently_sorted?(column) - content_tag(:i, nil, class: "sort alphabet #{next_direction(column) == 'asc' ? 'up' : 'down'} icon") - end - end - - def currently_sorted?(column) - params[:sort] == column.to_s - end + # params[:direction] == 'asc' ? 'desc' : 'asc' + # end end diff --git a/app/helpers/turbo_streams/close_modal_helper.rb b/app/helpers/turbo_streams/close_modal_helper.rb new file mode 100644 index 000000000..b7902551d --- /dev/null +++ b/app/helpers/turbo_streams/close_modal_helper.rb @@ -0,0 +1,7 @@ +module TurboStreams::CloseModalHelper + def close_modal(modal_class, open_class, form_id) + turbo_stream_action_tag :close_modal, modal_class: modal_class, open_class: open_class, form_class: form_id + end +end + +Turbo::Streams::TagBuilder.prepend(TurboStreams::CloseModalHelper) diff --git a/app/helpers/turbo_streams/toast_helper.rb b/app/helpers/turbo_streams/toast_helper.rb new file mode 100644 index 000000000..8a9b556f4 --- /dev/null +++ b/app/helpers/turbo_streams/toast_helper.rb @@ -0,0 +1,7 @@ +module TurboStreams::ToastHelper + def toast(message, position: "left", **options) + turbo_stream_action_tag :toast, message: message, position: position, **options + end +end + +Turbo::Streams::TagBuilder.prepend(TurboStreams::ToastHelper) diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 000000000..488516712 --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,28 @@ +// Entry point for the build script in your package.json +import "./turbo_streams" +import "@hotwired/turbo-rails" +import "./controllers" + +import { StreamActions } from "@hotwired/turbo" + +Turbo.StreamActions.redirect = function () { + Turbo.visit(this.target); +}; + +document.addEventListener('DOMContentLoaded', function() { + // Find all elements with the class 'js-close-toast' + var closeButtons = document.querySelectorAll('.js-close-toast'); + + closeButtons.forEach(function(closeButton) { + closeButton.addEventListener('click', function() { + var toast = closeButton.closest('.c-toast'); + if (toast) { + toast.style.transition = 'opacity 0.5s ease'; + toast.style.opacity = '0'; + toast.addEventListener('transitionend', function() { + toast.remove(); + }, { once: true }); + } + }); + }); +}); \ No newline at end of file diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 000000000..c12300dfd --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,16 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = document.documentElement.classList.contains("debug"); +window.Stimulus = application + +// if (process.env.RAILS_ENV === "test") { +// // propagate errors that happen inside Stimulus controllers +// application.handleError = (error, message, detail) => { +// throw error +// } +// } + +export { application } diff --git a/app/javascript/controllers/auction_timezone_controller.js b/app/javascript/controllers/auction_timezone_controller.js new file mode 100644 index 000000000..c5759ce7a --- /dev/null +++ b/app/javascript/controllers/auction_timezone_controller.js @@ -0,0 +1,26 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { startTime: String, endTime: String } + static targets = ["endtime", "starttime"] + + connect() { + this.updateTime() + } + + updateTime() { + if (this.hasEndtimeTarget) { + const endTime = new Date(this.endTimeValue) + const userLocalTime = endTime.toLocaleString(undefined, {timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone}) + + this.endtimeTarget.textContent = userLocalTime + } + + if (this.hasStarttimeTarget) { + const startTime = new Date(this.startTimeValue) + const userLocalTime = startTime.toLocaleString(undefined, {timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone}) + + this.starttimeTarget.textContent = userLocalTime + } + } +} diff --git a/app/javascript/controllers/autotax_counter_controller.js b/app/javascript/controllers/autotax_counter_controller.js new file mode 100644 index 000000000..317fb0776 --- /dev/null +++ b/app/javascript/controllers/autotax_counter_controller.js @@ -0,0 +1,38 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["result"] + static values = { + tax: String, + template: String, + defaulttemplate: String, + priceElement: { default: 'input[name="offer[price]"]', type: String } + } + + connect() { + this.updateTax(); + } + + count(event) { + const value = parseFloat(event.target.value) + const result = this.resultTarget + + if (!isNaN(value) && value > 0) { + const tax = parseFloat(this.taxValue) || 0.0; + const taxAmount = value * tax; + const totalAmount = value + taxAmount; + + result.innerHTML = this.templateValue.replace('{price}', totalAmount.toFixed(2)).replace('{tax}', (tax * 100.0).toFixed(2)); + } else { + result.innerHTML = this.defaulttemplateValue; + } + } + + updateTax(event) { + let selectElement = this.element.querySelector('select'); + this.taxValue = selectElement.options[selectElement.selectedIndex].dataset.vatRate || 0.0; + const priceElement = this.element.querySelector(this.priceElementValue); + + this.count({target: priceElement}); + } +} diff --git a/app/javascript/controllers/cookie_consent_controller.js b/app/javascript/controllers/cookie_consent_controller.js new file mode 100644 index 000000000..b7e72bb68 --- /dev/null +++ b/app/javascript/controllers/cookie_consent_controller.js @@ -0,0 +1,214 @@ +import { Controller } from "@hotwired/stimulus" +import "vanilla-cookieconsent"; + +export default class extends Controller { + connect() { + var cc = initCookieConsent(); + + // run plugin with your configuration + cc.run({ + current_lang: 'en', + autoclear_cookies: false, // default: false + page_scripts: true, // default: false + + // mode: 'opt-in' // default: 'opt-in'; value: 'opt-in' or 'opt-out' + // delay: 0, // default: 0 + auto_language: 'document', // default: null; could also be 'browser' or 'document' + // autorun: true, // default: true + // force_consent: false, // default: false + // hide_from_bots: true, // default: true + // remove_cookie_tables: false // default: false + cookie_name: 'cc_cookie', // default: 'cc_cookie' + // cookie_expiration: 182, // default: 182 (days) + // cookie_necessary_only_expiration: 182 // default: disabled + // cookie_domain: location.hostname, // default: current domain + // cookie_path: '/', // default: root + // cookie_same_site: 'Lax', // default: 'Lax' + // use_rfc_cookie: false, // default: false + revision: 0, // default: 0 + + onFirstAction: function(user_preferences, cookie){ + // callback triggered only once on the first accept/reject action + }, + + onAccept: function (cookie) { + // callback triggered on the first accept/reject action, and after each page load + }, + + onChange: function (cookie, changed_categories) { + // callback triggered when user changes preferences after consent has already been given + }, + + gui_options: { + consent_modal: { + layout: 'cloud', + position: 'bottom center', + transition: 'slide', + swap_buttons: false, + }, + settings_modal: { + layout: 'box', + position: 'right', + transition: 'slide', + }, + }, + + languages: { + en: { + consent_modal: { + title: 'We use cookies!', + description: + 'To ensure the best user experience for our services, our websites use cookies. We use cookies to personalize content and ads, and to provide social media features. We ask for your permission to use cookies that are not necessarily essential for the basic functions of our website. Please read our detailed descriptions and rules of cookies here.', + primary_btn: { + text: 'ACCEPT ALL', + role: 'accept_all', // 'accept_selected' or 'accept_all' + }, + secondary_btn: { + text: 'SETTINGS', + role: 'settings', // 'settings' or 'accept_necessary' + }, + }, + settings_modal: { + title: 'Cookie Settings', + save_settings_btn: 'SAVE SETTINGS', + accept_all_btn: 'ACCEPT ALL', + reject_all_btn: 'REJECT ALL', + close_btn_label: 'Close', + // cookie_table_headers: [ + // { col1: 'Name' }, + // { col2: 'Domain' }, + // { col3: 'Expiration' }, + // { col4: 'Description' }, + // ], + blocks: [ + { + title: 'Cookie usage', + description: + 'We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below. The cookies that are categorized as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site.', + }, + { + title: 'Strictly necessary cookies', + description: + 'Help us make the website more user-friendly by activating essential functions. The website cannot function properly without these cookies. As these cookies are needed for the secure provision of services, the visitor cannot refuse them.', + toggle: { + value: 'necessary', + enabled: true, + readonly: true, // cookie categories with readonly=true are all treated as "necessary cookies" + }, + // cookie_table: [ + // // list of all expected cookies + // { + // col1: '_registrar_center2_session', + // col2: 'registrar.internet.ee', + // col3: 'At the end of the session', + // col4: 'Session cookie used by web application for maintaining session state for a user.', + // is_regex: true, + // }, + // { + // col1: 'request_ip', + // col2: 'registrar.internet.ee', + // col3: '1 day', + // col4: "Used to store the IP address of the user's request for various purposes, like rate limiting or logging in.", + // }, + // { + // col1: 'locale', + // col2: 'registrar.internet.ee', + // col3: '2 years', + // col4: "Used to store the user's preferred language setting.", + // }, + // ], + }, + { + title: 'Statistics and Analytics Cookies', + description: + 'Help us understand how a specific visitor uses the website. This way we see how many people visit the site during a certain period, how they navigate through web pages, and what they click on. These cookies provide us with information based on which we improve the customer experience.', + toggle: { + value: 'analytics', + enabled: true, + readonly: false, // cookie categories with readonly=true are all treated as "necessary cookies" + }, + } + ], + }, + }, + et: { + consent_modal: { + title: 'Kasutame küpsiseid!', + description: + 'Meie teenuste parima kasutajakogemuse tagamiseks kasutavad meie veebisaidid küpsiseid. Kasutame küpsiseid sisu ja reklaamide isikupärastamiseks, sotsiaalse meedia funktsioonide pakkumiseks. Küsime sinult luba, et kasutada küpsiseid, mis ei ole tingimata vajalikud meie veebilehe põhifunktsioonide toimimiseks. Palun loe meie küpsiste üksikasjalikke kirjeldusi ja reegleid siit.', + primary_btn: { + text: 'LUBA KÕIK', + role: 'accept_all', // 'accept_selected' or 'accept_all' + }, + secondary_btn: { + text: 'SEADISTUSED', + role: 'settings', // 'settings' or 'accept_necessary' + }, + }, + settings_modal: { + title: 'Küpsiste seaded', + save_settings_btn: 'SALVESTA', + accept_all_btn: 'LUBA KÕIK', + reject_all_btn: 'KEELA KÕIK', + close_btn_label: 'Sulge', + // cookie_table_headers: [ + // { col1: 'Nimi' }, + // { col2: 'Domeen' }, + // { col3: 'Kehtivus' }, + // { col4: 'Kirjeldus' }, + // ], + blocks: [ + { + title: 'Küpsiste kasutamine', + description: + 'Kasutame küpsiseid, et aidata Teil tõhusalt navigeerida ja teatud funktsioone täita. Üksikasjalikku teavet kõigi küpsiste kohta leiate allpool iga nõusolekukategooria alt. Küpsistest, mis on liigitatud kui "Vajalikud" ei saa loobuda, sest need on olulised saidi põhifunktsioonide toimimiseks.', + }, + { + title: 'Vajalikud küpsised', + description: + 'Veebileht ei saa ilma nende küpsisteta korralikult toimida. Seetõttu ei ole külastajal võimalik neist keelduda.', + toggle: { + value: 'necessary', + enabled: true, + readonly: true, // cookie categories with readonly=true are all treated as "necessary cookies" + }, + // cookie_table: [ + // // list of all expected cookies + // { + // col1: '_registrar_center2_session', + // col2: 'registrar.internet.ee', + // col3: 'Sessiooni lõpus', + // col4: 'Sessiooni küpsis, mida veebirakendus kasutab kasutaja seansi oleku säilitamiseks.', + // is_regex: true, + // }, + // { + // col1: 'request_ip', + // col2: 'registrar.internet.ee', + // col3: '1 päev', + // col4: "Kasutatakse kasutaja päringu IP-aadressi salvestamiseks mitmesugustel eesmärkidel, nagu rate limiting või sisselogimine.", + // }, + // { + // col1: 'locale', + // col2: 'registrar.internet.ee', + // col3: '2 aastat', + // col4: "Kasutatakse kasutaja eelistatud keeleseade salvestamiseks.", + // }, + // ], + }, + { + title: 'Statistics and Analytics Cookies', + description: + 'Aitavad meil mõista, kuidas konkreetne külastaja veebilehte kasutab. Nii näeme, kui palju inimesi kindlal ajavahemikul lehte külastab, kuidas veebilehtedel liigutakse ja millele klikitakse. Need küpsised annavad meile infot, mille põhjal parendada kliendikogemust.', + toggle: { + value: 'analytics', + enabled: true, + readonly: false, // cookie categories with readonly=true are all treated as "necessary cookies" + }, + } + ], + }, + }, + }, + }); + } +} diff --git a/app/javascript/controllers/countdown_controller.js b/app/javascript/controllers/countdown_controller.js new file mode 100644 index 000000000..046505a1a --- /dev/null +++ b/app/javascript/controllers/countdown_controller.js @@ -0,0 +1,58 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + date: String, + refreshInterval: { type: Number, default: 1000 }, + messageTimer: { type: String, default: "${days}d ${hours}h ${minutes}m ${seconds}s" } + } + + connect() { + if (this.dateValue) { + this.endTime = new Date(this.dateValue).getTime(); + + this.update(); + this.timer = setInterval(() => { + this.update(); + }, this.refreshIntervalValue); + } else { + console.log("Missing data-countdown-date-value attribute", this.element); + } + } + + disconnect() { + this.stopTimer(); + } + + stopTimer() { + if(this.timer) { + clearInterval(this.timer); + } + } + + update() { + let difference = this.timeDifference(); + + if (difference < 0) { + var expiredMsg = $("#timer_message").data("expiredMessage"); + $("#timer_message").html(expiredMsg); + this.stopTimer(); + return; + } + + let days = Math.floor(difference / (1000 * 60 * 60 * 24)); + let hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + let minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); + let seconds = Math.floor((difference % (1000 * 60)) / 1000); + + this.element.innerHTML = this.messageTimerValue + .replace("${days}", days) + .replace("${hours}", hours) + .replace("${minutes}", minutes) + .replace("${seconds}", seconds) + } + + timeDifference() { + return this.endTime - new Date().getTime(); + } +} \ No newline at end of file diff --git a/app/javascript/controllers/daily_summary_controller.js b/app/javascript/controllers/daily_summary_controller.js new file mode 100644 index 000000000..3bb504d83 --- /dev/null +++ b/app/javascript/controllers/daily_summary_controller.js @@ -0,0 +1,12 @@ +import { Controller } from "@hotwired/stimulus" +import { get } from '@rails/request.js' + +export default class extends Controller { + updateDailtSummary(_event) { + clearTimeout(this.timeout) + + this.timeout = setTimeout(() => { + get('/profile/toggle_subscription') + }, 500) + } +} diff --git a/app/javascript/controllers/english_offers_controller.js b/app/javascript/controllers/english_offers_controller.js new file mode 100644 index 000000000..76940f4b4 --- /dev/null +++ b/app/javascript/controllers/english_offers_controller.js @@ -0,0 +1,40 @@ +// app/javascript/controllers/english_offers_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + userId: String, + decimalMark: String, + bidText: String, + participateText: String, + englishText: String, + blindText: String + } + + beforeStreamRender(e){ + var content = e.target.templateElement.content; + var currentPrice = content.querySelector(".current_price"); + var auctionRow = content.querySelector(".auctions-table-row"); + if (currentPrice) { + let offerUserId = currentPrice.dataset.userId; + let you = currentPrice.dataset.you; + if (this.values.userId === offerUserId) { + currentPrice.style.color = "green"; + var currentPriceWrapper = document.getElementById("current_price_wrapper"); + currentPriceWrapper.style.color = "green"; + currentPriceWrapper.className = ""; + currentPrice.querySelector(".bidder").textContent = "(" + you + ")"; + } else { + currentPrice.style.color = "red"; + var currentPriceWrapper = document.getElementById("current_price_wrapper"); + currentPriceWrapper.style.color = "red"; + currentPriceWrapper.className = ""; + } + } + if (auctionRow) { + let platform = auctionRow.dataset.platform; + auctionRow.querySelector(".bid_button").textContent = this.values.bidText; + auctionRow.querySelector(".auction_platform").textContent = this.values[platform + 'Text']; + } + } +} diff --git a/app/javascript/controllers/form/autobider_submit_controller.js b/app/javascript/controllers/form/autobider_submit_controller.js new file mode 100644 index 000000000..4c9e388cd --- /dev/null +++ b/app/javascript/controllers/form/autobider_submit_controller.js @@ -0,0 +1,44 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["form", "price", "checkbox"] + + connect() { + this.validatingInputPrice(); + } + + disconnect() { + if (this.timeout) { + clearTimeout(this.timeout) + } + } + + submitAutobider() { + clearTimeout(this.timeout) + + if (parseFloat(this.priceTarget.value) <= 5.0) { + return + } + + this.timeout = setTimeout(() => { + this.formTarget.requestSubmit() + }, 300) + } + + validatePrice(event) { + const char = event.key; + const value = this.priceTarget.value; + + if (![".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "Backspace", "Delete", "Tab", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(char) || (char == '.' && value.includes('.'))) { + event.preventDefault(); + } + } + + validatingInputPrice() { + if (parseFloat(this.priceTarget.value) <= 5.0) { + this.checkboxTarget.disabled = true; + } else { + this.checkboxTarget.disabled = false; + } + } +} \ No newline at end of file diff --git a/app/javascript/controllers/form/autosave_controller.js b/app/javascript/controllers/form/autosave_controller.js new file mode 100644 index 000000000..46b89410a --- /dev/null +++ b/app/javascript/controllers/form/autosave_controller.js @@ -0,0 +1,20 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["form", "select"] + static values = { + select: String, + } + + connect() { + this.selectValue = this.selectTarget.value; + } + + save() { + if (confirm("Are you sure?") == true) { + this.formTarget.requestSubmit() + } else { + this.selectTarget.value = this.selectValue; + } + } +} diff --git a/app/javascript/controllers/form/bundle_checkbox_controller.js b/app/javascript/controllers/form/bundle_checkbox_controller.js new file mode 100644 index 000000000..8444d9144 --- /dev/null +++ b/app/javascript/controllers/form/bundle_checkbox_controller.js @@ -0,0 +1,20 @@ +// app/javascript/controllers/debounce_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["hiddenField", "checkboxes"]; + + collect_ids() { + if(this.hasCheckboxesTarget == false) return; + + const regx = /\d+/; + this.hiddenFieldTarget.value = ''; + + this.checkboxesTargets.forEach((el) => { + if (el.checked == false) return; + + const element_id = el.id; + this.hiddenFieldTarget.value += ` ${element_id.match(regx)}` + }); + } +} diff --git a/app/javascript/controllers/form/check_all_controller.js b/app/javascript/controllers/form/check_all_controller.js new file mode 100644 index 000000000..d56e7bc70 --- /dev/null +++ b/app/javascript/controllers/form/check_all_controller.js @@ -0,0 +1,12 @@ +// app/javascript/controllers/check_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["checkboxes"] + + checkAll() { + this.checkboxesTargets.forEach((checkbox) => { + checkbox.checked = !checkbox.checked; + }) + } +} diff --git a/app/javascript/controllers/form/checkbox_autosubmit_controller.js b/app/javascript/controllers/form/checkbox_autosubmit_controller.js new file mode 100644 index 000000000..b8cb2e0ac --- /dev/null +++ b/app/javascript/controllers/form/checkbox_autosubmit_controller.js @@ -0,0 +1,37 @@ +import { Controller } from "@hotwired/stimulus" +import { FetchRequest } from '@rails/request.js' + +export default class extends Controller { + static targets = ["checkbox"]; + static values = { url: String } + + submit() { + clearTimeout(this.timeout) + + this.timeout = setTimeout(() => { + this.performRequest() + }, 300) + } + + disconnect() { + if (this.timeout) { + clearTimeout(this.timeout) + } + } + + performRequest() { + const originalState = this.checkboxTarget.checked; + + const request = new FetchRequest("PATCH", this.urlValue, { + headers: { responseKind: "turbo-stream", accept: "text/vnd.turbo-stream.html" }, + body: new FormData(this.checkboxTarget.form) + }); + + const response = request.perform(); + + if (!response.ok) { + this.checkboxTarget.checked = !originalState; + console.error("Failed to update user profile", response) + } + } +} \ No newline at end of file diff --git a/app/javascript/controllers/form/checkbox_toggle_controller.js b/app/javascript/controllers/form/checkbox_toggle_controller.js new file mode 100644 index 000000000..ca448a9d4 --- /dev/null +++ b/app/javascript/controllers/form/checkbox_toggle_controller.js @@ -0,0 +1,16 @@ +// app/javascript/controllers/check_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["enableDeposit", "disableDeposit"]; + + connect() { + this.enableDepositTarget.addEventListener('click', (source) => { + this.disableDepositTarget.checked = false; + }); + + this.disableDepositTarget.addEventListener('click', (source) => { + this.enableDepositTarget.checked = false; + }); + } +} diff --git a/app/javascript/controllers/form/debounce_controller.js b/app/javascript/controllers/form/debounce_controller.js new file mode 100644 index 000000000..f66e4615f --- /dev/null +++ b/app/javascript/controllers/form/debounce_controller.js @@ -0,0 +1,13 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["form"] + + search() { + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + + this.formTarget.requestSubmit() + }, 300) + } +} diff --git a/app/javascript/controllers/form/filter_controller.js b/app/javascript/controllers/form/filter_controller.js new file mode 100644 index 000000000..950905649 --- /dev/null +++ b/app/javascript/controllers/form/filter_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["button", "label"] + + click(event) { + const label = event.currentTarget + + this.resetAll() + label.classList.add('is-active') + } + + resetAll() { + this.buttonTargets.forEach(r => r.checked = false) + this.labelTargets.forEach(l => l.classList.remove('is-active')) + } +} diff --git a/app/javascript/controllers/google_analytics_controller.js b/app/javascript/controllers/google_analytics_controller.js new file mode 100644 index 000000000..f798470d1 --- /dev/null +++ b/app/javascript/controllers/google_analytics_controller.js @@ -0,0 +1,27 @@ +// app/javascript/controllers/english_offers_controller.js +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + const googleId = document + .getElementById('google-tracking-id') + .getAttribute('data-value'); + + if (googleId) { + window.dataLayer = window.dataLayer || []; + function gtag() { + dataLayer.push(arguments); + }; + gtag('js', new Date()); + gtag('config', googleId); + + gtag('config', googleId, { + // 'page_location': event.detail.url, + 'cookie_prefix': 'AuctionTest', + 'cookie_domain': 'auction.ee', + }); + }; + + console.log('Google Analytics controller connected...'); + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 000000000..b80083b8e --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,83 @@ +// This file is auto-generated by ./bin/rails stimulus:manifest:update +// Run that command whenever you add a new controller or create them with +// ./bin/rails generate stimulus controllerName + +import { application } from "./application" + +import Modals__OfferModalController from "./modals/offer_modal_controller"; +application.register("modals--offer-modal", Modals__OfferModalController); + +import Modals__ModalController from "./modals/modal_controller"; +application.register("modals--modal", Modals__ModalController); + +import Form__DebounceController from "./form/debounce_controller"; +application.register("form--debounce", Form__DebounceController); + +import Form__FilterController from "./form/filter_controller"; +application.register("form--filter", Form__FilterController); + +import Form__AutobiderSubmitController from "./form/autobider_submit_controller"; +application.register("form--autobider-submit", Form__AutobiderSubmitController); + +import Form__BundleCheckboxController from "./form/bundle_checkbox_controller"; +application.register("form--bundle-checkbox", Form__BundleCheckboxController); + +import Form__CheckboxToggleController from "./form/checkbox_toggle_controller"; +application.register("form--checkbox-toggle", Form__CheckboxToggleController); + +import Form__CheckboxAutosubmitController from "./form/checkbox_autosubmit_controller"; +application.register("form--checkbox-autosubmit", Form__CheckboxAutosubmitController); + +import Form__AutosaveController from "./form/autosave_controller"; +application.register("form--autosave", Form__AutosaveController); + +import Form__CheckAllController from "./form/check_all_controller"; +application.register("form--check-all", Form__CheckAllController); + +import Table__OrdeableController from "./table/ordeable_controller"; +application.register("table--ordeable", Table__OrdeableController); + +import Table__TabController from "./table/tab_controller"; +application.register("table--tab", Table__TabController); + +import AutotaxCounter from "./autotax_counter_controller"; +application.register("autotax-counter", AutotaxCounter); + +import InvoiceAutotaxCounter from "./invoice_autotax_counter_controller"; +application.register("invoice-autotax-counter", InvoiceAutotaxCounter); + +import EnglishOffer from "./english_offers_controller"; +application.register("english-offer", EnglishOffer); + +import CountDownController from "./countdown_controller"; +application.register("countdown", CountDownController); + +import TimeleftController from "./timeleft_controller" +application.register("timeleft", TimeleftController) + +import ProfileWebpushController from "./profile_webpush_controller"; +application.register("profile-webpush", ProfileWebpushController); + +import PushNotificationController from "./push_notification_controller"; +application.register("push-notification", PushNotificationController); + +import ToggleController from "./toggle_controller"; +application.register("toggle", ToggleController); + +import HamburgerController from "./navbar/hamburger_controller"; +application.register("hamburger", HamburgerController); + +import ExpandableController from "./navbar/expandable_controller"; +application.register("navbar--expandable", ExpandableController); + +import CookieConsentController from "./cookie_consent_controller"; +application.register("cookie-consent", CookieConsentController); + +import GoogleAnalyticsController from "./google_analytics_controller"; +application.register("google-analytics", GoogleAnalyticsController); + +import AuctionTimezoneController from "./auction_timezone_controller"; +application.register("auction-timezone", AuctionTimezoneController); + +import DailySummaryController from "./daily_summary_controller"; +application.register("daily-summary", DailySummaryController); diff --git a/app/javascript/controllers/invoice_autotax_counter_controller.js b/app/javascript/controllers/invoice_autotax_counter_controller.js new file mode 100644 index 000000000..ecaffc5cd --- /dev/null +++ b/app/javascript/controllers/invoice_autotax_counter_controller.js @@ -0,0 +1,51 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["result", "dropdown", "totalprice", "tax", "taxDescription"] + static values = { + template: String, + taxTemplate: String, + dirtyprice: Number, + invoiceUpdateUrl: String + } + + connect() { + this.updateTax(); + } + + count(taxValue) { + const tax = parseFloat(taxValue); + + // .replace('{tax}', (tax * 100.0).toFixed(2)) + + let dirtypriceValue = parseFloat(this.dirtypriceValue); + let taxAmount = dirtypriceValue * tax; + + this.taxDescriptionTarget.innerHTML = this.taxTemplateValue.replace('{tax}', (tax * 100).toFixed(2)); + this.taxTarget.innerHTML = taxAmount.toFixed(2); + this.totalpriceTarget.innerHTML = this.templateValue.replace('{price}', (dirtypriceValue + taxAmount).toFixed(2)); + + // const value = parseFloat(event.target.value) + // const result = this.resultTarget + + // if (!isNaN(value) && value > 0) { + // const tax = parseFloat(this.taxValue) || 0.0; + // const taxAmount = value * tax; + // const totalAmount = value + taxAmount; + + // result.innerHTML = this.templateValue.replace('{price}', totalAmount.toFixed(2)).replace('{tax}', (tax * 100.0).toFixed(2)); + // } else { + // result.innerHTML = this.defaulttemplateValue; + // } + } + + updateTax(event) { + // let selectElement = this.element.querySelector('select'); + // this.taxValue = selectElement.options[selectElement.selectedIndex].dataset.vatRate || 0.0; + // const priceElement = this.element.querySelector(this.priceElementValue); + + // this.count({target: priceElement}); + let taxValue = this.dropdownTarget.options[this.dropdownTarget.selectedIndex].dataset.vatRate || 0.0; + this.count(taxValue); + } +} diff --git a/app/javascript/controllers/modals/modal_controller.js b/app/javascript/controllers/modals/modal_controller.js new file mode 100644 index 000000000..efc1220ac --- /dev/null +++ b/app/javascript/controllers/modals/modal_controller.js @@ -0,0 +1,22 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["modal"] + + open(event) { + event.preventDefault(); + + this.modalTarget.showModal(); + this.modalTarget.addEventListener('click', (e) => this.backdropClick(e)); + } + + close(event) { + event.preventDefault(); + + this.modalTarget.close(); + } + + backdropClick(event) { + event.target === this.modalTarget && this.close(event); + } +} diff --git a/app/javascript/controllers/modals/offer_modal_controller.js b/app/javascript/controllers/modals/offer_modal_controller.js new file mode 100644 index 000000000..1842f9e74 --- /dev/null +++ b/app/javascript/controllers/modals/offer_modal_controller.js @@ -0,0 +1,14 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static classes = ['open'] + static targets = ['modal'] + + connect() { + this.modalTarget.classList.toggle(this.openClass); + } + + close() { + this.modalTarget.classList.toggle(this.openClass); + } +} diff --git a/app/javascript/controllers/navbar/expandable_controller.js b/app/javascript/controllers/navbar/expandable_controller.js new file mode 100644 index 000000000..62a2e4eef --- /dev/null +++ b/app/javascript/controllers/navbar/expandable_controller.js @@ -0,0 +1,13 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ['element'] + + expand() { + if (this.elementTarget.style.display === "block") { + this.elementTarget.style.display = "none"; + } else { + this.elementTarget.style.display = "block"; + } + } +} \ No newline at end of file diff --git a/app/javascript/controllers/navbar/hamburger_controller.js b/app/javascript/controllers/navbar/hamburger_controller.js new file mode 100644 index 000000000..863d54134 --- /dev/null +++ b/app/javascript/controllers/navbar/hamburger_controller.js @@ -0,0 +1,14 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["menu"] + static classes = ["open"] + + connect() {} + + toggle() { + this.menuTarget.classList.toggle(this.openClass); + this.menuTarget.style.display = this.menuTarget.classList.contains(this.openClass) ? "block" : "none"; + } + +} \ No newline at end of file diff --git a/app/javascript/controllers/profile_webpush_controller.js b/app/javascript/controllers/profile_webpush_controller.js new file mode 100644 index 000000000..ceb60d54d --- /dev/null +++ b/app/javascript/controllers/profile_webpush_controller.js @@ -0,0 +1,62 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + vapidPublic: String, + } + + static targets = ["checkbox"] + + connect() { + + console.log('webpush connected!'); + + if ('serviceWorker' in navigator && 'PushManager' in window) { + navigator.serviceWorker.ready.then(registration => { + registration.pushManager.getSubscription().then(subscription => { + if (subscription) { + this.checkboxTarget.style.disabled = true; + this.checkboxTarget.classList.add('disabled'); + } + } + )} + )} + } + + setupPushNotifications() { + const applicationServerKey = this.urlBase64ToUint8Array(this.vapidPublicValue); + + navigator.serviceWorker.register("/service-worker.js", {scope: "./" }).then((registration) => { + registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: applicationServerKey + }).then((subscription) => { + + const endpoint = subscription.endpoint; + const p256dh = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))); + const auth = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))); + + fetch('/push_subscriptions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content') + }, + body: JSON.stringify({ + subscription: { + endpoint: endpoint, + p256dh: p256dh, + auth: auth + } + }) + }); + + localStorage.setItem('block-webpush-modal', 'true'); + document.querySelector('.webpush-modal').style.display = 'none'; + this.checkboxTarget.style.disabled = true; + this.checkboxTarget.classList.add('disabled'); + }); + }); + } + +} \ No newline at end of file diff --git a/app/javascript/controllers/push_notification_controller.js b/app/javascript/controllers/push_notification_controller.js new file mode 100644 index 000000000..0df6ac1f7 --- /dev/null +++ b/app/javascript/controllers/push_notification_controller.js @@ -0,0 +1,107 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + vapidPublic: String, + userLogin: Boolean + }; + + connect() { + if(!this.userLoginValue) return; + + let subscribed = localStorage.getItem('block-webpush-modal'); + if (subscribed === 'true') { + const element = document.querySelector('.webpush-modal'); + if (element) { + element.style.display = 'none'; + } + } + + if ('serviceWorker' in navigator && 'PushManager' in window) { + navigator.serviceWorker.ready.then(registration => { + registration.pushManager.getSubscription().then(subscription => { + if (!subscription) { + if (Notification.permission === 'granted') { + this.setupPushNotifications(); + } else { + this.requestPermission(); + } + } + } + )} + )} + } + + requestPermission() { + window.addEventListener('load', () => { + Notification.requestPermission().then((permission) => { + if (permission === 'granted') { + this.setupPushNotifications(applicationServerKey); + } + }); + }); + } + + setupPushNotifications() { + const applicationServerKey = this.urlBase64ToUint8Array(this.vapidPublicValue); + + navigator.serviceWorker.register("/service-worker.js", {scope: "./" }).then((registration) => { + console.log('Service Worker registered successfully:', registration); + + registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: applicationServerKey + }).then((subscription) => { + + const endpoint = subscription.endpoint; + const p256dh = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))); + const auth = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))); + + fetch('/push_subscriptions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content') + }, + body: JSON.stringify({ + subscription: { + endpoint: endpoint, + p256dh: p256dh, + auth: auth + } + }) + }); + + localStorage.setItem('block-webpush-modal', 'true'); + document.querySelector('.webpush-modal').style.display = 'none' + }); + }).catch(err => { + console.log('Service Worker registration failed:', err); + });; + } + + close() { + document.querySelector('.webpush-modal').style.display = 'none' + } + + decline() { + localStorage.setItem('block-webpush-modal', 'true'); + document.querySelector('.webpush-modal').style.display = 'none' + } + + urlBase64ToUint8Array(base64String) { + var padding = '='.repeat((4 - base64String.length % 4) % 4); + var base64 = (base64String + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + + var rawData = window.atob(base64); + var outputArray = new Uint8Array(rawData.length); + + for (var i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + + return outputArray; + } +} diff --git a/app/javascript/controllers/table/ordeable_controller.js b/app/javascript/controllers/table/ordeable_controller.js new file mode 100644 index 000000000..40f2a52d1 --- /dev/null +++ b/app/javascript/controllers/table/ordeable_controller.js @@ -0,0 +1,36 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { direction: String, column: String, frameName: String } + static targets = ['th'] + static classes = ['asc', 'desc'] + + initialize() { + this.classHandle = this.classHandle.bind(this); + } + + resortTable(_event) { + Turbo.visit('?sort_by=' + this.columnValue + '&sort_direction=' + this.directionValue, { frame: this.frameNameValue }); + } + + directionValueChanged() { + this.classHandle(); + } + + columnValueChanged() { + this.classHandle(); + } + + classHandle() { + if (this.directionValue == 'asc') { + this.thTarget.classList.remove(this.ascClass); + this.thTarget.classList.add(this.descClass); + } else if (this.directionValue == 'desc') { + this.thTarget.classList.add(this.ascClass); + this.thTarget.classList.remove(this.descClass); + } else { + this.thTarget.classList.remove(this.ascClass); + this.thTarget.classList.remove(this.descClass); + } + } +} diff --git a/app/javascript/controllers/table/tab_controller.js b/app/javascript/controllers/table/tab_controller.js new file mode 100644 index 000000000..595bb606a --- /dev/null +++ b/app/javascript/controllers/table/tab_controller.js @@ -0,0 +1,45 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["tab", "content", "payablebtn"] + static classes = ["active"] + + initialize() { + this.showTab = this.showTab.bind(this); + } + + connect() { + if (this.payablebtnTargets.length > 0) { + this.payablebtnTargets[0].style.display = "block" + this.payablebtnTargets[1].style.display = "none" + } + } + + showTab(event) { + const index = event.params.index; + + this.tabTargets.forEach((tab) => { + tab.classList.remove(this.activeClass) + }) + + this.contentTargets.forEach((content) => { + content.classList.remove(this.activeClass) + }) + + this.tabTargets[index].classList.add(this.activeClass) + this.contentTargets[index].classList.add(this.activeClass) + + if (this.payablebtnTargets.length > 0) { + if (index == 0) { + this.payablebtnTargets[0].style.display = "block" + this.payablebtnTargets[1].style.display = "none" + } else if (index == 1) { + this.payablebtnTargets[1].style.display = "block" + this.payablebtnTargets[0].style.display = "none" + } else { + this.payablebtnTargets[0].style.display = "none" + this.payablebtnTargets[1].style.display = "none" + } + } + } +} diff --git a/app/javascript/controllers/timeleft_controller.js b/app/javascript/controllers/timeleft_controller.js new file mode 100644 index 000000000..ed0783f36 --- /dev/null +++ b/app/javascript/controllers/timeleft_controller.js @@ -0,0 +1,77 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + date: String, + refreshInterval: { type: Number, default: 1000 }, + messageTimer: String, + defaultMessageTimer: String + } + + static targets = [ "button" ] + + connect() { + this.update = this.update.bind(this); + + if (this.dateValue) { + this.endTime = new Date(this.dateValue).getTime(); + + this.update(); + + this.timer = setInterval(() => { + this.update(); + }, this.refreshIntervalValue); + } else { + console.log("Missing data-timeleft-date-value attribute", this.element); + } + } + + disconnect() { + this.stopTimer(); + } + + stopTimer() { + if(this.timer) { + clearInterval(this.timer); + } + } + + update() { + let difference = this.timeDifference(); + + if (difference < 0) { + var expiredMsg = $("#timer_message").data("expiredMessage"); + $("#timer_message").html(expiredMsg); + this.stopTimer(); + this.buttonTarget.removeAttribute("disabled"); + this.buttonTarget.innerHTML = this.defaultMessageTimerValue; + } else { + this.buttonTarget.setAttribute("disabled", "disabled"); + + let minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); + let seconds = Math.floor((difference % (1000 * 60)) / 1000); + + let formattedSeconds = seconds < 10 ? `0${seconds}` : seconds; + + this.buttonTarget.innerHTML = `${this.messageTimerValue} ${minutes}:${formattedSeconds}` + } + } + + + timeDifference() { + const convertedToTimeZone = this.convertDateToTimeZone(new Date().getTime(), 'Europe/Tallinn'); + return this.endTime - new Date(convertedToTimeZone).getTime(); + } + + convertDateToTimeZone(date, timeZone) { + return new Intl.DateTimeFormat('en-US', { + timeZone: timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }).format(date); + } +} \ No newline at end of file diff --git a/app/javascript/controllers/toggle_controller.js b/app/javascript/controllers/toggle_controller.js new file mode 100644 index 000000000..a9d12c3ce --- /dev/null +++ b/app/javascript/controllers/toggle_controller.js @@ -0,0 +1,10 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static classes = ['toggle'] + static targets = ['element'] + + toggle() { + this.elementTarget.classList.toggle(this.toggleClass); + } +} diff --git a/app/javascript/turbo_streams/close_modal.js b/app/javascript/turbo_streams/close_modal.js new file mode 100644 index 000000000..d846ff346 --- /dev/null +++ b/app/javascript/turbo_streams/close_modal.js @@ -0,0 +1,14 @@ +import { StreamActions } from "@hotwired/turbo" + +StreamActions.close_modal = function() { + + const modalClass = this.getAttribute("modal-class"); + const openClass = this.getAttribute("open-class"); + const formId = this.getAttribute("form-id"); + const modal = document.querySelector(`.${modalClass}`); + const form = document.getElementById(formId); + + form.addEventListener('submit', (event) => { + modal.classList.toggle(openClass); + }); +} diff --git a/app/javascript/turbo_streams/index.js b/app/javascript/turbo_streams/index.js new file mode 100644 index 000000000..6d66c6418 --- /dev/null +++ b/app/javascript/turbo_streams/index.js @@ -0,0 +1,2 @@ +import './toast.js' +import './close_modal.js' \ No newline at end of file diff --git a/app/javascript/turbo_streams/toast.js b/app/javascript/turbo_streams/toast.js new file mode 100644 index 000000000..b0d9e86c6 --- /dev/null +++ b/app/javascript/turbo_streams/toast.js @@ -0,0 +1,24 @@ +import { StreamActions } from "@hotwired/turbo" +import Toastify from 'toastify-js' + +StreamActions.toast = function() { + const message = this.getAttribute("message") + const position = this.getAttribute("position") + const background = this.getAttribute("background") + + Toastify({ + text: message, + duration: 5000, + destination: '', + close: true, + gravity: 'top', + position: position, + stopOnFocus: true, + offset: { + y: '9em' // vertical axis - can be a number or a string indicating unity. eg: '2em' + }, + style: { + background: background, + } + }).showToast() +} diff --git a/app/jobs/active_auctions_ai_sorting_job.rb b/app/jobs/active_auctions_ai_sorting_job.rb index ac2c859c2..d02ede7d2 100644 --- a/app/jobs/active_auctions_ai_sorting_job.rb +++ b/app/jobs/active_auctions_ai_sorting_job.rb @@ -7,7 +7,7 @@ def perform auctions_list = Auction.active_with_offers_count ai_response = fetch_ai_response(auctions_list) process_ai_response(ai_response) - rescue OpenAI::Error => e + rescue StandardError, OpenAI::Error => e handle_openai_error(e) end @@ -40,6 +40,7 @@ def process_ai_response(response) def fetch_ai_response(auctions_list) ai_client = OpenAI::Client.new response = ai_client.chat(parameters: chat_parameters(auctions_list)) + ai_response = response.dig('choices', 0, 'message', 'content') raise StandardError, response.dig('error', 'message') if ai_response.nil? @@ -52,8 +53,17 @@ def chat_parameters(auctions_list) messages: [ { role: 'system', content: system_message }, { role: 'user', content: format(auctions_list) }, - { role: 'user', content: 'Response in JSON format: [{id:, domain_name:, ai_score:}]' } - ] + { role: 'user', content: 'Please provide a detailed response in JSON format without any text and only the result. + Here is an example of how I expect the JSON output: + [ + { + id:, + domain_name:, + ai_score: + } + ]' } + ], + temperature: 0.7 } end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index f290088db..eb7639884 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -1,5 +1,6 @@ class ApplicationJob < ActiveJob::Base rescue_from StandardError do |e| + Rails.logger.info e Airbrake.notify(e) end end diff --git a/app/jobs/refund_job.rb b/app/jobs/refund_job.rb index 898b37d33..37b8a54c5 100644 --- a/app/jobs/refund_job.rb +++ b/app/jobs/refund_job.rb @@ -11,7 +11,7 @@ def perform(domain_participate_auction_id, invoice_number) d = DomainParticipateAuction.find(domain_participate_auction_id) if response.status == 200 - d.update(status: 'returned') + d.update(status: 'returned', refund_time: Time.zone.now) else inform(d, response.body) end diff --git a/app/jobs/result_status_update_job.rb b/app/jobs/result_status_update_job.rb index 82668594e..1d279d22c 100644 --- a/app/jobs/result_status_update_job.rb +++ b/app/jobs/result_status_update_job.rb @@ -1,8 +1,9 @@ class ResultStatusUpdateJob < ApplicationJob def perform - Result.pending_status_report.map do |result| + Result.pending_status_report.each do |result| Registry::StatusReporter.new(result).call - rescue Registry::CommunicationError => e + rescue StandardError, Registry::CommunicationError => e + Rails.logger.info e Airbrake.notify(e) next end diff --git a/app/models/ability.rb b/app/models/ability.rb index 47c813ba6..238e04bdd 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -15,6 +15,7 @@ def anonymous_user def participant can :manage, BillingProfile, user_id: user.id + can :manage, Notification, user_id: user.id can :pay_deposit, Auction can %i[read update], Invoice can %i[read create], PaymentOrder, user_id: user.id diff --git a/app/models/auction.rb b/app/models/auction.rb index fc20babdc..9c7b801c9 100644 --- a/app/models/auction.rb +++ b/app/models/auction.rb @@ -1,6 +1,5 @@ class Auction < ApplicationRecord # rubocop:disable Metrics include Presentable - include SqlQueriable include Searchable include PgSearch::Model @@ -19,7 +18,7 @@ class Auction < ApplicationRecord # rubocop:disable Metrics validate :deposit_and_enable_deposit_should_be_togeter, on: :update has_many :offers, dependent: :delete_all - has_many :domain_participate_auctions + has_many :domain_participate_auctions, dependent: :delete_all has_many :domain_offer_histories has_one :result, required: false, dependent: :destroy @@ -147,12 +146,12 @@ def currently_winning_offer end def current_price_from_user(user_id) - offers_query = offers.where(user_id: user_id) + offers_query = offers.where(user_id:) offers_query.order(cents: :desc).first&.price end def offer_from_user(user_id) - offers.where(user_id: user_id).order(cents: :desc).first + offers.where(user_id:).order(cents: :desc).first end def ends_at_later_than_starts_at @@ -167,7 +166,7 @@ def overlaping_auctions dates_order = [starts_at, ends_at].sort sql = "domain_name = ? AND tsrange(starts_at, ends_at, '[]') && tsrange(?, ?, '[]')" auctions = Auction.unscoped.where(sql, domain_name, dates_order.first, dates_order.second) - auctions = auctions.where.not(id: id) if persisted? + auctions = auctions.where.not(id:) if persisted? auctions end @@ -182,6 +181,8 @@ def can_be_deleted? end def finished? + return false if ends_at.nil? + if valid? Time.now.utc > ends_at else @@ -204,7 +205,7 @@ def find_auction_turns end def calculate_turns_count - auctions = Auction.unscoped.where(domain_name: domain_name).where('starts_at <= ?', starts_at) + auctions = Auction.unscoped.where(domain_name:).where('starts_at <= ?', starts_at) result_statuses = auctions.order(:ends_at).map { |auction| auction.result&.status } return 1 unless result_statuses.present? && result_statuses.first.present? diff --git a/app/models/bank_transaction.rb b/app/models/bank_transaction.rb index ea53d2005..8f1fcf1e4 100644 --- a/app/models/bank_transaction.rb +++ b/app/models/bank_transaction.rb @@ -32,7 +32,8 @@ def autobind_invoice payment_order = PaymentOrder.find_by(invoice_id: invoice.id) || PaymentOrders::EveryPay.create(invoices: [invoice], user: invoice.user) - payment_order.response = { 'transaction_time' => Time.zone.now, 'payment_state' => 'settled' } + payment_order.response = { 'transaction_time' => Time.zone.now, 'payment_state' => 'settled', + 'initial_amount' => invoice.total.to_f } payment_order.save payment_order.mark_invoice_as_paid diff --git a/app/models/billing_profile.rb b/app/models/billing_profile.rb index 5bdf05811..ed58c63ae 100644 --- a/app/models/billing_profile.rb +++ b/app/models/billing_profile.rb @@ -38,7 +38,9 @@ def self.search(params = {}) end def vat_code_must_be_registered_in_vies - return if vat_code.blank? + # Vat code validation only for EU countries + vat_rate = Countries.vat_rate_from_alpha2_code(self.country_code) + return if vat_code.blank? || vat_rate == BigDecimal(0) errors.add(:vat_code, I18n.t('billing_profiles.vat_validation_error')) unless Valvat.new(self.vat_code).exists? end diff --git a/app/models/concerns/auction/searchable.rb b/app/models/concerns/auction/searchable.rb index 4d1f8262a..45b582f3d 100644 --- a/app/models/concerns/auction/searchable.rb +++ b/app/models/concerns/auction/searchable.rb @@ -1,10 +1,28 @@ # frozen_string_literal: true +# rubocop:disable Metrics/BlockLength +# rubocop:disable Metrics/ModuleLength module Auction::Searchable extend ActiveSupport::Concern - BLIND = '0' - ENGLISH = '1' + BLIND = 'blind' + ENGLISH = 'english' + + FILTERING_COLUMNS = %w[domain_name + starts_at + ends_at + highest_offer_cents + number_of_offers + turns_count + starting_price + min_bids_step + slipping_end + platform + requirement_deposit_in_cents + enable_deposit].freeze + + PARAM_LIST = %w[domain_name starts_at ends_at platform users_price].freeze + DIRECTION = %w[asc desc].freeze included do scope :active, -> { where('starts_at <= ? AND ends_at >= ?', Time.now.utc, Time.now.utc) } @@ -22,7 +40,6 @@ module Auction::Searchable } scope :without_offers, -> { includes(:offers).where(offers: { auction_id: nil }) } - scope :with_offers, -> { includes(:offers).where.not(offers: { auction_id: nil }) } scope :with_domain_name, (lambda do |domain_name| return unless domain_name.present? @@ -30,7 +47,7 @@ module Auction::Searchable end) scope :with_type, (lambda do |type| - if type.present? + if type.present? && type.in?([BLIND, ENGLISH]) return where(platform: [type, nil]) if type == BLIND where(platform: type) @@ -63,23 +80,13 @@ module Auction::Searchable end) end + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize class_methods do def search(params = {}, current_user = nil) - param_list = %w[domain_name starts_at ends_at platform users_price] - sort_column = params[:sort].presence_in(param_list) || 'domain_name' - sort_admin_column = params[:sort].presence_in(%w[domain_name - starts_at - ends_at - highest_offer_cents - number_of_offers - turns_count - starting_price - min_bids_step - slipping_end - platform - requirement_deposit_in_cents - enable_deposit]) || 'id' - sort_direction = params[:direction].presence_in(%w[asc desc]) || 'desc' + sort_column = params[:sort_by].presence_in(PARAM_LIST) || 'domain_name' + sort_admin_column = params[:sort_by].presence_in(FILTERING_COLUMNS) || 'id' + sort_direction = params[:sort_direction].presence_in(DIRECTION) || 'desc' is_from_admin = params[:admin] == 'true' query = @@ -91,14 +98,46 @@ def search(params = {}, current_user = nil) .with_starts_at_nil(params[:starts_at_nil]) .with_offers(params[:auction_offer_type], params[:type]) - if params[:sort] == 'users_price' + if params[:sort_by] == 'users_price' query.with_max_offer_cents_for_english_auction(current_user) .order("offers_subquery.max_offer_cents #{sort_direction} NULLS LAST") - elsif params[:sort] == 'username' + elsif params[:sort_by] == 'username' query.sorted_by_winning_offer_username.order("offers_subquery.username #{sort_direction} NULLS LAST") else query.order("#{is_from_admin ? sort_admin_column : sort_column} #{sort_direction} NULLS LAST") end end + + def with_user_offers(user_id) + Auction.from(with_user_offers_query(user_id)) + end + + def with_user_offers_query(user_id) + Queries::Auction::WithUserOffersQuery.call(user_id:) + end + + def with_highest_offers + Auction.from(with_highest_offers_query) + end + + def with_highest_offers_query + Queries::Auction::WithHighestOffersQuery.call + end + + def active_with_offers_count + Auction.active.from(with_offers_count_query) + end + + def with_offers_count_query + Queries::Auction::WithOffersCountQuery.call + end + + def with_max_offer_cents_for_english_auction(user = nil) + Queries::Auction::WithMaxOfferCentsForEnglishAuction.call(user: user) + end + + def sorted_by_winning_offer_username + Queries::Auction::SortedByWinningOfferUsername.call + end end end diff --git a/app/models/concerns/auction/sql_queriable.rb b/app/models/concerns/auction/sql_queriable.rb deleted file mode 100644 index 63c049eb3..000000000 --- a/app/models/concerns/auction/sql_queriable.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -module Auction::SqlQueriable # rubocop:disable Metrics/ModuleLength - extend ActiveSupport::Concern - - class_methods do - def with_user_offers(user_id) - Auction.from(with_user_offers_query(user_id)) - end - - def with_user_offers_query(user_id) - sql = <<~SQL - (WITH offers_subquery AS ( - SELECT * - FROM offers - WHERE user_id = ? - ) - SELECT DISTINCT - auctions.*, - offers_subquery.cents AS users_offer_cents, - offers_subquery.id AS users_offer_id, - offers_subquery.uuid AS users_offer_uuid - FROM auctions - LEFT JOIN offers_subquery on auctions.id = offers_subquery.auction_id) AS auctions - SQL - - ActiveRecord::Base.sanitize_sql([sql, user_id]) - end - - def with_highest_offers - Auction.from(with_highest_offers_query) - end - - def with_highest_offers_query - <<~SQL - (WITH offers_subquery AS ( - SELECT DISTINCT on (uuid) offers.* - FROM (SELECT auction_id, - max(cents) over (PARTITION BY auction_id) as max_price, - min(created_at) over (PARTITION BY auction_id, cents) as min_time - FROM offers - ) AS highest_offers - INNER JOIN offers - ON - offers.cents = highest_offers.max_price AND - offers.created_at = highest_offers.min_time AND - offers.auction_id = highest_offers.auction_id) - SELECT DISTINCT - auctions.*, - offers_subquery.cents AS highest_offer_cents, - offers_subquery.id AS highest_offer_id, - offers_subquery.uuid AS highest_offer_uuid, - (SELECT COUNT(*) FROM offers where offers.auction_id = auctions.id) AS number_of_offers - FROM auctions - LEFT JOIN offers_subquery on auctions.id = offers_subquery.auction_id) AS auctions - SQL - end - - def active_with_offers_count - Auction.active.from(with_offers_count_query) - end - - def with_offers_count_query - <<~SQL - (SELECT - auctions.*, - offers.cents, - recent_offers.offers_count - FROM auctions - LEFT JOIN ( - SELECT auction_id, - MAX(updated_at) as last_updated_at, - COUNT(*) as offers_count - FROM offers - GROUP BY auction_id - ) AS recent_offers ON auctions.id = recent_offers.auction_id - LEFT JOIN offers ON auctions.id = offers.auction_id AND offers.updated_at = recent_offers.last_updated_at) AS auctions - SQL - end - - def with_max_offer_cents_for_english_auction(user = nil) - if user - joins(<<-SQL - LEFT JOIN ( - SELECT auction_id, MAX(cents) AS max_offer_cents - FROM offers - WHERE auction_id IN ( - SELECT id FROM auctions WHERE platform = 1 - UNION - SELECT auction_id FROM offers WHERE user_id = #{user.id} AND auction_id IN (SELECT id FROM auctions WHERE platform IS NULL OR platform = 0) - ) - GROUP BY auction_id - ) AS offers_subquery ON auctions.id = offers_subquery.auction_id - SQL - ) - else - joins(<<-SQL - LEFT JOIN ( - SELECT auction_id, MAX(cents) AS max_offer_cents - FROM offers - WHERE auction_id IN (SELECT id FROM auctions WHERE platform = 1) - GROUP BY auction_id - ) AS offers_subquery ON auctions.id = offers_subquery.auction_id - SQL - ) - end - end - - def sorted_by_winning_offer_username - joins(<<-SQL - LEFT JOIN ( - SELECT offers.auction_id, offers.username - FROM offers - WHERE offers.cents = ( - SELECT MAX(offers_inner.cents) - FROM offers AS offers_inner - WHERE offers_inner.auction_id = offers.auction_id - ) - GROUP BY offers.auction_id, offers.username - ) AS offers_subquery ON auctions.id = offers_subquery.auction_id - SQL - ) - end - end -end diff --git a/app/models/concerns/invoice/payable.rb b/app/models/concerns/invoice/payable.rb index c04428ab2..7975d75ef 100644 --- a/app/models/concerns/invoice/payable.rb +++ b/app/models/concerns/invoice/payable.rb @@ -12,10 +12,7 @@ def payable? issued? || cancelled_and_have_valid_ban? end - private - def cancelled_and_have_valid_ban? cancelled? && Ban.valid.where(invoice_id: id).present? end end - diff --git a/app/models/invoice.rb b/app/models/invoice.rb index eaf1d8961..e9a8ff24d 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -165,15 +165,6 @@ def items=(items) self.invoice_items = items end - def user_id_must_be_the_same_as_on_billing_profile_or_nil - return unless billing_profile - return unless user - - return if billing_profile.user_id == user_id - - errors.add(:billing_profile, I18n.t('invoices.billing_profile_must_belong_to_user')) - end - def auction_currency Rails.cache.fetch(cache_key_with_version, expires_in: 12.hours) do Setting.find_by(code: 'auction_currency').retrieve @@ -185,11 +176,14 @@ def price end def total - if result.auction.enable_deposit? - (price * (1 + vat_rate) - deposit).round(2) - else - (price * (1 + vat_rate)).round(2) - end + total_amount = price * (1 + vat_rate) + total_amount -= deposit if result.auction.enable_deposit? + + total_amount.round(2) + end + + def due_amount + total - Money.from_amount(paid_amount || 0, auction_currency) end def vat @@ -224,24 +218,37 @@ def billing_vat_rate # paid in the user interface. def mark_as_paid_at(time) ActiveRecord::Base.transaction do - prepare_payment_fields(time) + self.vat_rate = billing_profile.present? ? billing_profile.vat_rate : vat_rate + self.paid_amount = total + self.paid_at = time result.mark_as_payment_received(time) unless cancelled? clear_linked_ban paid! end + ResultStatusUpdateJob.perform_now end def mark_as_paid_at_with_payment_order(time, payment_order) ActiveRecord::Base.transaction do - prepare_payment_fields(time) + self.vat_rate = billing_profile.present? ? billing_profile.vat_rate : vat_rate + initial_amount = payment_order.response['initial_amount'].to_f + self.paid_amount ||= 0 + self.paid_amount += initial_amount.to_f + self.paid_with_payment_order = payment_order - result.mark_as_payment_received(time) unless cancelled? - clear_linked_ban - paid! + if finally_paid? + self.paid_at = time + result.mark_as_payment_received(time) unless cancelled? + clear_linked_ban + paid! + else + save! + end end + ResultStatusUpdateJob.perform_now end @@ -291,15 +298,22 @@ def self.numeric?(string) private + def user_id_must_be_the_same_as_on_billing_profile_or_nil + return unless billing_profile + return unless user + + return if billing_profile.user_id == user_id + + errors.add(:billing_profile, I18n.t('invoices.billing_profile_must_belong_to_user')) + end + def clear_linked_ban ban = Ban.find_by(invoice_id: id) ban.lift if ban.present? end - def prepare_payment_fields(time) - self.paid_at = time - self.vat_rate = billing_profile.present? ? billing_profile.vat_rate : vat_rate - self.paid_amount = total + def finally_paid? + due_amount <= 0 end end # rubocop:enable Metrics/ClassLength diff --git a/app/models/invoice_cancellation.rb b/app/models/invoice_cancellation.rb index 5a10f337b..8ef14a80a 100644 --- a/app/models/invoice_cancellation.rb +++ b/app/models/invoice_cancellation.rb @@ -7,6 +7,7 @@ def initialize(invoice) def cancel return unless invoice.overdue? + return if invoice.number.blank? ActiveRecord::Base.transaction do result.payment_not_received! diff --git a/app/models/notification.rb b/app/models/notification.rb index 77646b092..75f91214e 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -2,8 +2,8 @@ class Notification < ApplicationRecord include Noticed::Model belongs_to :recipient, polymorphic: true - after_create_commit :broadcast_to_bell - after_create_commit :broadcast_to_recipient + # after_create_commit :broadcast_to_bell + # after_create_commit :broadcast_to_recipient def self.notify subscriptions = WebpushSubscription.all @@ -30,27 +30,27 @@ def self.notify end end - def broadcast_to_recipient - broadcast_append_later_to( - recipient, - :notifications, - target: 'notifications-list', - partial: 'notifications/notification', - locals: { - notification: self - } - ) - end + # def broadcast_to_recipient + # broadcast_append_later_to( + # recipient, + # :notifications, + # target: 'notifications-list', + # partial: 'notifications/notification', + # locals: { + # notification: self + # } + # ) + # end - def broadcast_to_bell - broadcast_update_later_to( - recipient, - :notifications, - target: 'bell-broadcast', - partial: 'notifications/bell', - locals: { - any_unread: true - } - ) - end + # def broadcast_to_bell + # broadcast_update_later_to( + # recipient, + # :notifications, + # target: 'bell-broadcast', + # partial: 'notifications/bell', + # locals: { + # any_unread: true + # } + # ) + # end end diff --git a/app/models/offer.rb b/app/models/offer.rb index 7bb2a8cee..335c8b7b9 100644 --- a/app/models/offer.rb +++ b/app/models/offer.rb @@ -1,5 +1,7 @@ class Offer < ApplicationRecord include ActionView::RecordIdentifier + include Broadcast + include Searchable belongs_to :user, optional: true belongs_to :auction, optional: false @@ -17,9 +19,6 @@ class Offer < ApplicationRecord DEFAULT_PRICE_VALUE = 1 - after_create_commit :broadcast_replace_auction - after_update_commit :broadcast_replace_auction - attr_accessor :skip_autobider, :skip_if_wishlist_case, :skip_validation after_create :update_auction_ends_at diff --git a/app/models/offer/broadcast.rb b/app/models/offer/broadcast.rb new file mode 100644 index 000000000..d96fcd947 --- /dev/null +++ b/app/models/offer/broadcast.rb @@ -0,0 +1,14 @@ +module Offer::Broadcast + extend ActiveSupport::Concern + + included do + after_create_commit :broadcast_replace_auction + after_update_commit :broadcast_replace_auction + end + + def broadcast_replace_auction + return if auction.platform == 'blind' || auction.platform.nil? + + Offers::ReplaceBroadcastService.call({ offer: self }) + end +end diff --git a/app/models/offer/searchable.rb b/app/models/offer/searchable.rb new file mode 100644 index 000000000..1eff8664e --- /dev/null +++ b/app/models/offer/searchable.rb @@ -0,0 +1,18 @@ +module Offer::Searchable + extend ActiveSupport::Concern + + included do + scope :filter_by_domain_name, -> { + includes(:auction).where("auctions.domain_name LIKE ?", "%#{params[:domain_name]}%") + } + end + + class_methods do + def search(params = {}) + sort_by = params[:sort_by] || 'created_at' + sort_direction = params[:sort_direction] || 'desc' + + includes(:auction).order("#{sort_by} #{sort_direction}") + end + end +end diff --git a/app/models/payment_orders/every_pay.rb b/app/models/payment_orders/every_pay.rb index 5d3f15eea..42f3db80d 100644 --- a/app/models/payment_orders/every_pay.rb +++ b/app/models/payment_orders/every_pay.rb @@ -57,15 +57,11 @@ def mark_invoice_as_paid Invoice.transaction do invoices.each do |invoice| - process_payment(invoice, time) + invoice.mark_as_paid_at_with_payment_order(time, self) end end end - def process_payment(invoice, time) - invoice.mark_as_paid_at_with_payment_order(time, self) - end - # Check if the intermediary reports payment as settled and we can expect money on # our accounts def settled_payment? diff --git a/app/models/queries/auction/sorted_by_winning_offer_username.rb b/app/models/queries/auction/sorted_by_winning_offer_username.rb new file mode 100644 index 000000000..088ead994 --- /dev/null +++ b/app/models/queries/auction/sorted_by_winning_offer_username.rb @@ -0,0 +1,25 @@ +module Queries::Auction + class SortedByWinningOfferUsername + class << self + def call + new.call + end + end + + def call + Auction.joins(<<-SQL + LEFT JOIN ( + SELECT offers.auction_id, offers.username + FROM offers + WHERE offers.cents = ( + SELECT MAX(offers_inner.cents) + FROM offers AS offers_inner + WHERE offers_inner.auction_id = offers.auction_id + ) + GROUP BY offers.auction_id, offers.username + ) AS offers_subquery ON auctions.id = offers_subquery.auction_id + SQL + ) + end + end +end diff --git a/app/models/queries/auction/with_highest_offers_query.rb b/app/models/queries/auction/with_highest_offers_query.rb new file mode 100644 index 000000000..2e341e677 --- /dev/null +++ b/app/models/queries/auction/with_highest_offers_query.rb @@ -0,0 +1,34 @@ +module Queries::Auction + class WithHighestOffersQuery + class << self + def call + new.call + end + end + + def call + <<~SQL + (WITH offers_subquery AS ( + SELECT DISTINCT on (uuid) offers.* + FROM (SELECT auction_id, + max(cents) over (PARTITION BY auction_id) as max_price, + min(created_at) over (PARTITION BY auction_id, cents) as min_time + FROM offers + ) AS highest_offers + INNER JOIN offers + ON + offers.cents = highest_offers.max_price AND + offers.created_at = highest_offers.min_time AND + offers.auction_id = highest_offers.auction_id) + SELECT DISTINCT + auctions.*, + offers_subquery.cents AS highest_offer_cents, + offers_subquery.id AS highest_offer_id, + offers_subquery.uuid AS highest_offer_uuid, + (SELECT COUNT(*) FROM offers where offers.auction_id = auctions.id) AS number_of_offers + FROM auctions + LEFT JOIN offers_subquery on auctions.id = offers_subquery.auction_id) AS auctions + SQL + end + end +end diff --git a/app/models/queries/auction/with_max_offer_cents_for_english_auction.rb b/app/models/queries/auction/with_max_offer_cents_for_english_auction.rb new file mode 100644 index 000000000..bd8bc9438 --- /dev/null +++ b/app/models/queries/auction/with_max_offer_cents_for_english_auction.rb @@ -0,0 +1,41 @@ +module Queries::Auction + class WithMaxOfferCentsForEnglishAuction + class << self + def call(user: nil) + new(user:).call + end + end + + def initialize(user: nil) + @user = user + end + + def call + if @user + Auction.joins(<<-SQL + LEFT JOIN ( + SELECT auction_id, MAX(cents) AS max_offer_cents + FROM offers + WHERE auction_id IN ( + SELECT id FROM auctions WHERE platform = 1 + UNION + SELECT auction_id FROM offers WHERE user_id = #{@user.id} AND auction_id IN (SELECT id FROM auctions WHERE platform IS NULL OR platform = 0) + ) + GROUP BY auction_id + ) AS offers_subquery ON auctions.id = offers_subquery.auction_id + SQL + ) + else + Auction.joins(<<-SQL + LEFT JOIN ( + SELECT auction_id, MAX(cents) AS max_offer_cents + FROM offers + WHERE auction_id IN (SELECT id FROM auctions WHERE platform = 1) + GROUP BY auction_id + ) AS offers_subquery ON auctions.id = offers_subquery.auction_id + SQL + ) + end + end + end +end diff --git a/app/models/queries/auction/with_offers_count_query.rb b/app/models/queries/auction/with_offers_count_query.rb new file mode 100644 index 000000000..0a4d1a2ca --- /dev/null +++ b/app/models/queries/auction/with_offers_count_query.rb @@ -0,0 +1,27 @@ +module Queries::Auction + class WithOffersCountQuery + class << self + def call + new.call + end + end + + def call + <<~SQL + (SELECT + auctions.*, + offers.cents, + recent_offers.offers_count + FROM auctions + LEFT JOIN ( + SELECT auction_id, + MAX(updated_at) as last_updated_at, + COUNT(*) as offers_count + FROM offers + GROUP BY auction_id + ) AS recent_offers ON auctions.id = recent_offers.auction_id + LEFT JOIN offers ON auctions.id = offers.auction_id AND offers.updated_at = recent_offers.last_updated_at) AS auctions + SQL + end + end +end diff --git a/app/models/queries/auction/with_user_offers_query.rb b/app/models/queries/auction/with_user_offers_query.rb new file mode 100644 index 000000000..3e6c47a0a --- /dev/null +++ b/app/models/queries/auction/with_user_offers_query.rb @@ -0,0 +1,32 @@ +module Queries::Auction + class WithUserOffersQuery + class << self + def call(user_id:) + new(user_id:).call + end + end + + def initialize(user_id:) + @user_id = user_id + end + + def call + sql = <<~SQL + (WITH offers_subquery AS ( + SELECT * + FROM offers + WHERE user_id = ? + ) + SELECT DISTINCT + auctions.*, + offers_subquery.cents AS users_offer_cents, + offers_subquery.id AS users_offer_id, + offers_subquery.uuid AS users_offer_uuid + FROM auctions + LEFT JOIN offers_subquery on auctions.id = offers_subquery.auction_id) AS auctions + SQL + + ActiveRecord::Base.sanitize_sql([sql, @user_id]) + end + end +end diff --git a/app/models/registry/status_reporter.rb b/app/models/registry/status_reporter.rb index f47f0656c..e6a2960f5 100644 --- a/app/models/registry/status_reporter.rb +++ b/app/models/registry/status_reporter.rb @@ -64,7 +64,7 @@ def request_body def registration_deadline case result.status when 'awaiting_payment' - result.invoice.due_date.end_of_day + result.invoice&.due_date&.end_of_day when 'payment_received' result.registration_due_date.end_of_day end diff --git a/app/models/wishlist_item.rb b/app/models/wishlist_item.rb index 245e96e0d..113449b08 100644 --- a/app/models/wishlist_item.rb +++ b/app/models/wishlist_item.rb @@ -76,7 +76,7 @@ def valid_domain_extension domain_extension = Setting.find_by(code: 'wishlist_supported_domain_extensions').retrieve return if domain_extension.empty? || domain_extension.include?(domain_name.split('.', 2).last) - errors.add(:domain_name, :invalid) if errors[:domain_name].blank? + errors.add(:domain_name, I18n.t('wishlist_items.invalid_domain_zone')) if errors[:domain_name].blank? end def restrictions_for_active_auctions diff --git a/app/notifications/offer_notification.rb b/app/notifications/offer_notification.rb index 77224f0d1..6d1022c06 100644 --- a/app/notifications/offer_notification.rb +++ b/app/notifications/offer_notification.rb @@ -12,12 +12,12 @@ class OfferNotification < Noticed::Base def webpush_format { title: I18n.t('.webpush_title_outbidded'), - body: I18n.t('.participant_outbid_broadcast', name: params[:offer].auction.domain_name), + body: I18n.t('.participant_outbid_broadcast', name: params[:offer]&.auction&.domain_name), icon: 'https://example.com/icon.png' } end def message - I18n.t('.participant_outbid_broadcast', name: params[:offer].auction.domain_name) + I18n.t('.participant_outbid_broadcast', name: params[:offer]&.auction&.domain_name) end end diff --git a/app/packs/entrypoints/controllers/check_controller.js b/app/packs/entrypoints/controllers/check_controller.js index d7007e63c..604c46871 100644 --- a/app/packs/entrypoints/controllers/check_controller.js +++ b/app/packs/entrypoints/controllers/check_controller.js @@ -2,11 +2,16 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { - initialize() { + connect() { const checker = document.getElementById('check_all'); + + console.log('check_controller.js'); + checker.addEventListener('click', (source) => { var checkboxes = document.querySelectorAll('[id^="auction_elements_auction_ids"]'); + console.log(checkboxes); + for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = !checkboxes[i].checked; } diff --git a/app/packs/entrypoints/controllers/checker_controller.js b/app/packs/entrypoints/controllers/checker_controller.js deleted file mode 100644 index 8e96e7f70..000000000 --- a/app/packs/entrypoints/controllers/checker_controller.js +++ /dev/null @@ -1,23 +0,0 @@ -// app/javascript/controllers/debounce_controller.js -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - static targets = ["bulkactionform"] - - connect() { - } - - collect_ids() { - const r = /\d+/; - const hiddenField = document.querySelector('#auction_elements_elements_id'); - const checkboxes = document.querySelectorAll(`[id^="auction_elements_auction_ids_"]:checked`); - - hiddenField.value = ''; - - checkboxes.forEach((el) => { - const element_id = el.id; - - hiddenField.value += ` ${element_id.match(r)}` - }); - } -} diff --git a/app/packs/entrypoints/controllers/countdown_controller.js b/app/packs/entrypoints/controllers/countdown_controller.js index db7b96810..c1253664b 100644 --- a/app/packs/entrypoints/controllers/countdown_controller.js +++ b/app/packs/entrypoints/controllers/countdown_controller.js @@ -11,8 +11,6 @@ export default class extends Controller { if (this.dateValue) { this.endTime = new Date(this.dateValue).getTime(); - // console.log(this.dateValue); - this.update(); this.timer = setInterval(() => { this.update(); diff --git a/app/packs/entrypoints/controllers/push_notification_controller.js b/app/packs/entrypoints/controllers/push_notification_controller.js index 4a6e08fe7..679fc1432 100644 --- a/app/packs/entrypoints/controllers/push_notification_controller.js +++ b/app/packs/entrypoints/controllers/push_notification_controller.js @@ -42,8 +42,6 @@ export default class extends Controller { setupPushNotifications() { const applicationServerKey = this.urlBase64ToUint8Array(this.vapidPublicValue); - console.log(this.vapidPublicValue); - navigator.serviceWorker.register("/service-worker.js", {scope: "./" }).then((registration) => { console.log('Service Worker registered successfully:', registration); diff --git a/app/packs/entrypoints/stylesheets/application.scss b/app/packs/entrypoints/stylesheets/application.scss index da3612d63..4e3439d34 100644 --- a/app/packs/entrypoints/stylesheets/application.scss +++ b/app/packs/entrypoints/stylesheets/application.scss @@ -78,16 +78,6 @@ color: #a1a4aa; } -.pagy-nav .page.prev, -.pagy-nav .page.prev a { - /* @apply rounded-l-lg; */ -} - -.pagy-nav .page.next, -.pagy-nav .page.next a { - /* @apply rounded-r-lg; */ -} - .pagy-nav .page.gap { margin-left: 0.25rem; margin-right: 0.25rem; diff --git a/app/presenters/english_bids_presenter.rb b/app/presenters/english_bids_presenter.rb index d1aa18b0b..30338755b 100644 --- a/app/presenters/english_bids_presenter.rb +++ b/app/presenters/english_bids_presenter.rb @@ -14,7 +14,7 @@ def last_actual_offer def maximum_bids return 'Bad auction type' if auction.platform == 'blind' - return 0.0 if auction.offers.empty? + return Money.new(0, Setting.find_by(code: 'auction_currency').retrieve) if auction.offers.empty? Money.new(last_actual_offer.cents, Setting.find_by(code: 'auction_currency').retrieve) end diff --git a/app/services/admin_bulk_action_service.rb b/app/services/admin_bulk_action_service.rb index 5df64eee3..1a3bff504 100644 --- a/app/services/admin_bulk_action_service.rb +++ b/app/services/admin_bulk_action_service.rb @@ -6,7 +6,8 @@ def initialize(auction_elements:) end def self.apply_for_english_auction(auction_elements:) - admin_handler = new(auction_elements: auction_elements) + admin_handler = new(auction_elements:) + admin_handler.apply_english_auction end @@ -30,9 +31,10 @@ def apply_english_auction wishlists = WishlistItem.where(domain_name: auction.domain_name) wishlists.each do |wishlist| Autobider.create( - user_id: wishlist.user_id, - domain_name: wishlist.domain_name, - cents: wishlist.cents + user_id: wishlist.user_id, + domain_name: wishlist.domain_name, + cents: wishlist.cents, + enable: true ) end diff --git a/app/services/autobider_service.rb b/app/services/autobider_service.rb index 6c0f3bc28..862c6bf87 100644 --- a/app/services/autobider_service.rb +++ b/app/services/autobider_service.rb @@ -1,3 +1,9 @@ +# rubocop:disable Metrics/ClassLength +# rubocop:disable Metrics/MethodLength +# rubocop:disable Metrics/AbcSize +# rubocop:disable Metrics/PerceivedComplexity +# rubocop:disable Metrics/CyclomaticComplexity + class AutobiderService attr_reader :auction @@ -16,9 +22,6 @@ def self.autobid(auction) return if filtered_autobiders.empty? - # filtered_ids = filtered_autobiders.group_by(&:cents).map { |cents, autobidders| autobidders.min_by(&:created_at) }.pluck(:id) - # filtered_autobiders = Autobider.where(id: filtered_ids) - if filtered_autobiders.size == 1 bider.autobid_for_single_pericipant(autobider: filtered_autobiders.first) elsif filtered_autobiders.size > 1 @@ -29,7 +32,7 @@ def self.autobid(auction) def filter_autobider_for_banned_users(autobiders:) collection_ids = autobiders.select { |autobider| restrict_for_banned_user(autobider: autobider) }.pluck(:id) - Autobider.where(id: collection_ids) + Autobider.where(id: collection_ids, enable: true) end def reject_autobiders_which_bid_equal_to_auction_current_price(filtered_autobiders:, auction:) @@ -84,6 +87,8 @@ def outbid_if_exists(autobider:, auction:) if auction.highest_price.cents < autobider.cents skip_validation = min_bid_step_in_cents > autobider.cents ? true : false + skip_validation = autobider.updated_at < auction.currently_winning_offer.updated_at if skip_validation + min_bid_step_in_cents = min_bid_step_in_cents > autobider.cents ? autobider.cents : min_bid_step_in_cents create_or_update_offer(owner: autobider.user, @@ -111,30 +116,72 @@ def outbid_if_exists(autobider:, auction:) end def autobid_for_multiple_users(autobiders:, auction:) - highest_price = autobiders.order(cents: :asc).last(2).first.cents - highest_autobider = autobiders.where('cents >= ?', highest_price).reorder(created_at: :asc).first + smaller_price = autobiders.order(cents: :asc).last(2).first.cents + two_highest_autobiders = autobiders.where('cents >= ?', smaller_price).order(cents: :asc).last(2) - owner = highest_autobider.user + smaller_bidder = two_highest_autobiders.first + higher_bidder = two_highest_autobiders.last - # last_created_autobider = autobiders.order(:created_at).last - # owner = last_created_autobider.user if last_created_autobider.cents == highest_price + if smaller_bidder.cents == higher_bidder.cents + autobiders_with_same_price = autobiders.where(cents: smaller_bidder.cents) + whois_daddy = autobiders_with_same_price.min_by(&:created_at) - # offer = auction.currently_winning_offer - # return if offer.present? && offer.cents >= highest_price + min_bid_step_in_cents = transform_money_to_cents(auction.min_bids_step) + skip_validation = min_bid_step_in_cents > whois_daddy.cents ? true : false - offer = create_or_update_offer(owner: owner, auction: auction, price: highest_price) + offer = create_or_update_offer(owner: whois_daddy.user, auction: auction, price: whois_daddy.cents, skip_validation: skip_validation) + money = Money.new(whois_daddy.cents).to_f + auction.update_minimum_bid_step(money) + auction.reload - money = Money.new(highest_price).to_f - auction.update_minimum_bid_step(money) - auction.reload + auction.update_ends_at(offer) + else - higher_autobider = autobiders.order('cents DESC').first + offer = auction.currently_winning_offer - update_bid_if_somebody_has_higher(highest_autobider: higher_autobider, auction: auction, highest_price: highest_price) - auction.update_ends_at(offer) + return if offer.present? && offer.cents >= higher_bidder.cents + + if offer.present? && offer.cents >= smaller_bidder.cents + min_bid_step_in_cents = transform_money_to_cents(auction.min_bids_step) + skip_validation = min_bid_step_in_cents > higher_bidder.cents ? true : false + + if skip_validation + offer = create_or_update_offer(owner: highest_autobider_owner, + auction: auction, + price: higher_bidder.cents, + skip_validation: skip_validation) + + money = Money.new(higher_bidder.cents).to_f + auction.update_minimum_bid_step(money) + auction.reload + + auction.update_ends_at(offer) + else + offer = create_or_update_offer(owner: highest_autobider_owner, + auction: auction, + price: min_bid_step_in_cents) + + money = Money.new(min_bid_step_in_cents).to_f + auction.update_minimum_bid_step(money) + auction.reload + + auction.update_ends_at(offer) + end + else + offer = create_or_update_offer(owner: smaller_bidder.user, auction: auction, price: smaller_bidder.cents) + + money = Money.new(smaller_bidder.cents).to_f + auction.update_minimum_bid_step(money) + auction.reload + + auction.update_ends_at(offer) + + update_bid_if_somebody_has_higher(highest_autobider: higher_bidder, auction: auction) + end + end end - def update_bid_if_somebody_has_higher(highest_autobider:, auction:, highest_price:) + def update_bid_if_somebody_has_higher(highest_autobider: , auction:) highest_autobider_owner = highest_autobider.user offer = auction.currently_winning_offer last_offer_owner = offer.user diff --git a/app/services/eis_billing/oneoff_service.rb b/app/services/eis_billing/oneoff_service.rb index 5a5f9b347..aa37473de 100644 --- a/app/services/eis_billing/oneoff_service.rb +++ b/app/services/eis_billing/oneoff_service.rb @@ -3,15 +3,16 @@ class OneoffService include EisBilling::Request include EisBilling::BaseService - attr_reader :invoice_number, :customer_url + attr_reader :invoice_number, :customer_url, :amount - def initialize(invoice_number:, customer_url:) + def initialize(invoice_number:, customer_url:, amount: nil) @invoice_number = invoice_number @customer_url = customer_url + @amount = amount end - def self.call(invoice_number:, customer_url:) - new(invoice_number: invoice_number, customer_url: customer_url).call + def self.call(invoice_number:, customer_url:, amount: nil) + new(invoice_number: invoice_number, customer_url: customer_url, amount: amount).call end def call @@ -25,8 +26,11 @@ def fetch end def params - { invoice_number: invoice_number, - customer_url: customer_url } + { + invoice_number: invoice_number, + customer_url: customer_url, + amount: amount + } end def invoice_oneoff_url diff --git a/app/views/admin/auctions/_auction.html.erb b/app/views/admin/auctions/_auction.html.erb index 1fbbab818..b50e5de0e 100644 --- a/app/views/admin/auctions/_auction.html.erb +++ b/app/views/admin/auctions/_auction.html.erb @@ -1,8 +1,15 @@ -<%# name_frame_id = dom_id(auction) %> +<%# locals: (form:,) -%> + - <%= f.check_box :auction_ids, { multiple: true }, auction.id, nil %> + + <%# TODO: Refactor this nested elements and move to the components %> + <%= form.label "auction_ids_#{auction.id}", class: 'o-checkbox' do %> + <%= form.check_box :auction_ids, { multiple: true, data: { form__bundle_checkbox_target: 'checkboxes', form__check_all_target: 'checkboxes' } }, auction.id, nil %> +
+ <% end %> + <%= link_to(admin_auction_path(auction.id), target: '_top') do %> diff --git a/app/views/admin/auctions/_deposit_participant.html.erb b/app/views/admin/auctions/_deposit_participant.html.erb index 6bb1d2ec7..457a1188e 100644 --- a/app/views/admin/auctions/_deposit_participant.html.erb +++ b/app/views/admin/auctions/_deposit_participant.html.erb @@ -1,11 +1,13 @@ - + <%= user.display_name %> <%= user.email %> - <%= form.check_box :user_ids, - { multiple: true, - checked: auction.allow_to_set_bid?(user) }, - user.id, - nil %> + + <%# TODO: Refactor this nested elements and move to the components %> + + diff --git a/app/views/admin/auctions/_deposit_participants.html.erb b/app/views/admin/auctions/_deposit_participants.html.erb index ce2c44738..f514a734a 100644 --- a/app/views/admin/auctions/_deposit_participants.html.erb +++ b/app/views/admin/auctions/_deposit_participants.html.erb @@ -1,65 +1,75 @@ -
+

<%= t('auctions.show.user_list') %>

-
- <%= form_with url: admin_auction_path(@auction), - method: :get, - data: { controller: 'debounce', - debounce_target: 'form', - turbo_action: "advance", - turbo_frame: "results", - action: 'input->debounce#search' - }, class: 'ui form' do |form| %> -
- <%= form.search_field :search_string, value: params[:search_string], placeholder: 'Search by user name, email and phone number', class: 'prompt' %> -
- <%= form.hidden_field :auction_id, value: auction.id %> -
-
+ <%= form_with url: admin_auction_path(@auction), method: :get, + data: { controller: 'form--debounce', form__debounce_target: 'form', + turbo_action: "advance", turbo_frame: "results", + action: 'input->form--debounce#search' } do |form| %> +
+ <%= form.hidden_field :auction_id, value: auction.id %> + + <%= form.search_field :search_string, value: params[:search_string], placeholder: 'Input user name, email or phone number', class: 'c-table__search__input js-table-search-dt' %> +
+ + + <%# TODO: Refactor this nested elements and move to the components %> +
+
+ +
+
+ -
+ <%# TODO: Refactor this nested elements and move to the components %> +
+
+ +
- <% end %> -
+ <% end %>
<%= form_with url: apply_auction_participants_admin_auctions_path do |form| %> <%= form.hidden_field :auction_uuid, value: auction.uuid %> -
- <%= form.submit t('auctions.show.apply'), class: 'ui button primary', style: 'margin-bottom: 20px;' %> +
+
+ <%= form.submit t('auctions.show.apply'), class: 'c-btn c-btn--blue', style: 'padding: 0 30px;' %> +
+ <% header_collection = [ { column: nil, caption: t('offers.price'), options: { class: "" } }, + { column: nil, caption: 'Email', options: { class: "" } }, + { column: nil, caption: t('auctions.show.allow'), options: { class: "" } },] %> + <%= turbo_frame_tag "results" do %> - - - - - - - - - + + <%= component 'common/table', header_collection:, options: { class: 'js-table-dt' } do %> + + <%= tag.tbody id: "auctions-table-body", class: 'contents' do %> + <%- if @users.present? %> <% @users.each do |user| %> <%= render partial: 'deposit_participant', locals: { user: user, form: form, auction: auction } %> <% end %> - -
<%= t('offers.price') %>Email<%= t('auctions.show.allow') %>
- -
- <% if @pagy.pages > 1 %> - - <% end %> -
- + <%- else %> + + <%= t(:no_offers) %> + + <%- end %> + <% end %> <% end %> <% end %> -
+ <%= turbo_frame_tag "results" do %> +
+ <% if @pagy.pages > 1 %> + + <% end %> +
+ <% end %> + <%= component 'common/pagy', pagy: @pagy %> + <% end %>
+
diff --git a/app/views/admin/auctions/deprecated/_auction.html.erb b/app/views/admin/auctions/deprecated/_auction.html.erb new file mode 100644 index 000000000..1fbbab818 --- /dev/null +++ b/app/views/admin/auctions/deprecated/_auction.html.erb @@ -0,0 +1,22 @@ + +<%# name_frame_id = dom_id(auction) %> + + + <%= f.check_box :auction_ids, { multiple: true }, auction.id, nil %> + + + <%= link_to(admin_auction_path(auction.id), target: '_top') do %> + <%= domain_name_with_embedded_colors(auction.domain_name) %> + <% end %> + + <%= auction.starts_at %> + <%= auction.ends_at %> + <%= auction.highest_offer_id.present? ? auction.highest_price : t('.no_price')%> + <%= auction.number_of_offers %> + <%= auction.turns_count %> + <%= auction.starting_price %> + <%= auction.slipping_end %> + <%= auction.platform %> + <%= auction.deposit %> + <%= auction.enable_deposit %> + diff --git a/app/views/admin/auctions/_auctions.html.erb b/app/views/admin/auctions/deprecated/_auctions.html.erb similarity index 100% rename from app/views/admin/auctions/_auctions.html.erb rename to app/views/admin/auctions/deprecated/_auctions.html.erb diff --git a/app/views/admin/auctions/deprecated/_deposit_participant.html.erb b/app/views/admin/auctions/deprecated/_deposit_participant.html.erb new file mode 100644 index 000000000..6bb1d2ec7 --- /dev/null +++ b/app/views/admin/auctions/deprecated/_deposit_participant.html.erb @@ -0,0 +1,11 @@ + + <%= user.display_name %> + <%= user.email %> + + <%= form.check_box :user_ids, + { multiple: true, + checked: auction.allow_to_set_bid?(user) }, + user.id, + nil %> + + diff --git a/app/views/admin/auctions/deprecated/_deposit_participants.html.erb b/app/views/admin/auctions/deprecated/_deposit_participants.html.erb new file mode 100644 index 000000000..ce2c44738 --- /dev/null +++ b/app/views/admin/auctions/deprecated/_deposit_participants.html.erb @@ -0,0 +1,65 @@ +
+

<%= t('auctions.show.user_list') %>

+
+ <%= form_with url: admin_auction_path(@auction), + method: :get, + data: { controller: 'debounce', + debounce_target: 'form', + turbo_action: "advance", + turbo_frame: "results", + action: 'input->debounce#search' + }, class: 'ui form' do |form| %> +
+ <%= form.search_field :search_string, value: params[:search_string], placeholder: 'Search by user name, email and phone number', class: 'prompt' %> +
+ <%= form.hidden_field :auction_id, value: auction.id %> +
+
+ <%= form.check_box :with_deposit_participants, { data: { name: 'first_checkbox' } }, 1, nil %> + +
+ +
+ <%= form.check_box :without_deposit_participants, { data: { name: 'second_checkbox' } }, 1, nil %> + +
+
+ <% end %> +
+ +
+ <%= form_with url: apply_auction_participants_admin_auctions_path do |form| %> + <%= form.hidden_field :auction_uuid, value: auction.uuid %> +
+ <%= form.submit t('auctions.show.apply'), class: 'ui button primary', style: 'margin-bottom: 20px;' %> +
+ + <%= turbo_frame_tag "results" do %> + + + + + + + + + + <% @users.each do |user| %> + <%= render partial: 'deposit_participant', locals: { user: user, form: form, auction: auction } %> + <% end %> + +
<%= t('offers.price') %>Email<%= t('auctions.show.allow') %>
+ +
+ <% if @pagy.pages > 1 %> + + <% end %> +
+ + <% end %> + <% end %> + +
+
diff --git a/app/views/admin/auctions/_form.html.erb b/app/views/admin/auctions/deprecated/_form.html.erb similarity index 100% rename from app/views/admin/auctions/_form.html.erb rename to app/views/admin/auctions/deprecated/_form.html.erb diff --git a/app/views/admin/auctions/deprecated/index.html.erb b/app/views/admin/auctions/deprecated/index.html.erb new file mode 100644 index 000000000..8c3f7e963 --- /dev/null +++ b/app/views/admin/auctions/deprecated/index.html.erb @@ -0,0 +1,220 @@ +<% content_for :hero do %> +
+

<%= t('.title') %>

+
+<% end %> + +
+ +
+ +
+ +
+ +
+
+
+ <%= turbo_frame_tag "results" do %> +
+ + + + + + + + + + + + + + + + + + + + <%= render partial: 'auctions', locals: { auctions: @auctions, form: form } %> + +
+ + <%= t('auctions.domain_name') %> + <%= sort_link_to "", "domain_name" %> + + <%= t('auctions.starts_at') %> + <%= sort_link_to "", "starts_at" %> + + <%= t('auctions.ends_at') %> + <%= sort_link_to "", "ends_at" %> + + <%= t('auctions.highest_price') %> + <%= sort_link_to "", "highest_offer_cents" %> + + <%= t('auctions.offers') %> + <%= sort_link_to "", "number_of_offers" %> + + <%= t('auctions.turns_count') %> + <%= sort_link_to "", "turns_count" %> + + <%= t('auctions.starting_price') %> + <%= sort_link_to "", "starting_price" %> + + <%= t('auctions.slipping_end') %> + <%= sort_link_to "", "slipping_end" %> + + <%= t('auctions.auction_type') %> + <%= sort_link_to "", "platform" %> + + <%= t('auctions.deposit') %> + <%= sort_link_to "", "requirement_deposit_in_cents" %> + + <%= t('auctions.enable_deposit') %> + <%= sort_link_to "", "enable_deposit" %> +
+
+
+ <% if @pagy.pages > 1 %> + + <% end %> +
+ <% end %> +
+
+
+ <% end %> \ No newline at end of file diff --git a/app/views/admin/auctions/new.html.erb b/app/views/admin/auctions/deprecated/new.html.erb similarity index 100% rename from app/views/admin/auctions/new.html.erb rename to app/views/admin/auctions/deprecated/new.html.erb diff --git a/app/views/admin/auctions/deprecated/show.html.erb b/app/views/admin/auctions/deprecated/show.html.erb new file mode 100644 index 000000000..c09d5b5d2 --- /dev/null +++ b/app/views/admin/auctions/deprecated/show.html.erb @@ -0,0 +1,85 @@ +<% content_for :hero do %> +
+

<%= t('.title') %>

+
<%= @auction.domain_name %>
+
+<% end %> + +
+
+
+
+ + + + + + + + + + + + + + <%- if @auction.offer_from_user(current_user).present? %> + + + + + <% end %> + +
<%= t('auctions.domain_name') %> + <%= domain_name_with_embedded_colors(@auction.domain_name) %> +
<%= t('auctions.starts_at') %><%= I18n.l(@auction.starts_at) if @auction.starts_at.present? %> +
<%= t('auctions.ends_at') %><%= I18n.l(@auction.ends_at) if @auction.ends_at.present? %>
<%= t('auctions.your_current_price') %><%= @auction.current_price_from_user(current_user) %>
+ + +
+ <%= link_to t(:delete), admin_auction_path(@auction), + method: :delete, + data: { confirm: t(:are_you_sure) } , + class: "ui button secondary", target: '_top' %> + + <%- if @auction.result %> + <%= link_to t(:result_name), admin_result_path(@auction.result), + class: "ui button primary" %> + <% end %> + + <%= link_to t(:versions_name), admin_auction_versions_path(@auction), + class: "ui button primary" %> +
+
+
+
+

<%= t(:offers_name) %>

+ + + + + + + + + + + + <% @offers.each do |offer| %> + + + + + + + + <% end %> + +
<%= t('offers.price') %><%= t('users.display_name') %><%= t(:created_at) %><%= t(:updated_at) %><%= t(:versions_name)%>
<%= t('offers.price_in_currency', price: offer.price) %><%= offer.user&.display_name || t(:deleted_user) %><%= I18n.l(offer.created_at) %><%= I18n.l(offer.updated_at) %><%= link_to t(:versions_name), admin_offer_versions_path(offer), class: "ui button primary" %>
+
+ + <% if @auction.enable_deposit? %> + <%= render partial: 'deposit_participants', locals: { auction: @auction } %> + <% end %> + + +
diff --git a/app/views/admin/auctions/index.html.erb b/app/views/admin/auctions/index.html.erb index 8c3f7e963..5a61eabb0 100644 --- a/app/views/admin/auctions/index.html.erb +++ b/app/views/admin/auctions/index.html.erb @@ -1,220 +1,125 @@ -<% content_for :hero do %> -
-

<%= t('.title') %>

-
-<% end %> +<% content_for :title, t(".title") %> -
-