diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb307922..754d06ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ jobs: rubocop: name: Rubocop runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Install Ruby @@ -30,6 +31,7 @@ jobs: erblint: name: Erblint runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Install Ruby @@ -54,6 +56,7 @@ jobs: eslint: name: ESLint runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Install Node.js @@ -80,6 +83,7 @@ jobs: brakeman: name: Brakeman runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Install Ruby @@ -107,6 +111,7 @@ jobs: rspec: name: RSpec runs-on: ubuntu-latest + timeout-minutes: 10 services: postgres: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3250e475..8bbd5052 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,6 +8,7 @@ on: jobs: build: runs-on: ubuntu-24.04-arm + timeout-minutes: 20 steps: - name: Checkout code diff --git a/Gemfile b/Gemfile index 7820977c..d0e64233 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ gem 'rails' gem 'rails_autolink' gem 'rails-i18n' gem 'rotp' +gem 'rouge', require: false gem 'rqrcode' gem 'ruby-vips' gem 'rubyXL' diff --git a/Gemfile.lock b/Gemfile.lock index 943a7d74..b55adaa1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -453,6 +453,7 @@ GEM retriable (3.1.2) rexml (3.4.0) rotp (6.3.0) + rouge (4.5.2) rqrcode (2.2.0) chunky_png (~> 1.0) rqrcode_core (~> 1.0) @@ -632,6 +633,7 @@ DEPENDENCIES rails-i18n rails_autolink rotp + rouge rqrcode rspec-rails rubocop diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index ef735dfd..0cf7d2ba 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -63,12 +63,7 @@ module Api submissions = create_submissions(@template, params) - WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url| - submissions.each do |submission| - SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id, - 'webhook_url_id' => webhook_url.id) - end - end + WebhookUrls.enqueue_events(submissions, 'submission.created') Submissions.send_signature_requests(submissions) @@ -96,10 +91,7 @@ module Api else @submission.update!(archived_at: Time.current) - WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url| - SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id, - 'webhook_url_id' => webhook_url.id) - end + WebhookUrls.enqueue_events(@submission, 'submission.archived') end render json: @submission.as_json(only: %i[id archived_at]) @@ -112,9 +104,9 @@ module Api submissions = submissions.where(slug: params[:slug]) if params[:slug].present? if params[:template_folder].present? - folder = TemplateFolder.accessible_by(current_ability).find_by(name: params[:template_folder]) + folder_ids = TemplateFolder.accessible_by(current_ability).where(name: params[:template_folder]).pluck(:id) - submissions = folder ? submissions.joins(:template).where(template: { folder_id: folder.id }) : submissions.none + submissions = submissions.joins(:template).where(template: { folder_id: folder_ids }) end if params.key?(:archived) diff --git a/app/controllers/api/submitter_form_views_controller.rb b/app/controllers/api/submitter_form_views_controller.rb index e8b52095..d9ac441e 100644 --- a/app/controllers/api/submitter_form_views_controller.rb +++ b/app/controllers/api/submitter_form_views_controller.rb @@ -13,10 +13,7 @@ module Api SubmissionEvents.create_with_tracking_data(@submitter, 'view_form', request) - WebhookUrls.for_account_id(@submitter.account_id, 'form.viewed').each do |webhook_url| - SendFormViewedWebhookRequestJob.perform_async('submitter_id' => @submitter.id, - 'webhook_url_id' => webhook_url.id) - end + WebhookUrls.enqueue_events(@submitter, 'form.viewed') render json: {} end diff --git a/app/controllers/api/templates_clone_controller.rb b/app/controllers/api/templates_clone_controller.rb index 2619a243..9835850a 100644 --- a/app/controllers/api/templates_clone_controller.rb +++ b/app/controllers/api/templates_clone_controller.rb @@ -28,10 +28,7 @@ module Api cloned_template.save! - WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url| - SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id, - 'webhook_url_id' => webhook_url.id) - end + WebhookUrls.enqueue_events(cloned_template, 'template.created') SearchEntries.enqueue_reindex(cloned_template) diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index 2af022e7..a45f5c6c 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -67,10 +67,7 @@ module Api SearchEntries.enqueue_reindex(@template) - WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url| - SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id, - 'webhook_url_id' => webhook_url.id) - end + WebhookUrls.enqueue_events(@template, 'template.updated') render json: @template.as_json(only: %i[id updated_at]) end @@ -95,9 +92,9 @@ module Api templates = templates.where(slug: params[:slug]) if params[:slug].present? if params[:folder].present? - folder = TemplateFolder.accessible_by(current_ability).find_by(name: params[:folder]) + folder_ids = TemplateFolder.accessible_by(current_ability).where(name: params[:folder]).pluck(:id) - templates = folder ? templates.where(folder:) : templates.none + templates = templates.where(folder_id: folder_ids) end templates diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index 5fd663b0..1cecfba6 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -51,7 +51,7 @@ class StartFormController < ApplicationController if @submitter.errors.blank? && @submitter.save if is_new_record - enqueue_submission_create_webhooks(@submitter) + WebhookUrls.enqueue_events(@submitter.submission, 'submission.created') SearchEntries.enqueue_reindex(@submitter) @@ -107,13 +107,6 @@ class StartFormController < ApplicationController redirect_to start_form_path(@template.slug) end - def enqueue_submission_create_webhooks(submitter) - WebhookUrls.for_account_id(submitter.account_id, 'submission.created').each do |webhook_url| - SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id, - 'webhook_url_id' => webhook_url.id) - end - end - def find_or_initialize_submitter(template, submitter_params) required_fields = template.preferences.fetch('link_form_fields', ['email']) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index c411b5df..9d4de051 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -54,7 +54,7 @@ class SubmissionsController < ApplicationController params: params.merge('send_completed_email' => true)) end - enqueue_submission_created_webhooks(@template, submissions) + WebhookUrls.enqueue_events(submissions, 'submission.created') Submissions.send_signature_requests(submissions) @@ -77,10 +77,7 @@ class SubmissionsController < ApplicationController else @submission.update!(archived_at: Time.current) - WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url| - SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id, - 'webhook_url_id' => webhook_url.id) - end + WebhookUrls.enqueue_events(@submission, 'submission.archived') I18n.t('submission_has_been_archived') end @@ -97,15 +94,6 @@ class SubmissionsController < ApplicationController template.save! end - def enqueue_submission_created_webhooks(template, submissions) - WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url| - submissions.each do |submission| - SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id, - 'webhook_url_id' => webhook_url.id) - end - end - end - def submissions_params params.permit(submission: { submitters: [%i[uuid email phone name]] }) end diff --git a/app/controllers/submissions_preview_controller.rb b/app/controllers/submissions_preview_controller.rb index d8a36f0b..40130e19 100644 --- a/app/controllers/submissions_preview_controller.rb +++ b/app/controllers/submissions_preview_controller.rb @@ -28,7 +28,7 @@ class SubmissionsPreviewController < ApplicationController raise ActionController::RoutingError, I18n.t('not_found') end - if !submission_valid_ttl?(@submission) && !signature_valid + if use_signature?(@submission) && !signature_valid Rollbar.info("TTL: #{@submission.id}") if defined?(Rollbar) return redirect_to submissions_preview_completed_path(@submission.slug) @@ -48,9 +48,15 @@ class SubmissionsPreviewController < ApplicationController private - def submission_valid_ttl?(submission) - return true if current_user && current_user.account.submissions.exists?(id: submission.id) + def use_signature?(submission) + return false if current_user && can?(:read, submission) + return true if submission.submitters.any? { |e| e.preferences['require_phone_2fa'] } + return true if submission.template&.preferences&.dig('require_phone_2fa') + !submission_valid_ttl?(submission) + end + + def submission_valid_ttl?(submission) last_submitter = submission.submitters.select(&:completed_at?).max_by(&:completed_at) last_submitter && last_submitter.completed_at > TTL.ago diff --git a/app/controllers/submit_form_decline_controller.rb b/app/controllers/submit_form_decline_controller.rb index 15572da1..918903fe 100644 --- a/app/controllers/submit_form_decline_controller.rb +++ b/app/controllers/submit_form_decline_controller.rb @@ -25,10 +25,7 @@ class SubmitFormDeclineController < ApplicationController SubmitterMailer.declined_email(submitter, user).deliver_later! end - WebhookUrls.for_account_id(submitter.account_id, 'form.declined').each do |webhook_url| - SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id, - 'webhook_url_id' => webhook_url.id) - end + WebhookUrls.enqueue_events(submitter, 'form.declined') redirect_to submit_form_path(submitter.slug) end diff --git a/app/controllers/template_folders_controller.rb b/app/controllers/template_folders_controller.rb index 25dad2c1..850a9046 100644 --- a/app/controllers/template_folders_controller.rb +++ b/app/controllers/template_folders_controller.rb @@ -3,11 +3,13 @@ class TemplateFoldersController < ApplicationController load_and_authorize_resource :template_folder + helper_method :selected_order + def show @templates = @template_folder.templates.active.accessible_by(current_ability) .preload(:author, :template_accesses) @templates = Templates.search(current_user, @templates, params[:q]) - @templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order]) + @templates = Templates::Order.call(@templates, current_user, selected_order) @pagy, @templates = pagy_auto(@templates, limit: 12) end @@ -25,6 +27,16 @@ class TemplateFoldersController < ApplicationController private + def selected_order + @selected_order ||= + if cookies.permanent[:dashboard_templates_order].blank? || + (cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless)) + 'created_at' + else + cookies.permanent[:dashboard_templates_order] + end + end + def template_folder_params params.require(:template_folder).permit(:name) end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index 40c8dbd1..fba51b90 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -74,7 +74,7 @@ class TemplatesController < ApplicationController SearchEntries.enqueue_reindex(@template) - enqueue_template_created_webhooks(@template) + WebhookUrls.enqueue_events(@template, 'template.created') maybe_redirect_to_template(@template) else @@ -91,7 +91,7 @@ class TemplatesController < ApplicationController SearchEntries.enqueue_reindex(@template) if is_name_changed - enqueue_template_updated_webhooks(@template) + WebhookUrls.enqueue_events(@template, 'template.updated') head :ok end @@ -142,20 +142,6 @@ class TemplatesController < ApplicationController end end - def enqueue_template_created_webhooks(template) - WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url| - SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id, - 'webhook_url_id' => webhook_url.id) - end - end - - def enqueue_template_updated_webhooks(template) - WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url| - SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id, - 'webhook_url_id' => webhook_url.id) - end - end - def load_base_template return if params[:base_template_id].blank? diff --git a/app/controllers/templates_dashboard_controller.rb b/app/controllers/templates_dashboard_controller.rb index 69843d15..218c89e7 100644 --- a/app/controllers/templates_dashboard_controller.rb +++ b/app/controllers/templates_dashboard_controller.rb @@ -8,12 +8,13 @@ class TemplatesDashboardController < ApplicationController TEMPLATES_PER_PAGE = 12 FOLDERS_PER_PAGE = 18 + helper_method :selected_order + def index @template_folders = @template_folders.where(id: @templates.active.select(:folder_id)) @template_folders = TemplateFolders.search(@template_folders, params[:q]) - @template_folders = sort_template_folders(@template_folders, current_user, - cookies.permanent[:dashboard_templates_order]) + @template_folders = sort_template_folders(@template_folders, current_user, selected_order) @pagy, @template_folders = pagy( @template_folders, @@ -26,7 +27,7 @@ class TemplatesDashboardController < ApplicationController else @template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME } @templates = filter_templates(@templates).preload(:author, :template_accesses) - @templates = Templates::Order.call(@templates, current_user, cookies.permanent[:dashboard_templates_order]) + @templates = Templates::Order.call(@templates, current_user, selected_order) limit = if @template_folders.size < 4 @@ -56,7 +57,7 @@ class TemplatesDashboardController < ApplicationController rel = Template.where( Template.arel_table[:id].in( rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel - .union(shared_template_ids.arel) + .union(:all, shared_template_ids.arel) ) ) else @@ -101,6 +102,16 @@ class TemplatesDashboardController < ApplicationController end end + def selected_order + @selected_order ||= + if cookies.permanent[:dashboard_templates_order].blank? || + (cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless)) + 'created_at' + else + cookies.permanent[:dashboard_templates_order] + end + end + def load_related_submissions @related_submissions = Submission.accessible_by(current_ability) .left_joins(:template) diff --git a/app/controllers/templates_uploads_controller.rb b/app/controllers/templates_uploads_controller.rb index 45403a7a..e6fb1069 100644 --- a/app/controllers/templates_uploads_controller.rb +++ b/app/controllers/templates_uploads_controller.rb @@ -23,7 +23,7 @@ class TemplatesUploadsController < ApplicationController @template.update!(schema:) - enqueue_template_created_webhooks(@template) + WebhookUrls.enqueue_events(@template, 'template.created') SearchEntries.enqueue_reindex(@template) @@ -68,11 +68,4 @@ class TemplatesUploadsController < ApplicationController { files: [file] } end - - def enqueue_template_created_webhooks(template) - WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url| - SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id, - 'webhook_url_id' => webhook_url.id) - end - end end diff --git a/app/controllers/webhook_events_controller.rb b/app/controllers/webhook_events_controller.rb new file mode 100644 index 00000000..fbb81b9a --- /dev/null +++ b/app/controllers/webhook_events_controller.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class WebhookEventsController < ApplicationController + load_and_authorize_resource :webhook_url, parent: false, id_param: :webhook_id + before_action :load_webhook_event + + def show + return unless current_ability.can?(:read, @webhook_event.record) + + @data = + case @webhook_event.event_type + when 'form.started', 'form.completed', 'form.declined', 'form.viewed' + Submitters::SerializeForWebhook.call(@webhook_event.record) + when 'submission.created', 'submission.completed', 'submission.expired' + Submissions::SerializeForApi.call(@webhook_event.record) + when 'template.created', 'template.updated' + Templates::SerializeForApi.call(@webhook_event.record) + when 'submission.archived' + @webhook_event.record.as_json(only: %i[id archived_at]) + end + end + + def resend + id_key = WebhookUrls::EVENT_TYPE_ID_KEYS.fetch(@webhook_event.event_type.split('.').first) + + last_attempt_id = @webhook_event.webhook_attempts.maximum(:id) + + WebhookUrls::EVENT_TYPE_TO_JOB_CLASS[@webhook_event.event_type].perform_async( + id_key => @webhook_event.record_id, + 'webhook_url_id' => @webhook_event.webhook_url_id, + 'event_uuid' => @webhook_event.uuid, + 'attempt' => SendWebhookRequest::MANUAL_ATTEMPT, + 'last_status' => 0 + ) + + render turbo_stream: [ + turbo_stream.after( + params[:button_id], + helpers.tag.submit_form( + helpers.button_to('', refresh_settings_webhook_event_path(@webhook_url.id, @webhook_event.uuid), + params: { last_attempt_id: }), + class: 'hidden', data: { interval: 3_000 } + ) + ) + ] + end + + def refresh + return head :ok if @webhook_event.webhook_attempts.maximum(:id) == params[:last_attempt_id].to_i + + render turbo_stream: [ + turbo_stream.replace(helpers.dom_id(@webhook_event), + partial: 'event_row', + locals: { with_status: true, webhook_url: @webhook_url, webhook_event: @webhook_event }), + turbo_stream.replace("drawer_events_#{helpers.dom_id(@webhook_event)}", + partial: 'drawer_events', + locals: { webhook_url: @webhook_url, webhook_event: @webhook_event }) + ] + end + + private + + def load_webhook_event + @webhook_event = @webhook_url.webhook_events.find_by!(uuid: params[:id]) + end +end diff --git a/app/controllers/webhook_settings_controller.rb b/app/controllers/webhook_settings_controller.rb index 5f023ad1..482116ef 100644 --- a/app/controllers/webhook_settings_controller.rb +++ b/app/controllers/webhook_settings_controller.rb @@ -8,10 +8,26 @@ class WebhookSettingsController < ApplicationController @webhook_urls = @webhook_urls.order(id: :desc) @webhook_url = @webhook_urls.first_or_initialize - render @webhook_urls.size > 1 ? 'index' : 'show' + if @webhook_urls.size > 1 + render :index + else + @webhook_events = @webhook_url.webhook_events + + @webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status]) + + @pagy, @webhook_events = pagy_countless(@webhook_events.order(id: :desc)) + + render :show + end end - def show; end + def show + @webhook_events = @webhook_url.webhook_events + + @webhook_events = @webhook_events.where(status: params[:status]) if %w[success error].include?(params[:status]) + + @pagy, @webhook_events = pagy_countless(@webhook_events.order(id: :desc)) + end def new; end @@ -37,13 +53,18 @@ class WebhookSettingsController < ApplicationController def resend submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last + authorize!(:read, submitter) + if submitter.blank? || @webhook_url.blank? return redirect_back(fallback_location: settings_webhooks_path, alert: I18n.t('unable_to_resend_webhook_request')) end - SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id, - 'webhook_url_id' => @webhook_url.id) + SendTestWebhookRequestJob.perform_async( + 'submitter_id' => submitter.id, + 'event_uuid' => SecureRandom.uuid, + 'webhook_url_id' => @webhook_url.id + ) redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent')) end diff --git a/app/javascript/elements/submit_form.js b/app/javascript/elements/submit_form.js index 4b5969e5..7a27ed5f 100644 --- a/app/javascript/elements/submit_form.js +++ b/app/javascript/elements/submit_form.js @@ -1,5 +1,17 @@ export default class extends HTMLElement { connectedCallback () { - this.querySelector('form').requestSubmit() + if (this.dataset.interval) { + this.interval = setInterval(() => { + this.querySelector('form').requestSubmit() + }, parseInt(this.dataset.interval)) + } else { + this.querySelector('form').requestSubmit() + } + } + + disconnectedCallback () { + if (this.interval) { + clearInterval(this.interval) + } } } diff --git a/app/javascript/submission_form/i18n.js b/app/javascript/submission_form/i18n.js index e47b4525..c734bd9d 100644 --- a/app/javascript/submission_form/i18n.js +++ b/app/javascript/submission_form/i18n.js @@ -1,4 +1,5 @@ const en = { + must_be_characters_length: 'Must be {number} characters length', complete_all_required_fields_to_proceed_with_identity_verification: 'Complete all required fields to proceed with identity verification.', verify_id: 'Verify ID', identity_verification: 'Identity verification', @@ -98,6 +99,7 @@ const en = { } const es = { + must_be_characters_length: 'Debe tener {number} caracteres de longitud', complete_all_required_fields_to_proceed_with_identity_verification: 'Complete todos los campos requeridos para continuar con la verificación de identidad.', verify_id: 'Verificar ID', identity_verification: 'Verificación de identidad', @@ -197,6 +199,7 @@ const es = { } const it = { + must_be_characters_length: 'Deve essere lungo {number} caratteri', complete_all_required_fields_to_proceed_with_identity_verification: "Compila tutti i campi obbligatori per procedere con la verifica dell'identità.", verify_id: 'Verifica ID', identity_verification: "Verifica dell'identità", @@ -296,6 +299,7 @@ const it = { } const de = { + must_be_characters_length: 'Muss {number} Zeichen lang sein', complete_all_required_fields_to_proceed_with_identity_verification: 'Vervollständigen Sie alle erforderlichen Felder, um mit der Identitätsverifizierung fortzufahren.', verify_id: 'ID überprüfen', identity_verification: 'Identitätsüberprüfung', @@ -395,6 +399,7 @@ const de = { } const fr = { + must_be_characters_length: 'Doit contenir {number} caractères', complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour continuer la vérification de l'identité.", verify_id: "Vérification de l'ID", identity_verification: "Vérification de l'identité", @@ -494,6 +499,7 @@ const fr = { } const pl = { + must_be_characters_length: 'Musi mieć długość {number} znaków', complete_all_required_fields_to_proceed_with_identity_verification: 'Uzupełnij wszystkie wymagane pola, aby kontynuować weryfikację tożsamości.', verify_id: 'Zweryfikuj ID', identity_verification: 'Weryfikacja tożsamości', @@ -593,6 +599,7 @@ const pl = { } const uk = { + must_be_characters_length: 'Має містити {number} символів', complete_all_required_fields_to_proceed_with_identity_verification: "Заповніть всі обов'язкові поля, щоб продовжити перевірку особи.", verify_id: 'Підтвердження ідентичності', identity_verification: 'Ідентифікація особи', @@ -692,6 +699,7 @@ const uk = { } const cs = { + must_be_characters_length: 'Musí mít délku {number} znaků', complete_all_required_fields_to_proceed_with_identity_verification: 'Vyplňte všechna povinná pole, abyste mohli pokračovat v ověření identity.', verify_id: 'Ověřit ID', identity_verification: 'Ověření identity', @@ -791,6 +799,7 @@ const cs = { } const pt = { + must_be_characters_length: 'Deve ter {number} caracteres', complete_all_required_fields_to_proceed_with_identity_verification: 'Preencha todos os campos obrigatórios para prosseguir com a verificação de identidade.', verify_id: 'Verificar ID', identity_verification: 'Verificação de identidade', @@ -890,6 +899,7 @@ const pt = { } const he = { + must_be_characters_length: 'חייב להיות באורך של {number} תווים', complete_all_required_fields_to_proceed_with_identity_verification: 'מלא את כל השדות הנדרשים כדי להמשיך עם אימות זהות.', verify_id: 'אמת מזהה', identity_verification: 'אימות זהות', @@ -989,6 +999,7 @@ const he = { } const nl = { + must_be_characters_length: 'Moet {number} tekens lang zijn', complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.', verify_id: 'Verifiëren ID', identity_verification: 'Identiteitsverificatie', @@ -1088,6 +1099,7 @@ const nl = { } const ar = { + must_be_characters_length: 'يجب أن يكون الطول {number} حرفًا', complete_all_required_fields_to_proceed_with_identity_verification: 'أكمل جميع الحقول المطلوبة للمتابعة في التحقق من الهوية.', verify_id: 'تحقق من الهوية', identity_verification: 'التحقق من الهوية', @@ -1187,6 +1199,7 @@ const ar = { } const ko = { + must_be_characters_length: '{number}자여야 합니다', complete_all_required_fields_to_proceed_with_identity_verification: '신원 확인을 진행하려면 모든 필수 필드를 작성하십시오.', verify_id: '아이디 확인', identity_verification: '신원 확인', @@ -1286,6 +1299,7 @@ const ko = { } const ja = { + must_be_characters_length: '{number}文字でなければなりません', complete_all_required_fields_to_proceed_with_identity_verification: '本人確認を進めるには、すべての必須項目を入力してください。', verify_id: '本人確認', identity_verification: '本人確認', diff --git a/app/javascript/submission_form/text_step.vue b/app/javascript/submission_form/text_step.vue index e6560c72..17b921c1 100644 --- a/app/javascript/submission_form/text_step.vue +++ b/app/javascript/submission_form/text_step.vue @@ -43,8 +43,8 @@ :placeholder="`${t('type_here_')}${field.required ? '' : ` (${t('optional')})`}`" type="text" :name="`values[${field.uuid}]`" - @invalid="field.validation?.message ? $event.target.setCustomValidity(field.validation.message) : ''" - @input="field.validation?.message ? $event.target.setCustomValidity('') : ''" + @invalid="validationMessage ? $event.target.setCustomValidity(validationMessage) : ''" + @input="validationMessage ? $event.target.setCustomValidity('') : ''" @focus="$emit('focus')" >