From 8e79fcbfb3a2b360fcadb08b11d73baf2979cd98 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Mon, 12 Feb 2024 12:54:48 +0200 Subject: [PATCH] added countdown for mobile confirmation --- Dockerfile | 21 ------ Gemfile.lock | 3 + .../send_sms_controller.rb | 32 ++++++++ .../phone_confirmations_controller.rb | 5 +- app/helpers/phone_confirmations_helper.rb | 5 ++ app/models/phone_confirmation.rb | 5 +- app/models/user.rb | 6 ++ app/packs/entrypoints/controllers/index.js | 2 + .../controllers/timeleft_controller.js | 74 +++++++++++++++++++ app/views/phone_confirmations/new.html.erb | 27 ++++++- config/customization.yml.sample | 2 + config/locales/phone_confirmations.en.yml | 5 ++ config/locales/phone_confirmations.et.yml | 5 ++ config/routes.rb | 6 +- ...9111309_add_sms_send_timestamp_to_users.rb | 5 ++ db/structure.sql | 4 +- 16 files changed, 179 insertions(+), 28 deletions(-) create mode 100644 app/controllers/phone_confirmations/send_sms_controller.rb create mode 100644 app/helpers/phone_confirmations_helper.rb create mode 100644 app/packs/entrypoints/controllers/timeleft_controller.js create mode 100644 db/migrate/20240209111309_add_sms_send_timestamp_to_users.rb diff --git a/Dockerfile b/Dockerfile index fb28fe3de..8f02cd4c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,3 @@ -# FROM internetee/ruby:3.0-buster - -# RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install wkhtmltopdf -y > /dev/null - -# RUN npm install -g yarn@latest -# WORKDIR /opt/webapps/app -# 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 - - FROM internetee/ruby:3.2.2 SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -69,15 +57,6 @@ RUN apt-get install -y --no-install-recommends > /dev/null \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# RUN curl https://chromedriver.storage.googleapis.com/2.46/chromedriver_linux64.zip -o /chromedriver_linux64.zip -# RUN apt-get update > /dev/null \ -# && apt-get install -yf --no-install-recommends > /dev/null unzip=* \ -# && apt-get clean \ -# && rm -rf /var/lib/apt/lists/* -# RUN unzip chromedriver_linux64.zip -d /usr/local/bin -# RUN rm /chromedriver_linux64.zip - -# RUN npm install --global yarn RUN npm install -g yarn@latest RUN curl https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -o /chrome.deb diff --git a/Gemfile.lock b/Gemfile.lock index 14635b3d2..67a283f73 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -229,6 +229,8 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.7.0) + nokogiri (1.15.2-arm64-darwin) + racc (~> 1.4) nokogiri (1.15.2-x86_64-linux) racc (~> 1.4) noticed (1.6.3) @@ -454,6 +456,7 @@ GEM zeitwerk (2.6.8) PLATFORMS + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/app/controllers/phone_confirmations/send_sms_controller.rb b/app/controllers/phone_confirmations/send_sms_controller.rb new file mode 100644 index 000000000..1a1a65a15 --- /dev/null +++ b/app/controllers/phone_confirmations/send_sms_controller.rb @@ -0,0 +1,32 @@ +class PhoneConfirmations::SendSmsController < ApplicationController + before_action :authenticate_user! + before_action :set_phone_confirmation + before_action :authorize_user + + include RecaptchaValidatable + recaptcha_action 'phone_confirmation' + + def create + if current_user.allow_to_send_sms_again? && recaptcha_valid + PhoneConfirmationJob.perform_now(@phone_confirmation.user.id) + + flash[:notice] = I18n.t('phone_confirmations.create.sms_sent') + else + flash[:alert] = I18n.t('phone_confirmations.create.sms_not_sent') + end + + @show_checkbox_recaptcha = true unless @success + + redirect_to new_user_phone_confirmation_path(@phone_confirmation.user.uuid), status: :see_other, turbo: false + end + + private + + def set_phone_confirmation + @phone_confirmation = PhoneConfirmation.new(current_user) + end + + def authorize_user + authorize! :manage, PhoneConfirmation + end +end diff --git a/app/controllers/phone_confirmations_controller.rb b/app/controllers/phone_confirmations_controller.rb index a25e32043..7a3051d17 100644 --- a/app/controllers/phone_confirmations_controller.rb +++ b/app/controllers/phone_confirmations_controller.rb @@ -3,13 +3,14 @@ class PhoneConfirmationsController < ApplicationController before_action :set_phone_confirmation before_action :authorize_user + include RecaptchaValidatable + recaptcha_action 'phone_confirmation' + 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) - else - PhoneConfirmationJob.perform_later(@phone_confirmation.user.id) end end diff --git a/app/helpers/phone_confirmations_helper.rb b/app/helpers/phone_confirmations_helper.rb new file mode 100644 index 000000000..3c38e0064 --- /dev/null +++ b/app/helpers/phone_confirmations_helper.rb @@ -0,0 +1,5 @@ +module PhoneConfirmationsHelper + def render_send_in_button? + Setting.find_by(code: 'require_phone_confirmation').retrieve && current_user.mobile_phone_confirmed_at.nil? && current_user.mobile_phone.present? + end +end diff --git a/app/models/phone_confirmation.rb b/app/models/phone_confirmation.rb index ac1ff45ff..1fb4cfdc7 100644 --- a/app/models/phone_confirmation.rb +++ b/app/models/phone_confirmation.rb @@ -3,17 +3,20 @@ class PhoneConfirmation attr_reader :user + TIME_LIMIT = 1.minutes + def initialize(user) @user = user end def generate_and_send_code return unless user.valid? + return if user.mobile_phone_confirmed_sms_send_at.present? && user.mobile_phone_confirmed_sms_send_at > TIME_LIMIT.ago number = SecureRandom.random_number(10_000) padded_number = format('%04d', number) - user.update!(mobile_phone_confirmation_code: padded_number) + user.update!(mobile_phone_confirmation_code: padded_number, mobile_phone_confirmed_sms_send_at: Time.zone.now) message_sender = Messente::Omnimessage.new( user.mobile_phone, I18n.t('phone_confirmations.instructions', code: padded_number) diff --git a/app/models/user.rb b/app/models/user.rb index db37138ea..df47808f4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -225,4 +225,10 @@ def participated_in_english_auction?(auction) auction.offers.any? { |offer| offer.user == self } || auction.domain_participate_auctions.any? { |p| p.user == self } end + + def allow_to_send_sms_again? + return true if mobile_phone_confirmed_sms_send_at.blank? + + mobile_phone_confirmed_sms_send_at < PhoneConfirmation::TIME_LIMIT.ago + end end diff --git a/app/packs/entrypoints/controllers/index.js b/app/packs/entrypoints/controllers/index.js index f577513bb..18c3c51d2 100644 --- a/app/packs/entrypoints/controllers/index.js +++ b/app/packs/entrypoints/controllers/index.js @@ -43,3 +43,5 @@ import CookieController from "./cookie_controller" application.register("cookie", CookieController) import AuctionTimezoneController from "./auction_timezone_controller" application.register("auction-timezone", AuctionTimezoneController) +import TimeleftController from "./timeleft_controller" +application.register("timeleft", TimeleftController) diff --git a/app/packs/entrypoints/controllers/timeleft_controller.js b/app/packs/entrypoints/controllers/timeleft_controller.js new file mode 100644 index 000000000..f36fe9ccf --- /dev/null +++ b/app/packs/entrypoints/controllers/timeleft_controller.js @@ -0,0 +1,74 @@ +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); + + this.buttonTarget.innerHTML = `${this.messageTimerValue} ${minutes}:${seconds}` + } + } + + 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/views/phone_confirmations/new.html.erb b/app/views/phone_confirmations/new.html.erb index 57fc2dc1e..86314323b 100644 --- a/app/views/phone_confirmations/new.html.erb +++ b/app/views/phone_confirmations/new.html.erb @@ -4,11 +4,34 @@ <% end %> -
+

<%= t('.subheader' , number: @phone_confirmation.user.mobile_phone )%>

<%= t('.instructions', number: @phone_confirmation.user.mobile_phone )%>

- <%= render 'form', phone_confirmation: @phone_confirmation, url: user_phone_confirmations_path %> + <%= render 'form', phone_confirmation: @phone_confirmation, url: user_phone_confirmations_path %>
+ + <% if current_user.allow_to_send_sms_again? %> + <%= button_to I18n.t('phone_confirmations.new.send_code'), send_sms_user_phone_confirmations_path, class: "ui button primary", data: { timeleft_target: 'button' } %> + <% else %> + <%= button_to I18n.t('phone_confirmations.new.send_again'), send_sms_user_phone_confirmations_path, class: "ui button primary", data: { timeleft_target: 'button' }, disabled: true %> + <% end if render_send_in_button? %> + +
+ <% if @captcha_required %> + <% if @show_checkbox_recaptcha %> +
+
+ <%= recaptcha_tags id: 'english_offer', site_key: @recaptcha2_site_key %> +
+ <% else %> + <%= recaptcha_v3(action: 'english_offer', site_key: @recaptcha3_site_key, turbolinks: true) %> + <% end %> + <% end %> +
+
diff --git a/config/customization.yml.sample b/config/customization.yml.sample index ec6071cb5..7ca9e653b 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -18,6 +18,8 @@ default: &default vapid_public: "BGgFbsalgk-emt7f0f279JsxF15NOfEJKQ-7w04FmXseeWt4pRvCaD7EMl6PqS-BHrwO3QWsPUOGkAjNWNBOVLY=" vapid_private: "2xl1lvaQARjFHRCKrPo2B-MbTAc1IZ3UrfugDh6cJiE=" + mobile_sms_sent_time_limit_in_minutes: 5 + mailer: # Host to which links from emails should redirect to host: 'https://auction.example.test' diff --git a/config/locales/phone_confirmations.en.yml b/config/locales/phone_confirmations.en.yml index d28f3b919..540987bce 100644 --- a/config/locales/phone_confirmations.en.yml +++ b/config/locales/phone_confirmations.en.yml @@ -11,6 +11,11 @@ en: Enter it below and click submit to confirm your mobile phone. It can take up to a few minutes for the code to be delivered. already_confirmed: "Your phone number has already been confirmed." + send_again: Send the code via + send_code: Send the code create: confirmed: "Your phone number has been confirmed. You can now submit offers." + + sms_sent: The code was sent to your phone number. + sms_not_sent: The code was not sent, please try again. diff --git a/config/locales/phone_confirmations.et.yml b/config/locales/phone_confirmations.et.yml index 92cfde641..5ba3398e2 100644 --- a/config/locales/phone_confirmations.et.yml +++ b/config/locales/phone_confirmations.et.yml @@ -11,6 +11,11 @@ et: Sisesta kood siia ja klõpsa „esita“ mobiilinumbri kinnitamiseks. Koodi saatmine võib võtta mitu minutit already_confirmed: "Sinu telefoninumber on juba kinnitatud." + send_again: Saada kood läbi. + send_code: Saada kood. create: confirmed: "Sinu telefoninumber on kinnitatud. Võid esitada pakkumusi." + + sms_sent: Kood saadeti teie telefoninumbrile. + sms_not_sent: Koodi ei saadetud, palun proovige uuesti. diff --git a/config/routes.rb b/config/routes.rb index a49c7570a..addea6108 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -125,7 +125,11 @@ resources :results, only: :show, param: :uuid resources :users, param: :uuid do - resources :phone_confirmations, only: %i[new create] + resources :phone_confirmations, only: %i[new create] do + scope module: :phone_confirmations do + post :send_sms, to: 'send_sms#create', on: :collection + end + end end resources :wishlist_items, param: :uuid, only: %i[index edit create destroy update] diff --git a/db/migrate/20240209111309_add_sms_send_timestamp_to_users.rb b/db/migrate/20240209111309_add_sms_send_timestamp_to_users.rb new file mode 100644 index 000000000..664b5e236 --- /dev/null +++ b/db/migrate/20240209111309_add_sms_send_timestamp_to_users.rb @@ -0,0 +1,5 @@ +class AddSmsSendTimestampToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :mobile_phone_confirmed_sms_send_at, :datetime + end +end diff --git a/db/structure.sql b/db/structure.sql index 6c85500f3..346b5e13f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1632,6 +1632,7 @@ CREATE TABLE public.users ( daily_summary boolean DEFAULT false NOT NULL, jti character varying, reference_no character varying, + mobile_phone_confirmed_sms_send_at timestamp(6) without time zone, CONSTRAINT users_roles_are_known CHECK ((roles <@ ARRAY['participant'::character varying, 'administrator'::character varying])) ); @@ -3192,6 +3193,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20231031122216'), ('20231116093310'), ('20231222074427'), -('20231222085647'); +('20231222085647'), +('20240209111309');