diff --git a/.gitignore b/.gitignore index 5f01e718..63ca72d8 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,8 @@ yarn-debug.log* /docuseal /ee dump.rdb +/custom/ +/docuseal.iml +/.idea/misc.xml +/.idea/modules.xml +/.idea/vcs.xml diff --git a/app/controllers/submit_form_take_photo_controller.rb b/app/controllers/submit_form_take_photo_controller.rb new file mode 100644 index 00000000..0411f165 --- /dev/null +++ b/app/controllers/submit_form_take_photo_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class SubmitFormTakePhotoController < ApplicationController + layout false + + around_action :with_browser_locale, only: %i[show] + skip_before_action :authenticate_user! + skip_authorization_check + + def show + @submitter = Submitter.find_by!(slug: params[:slug]) + + return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at? + + if @submitter.submission.template.archived_at? || @submitter.submission.archived_at? + return redirect_to submit_form_path(@submitter.slug) + end + + render :show + end +end diff --git a/app/javascript/photo.js b/app/javascript/photo.js new file mode 100644 index 00000000..cef7ab8b --- /dev/null +++ b/app/javascript/photo.js @@ -0,0 +1,142 @@ +window.customElements.define('file-photo', class extends HTMLElement { + connectedCallback () { + + this.clearButton.addEventListener('click', (e) => { + e.preventDefault() + this.valueInput.value = null + this.inputFile.click() + }) + + this.inputFile.addEventListener('change', (e) => { + e.preventDefault() + this.updateSubmitButtonVisibility() + this.uploadFiles(this.inputFile.files) + }) + + this.form.addEventListener('submit', (e) => { + e.preventDefault(); + this.submitButton.disabled = true + fetch(this.form.action, { + method: 'PUT', + body: new FormData(this.form) + }).then((response) => { + this.form.classList.add('hidden') + this.success.classList.remove('hidden') + return response + }).finally(() => { + this.submitButton.disabled = false + }) + }) + + } + + toggleLoading = (e) => { + this.updateSubmitButtonVisibility() + if (e && e.target && !e.target.contains(this)) { + return + } + this.loading.classList.toggle('hidden') + this.icon.classList.toggle('hidden') + this.classList.toggle('opacity-50') + } + + async uploadFiles (files) { + this.toggleLoading() + return await Promise.all( + Array.from(files).map(async (file) => { + const formData = new FormData() + if (file.type === 'image/bmp') { + file = await this.convertBmpToPng(file) + } + + formData.append('file', file) + formData.append('submitter_slug', this.dataset.slug) + formData.append('name', 'attachments') + + return fetch('/api/attachments', { + method: 'POST', + body: formData + }).then(resp => resp.json()).then((data) => { + return data + }) + })).then((result) => { + this.valueInput.value = result[0].uuid + return result[0] + }).finally(() => { + this.toggleLoading() + }) + } + + convertBmpToPng (bmpFile) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + + reader.onload = function (event) { + const img = new Image() + + img.onload = function () { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + canvas.width = img.width + canvas.height = img.height + ctx.drawImage(img, 0, 0) + canvas.toBlob(function (blob) { + const newFile = new File([blob], bmpFile.name.replace(/\.\w+$/, '.png'), { type: 'image/png' }) + resolve(newFile) + }, 'image/png') + } + + img.src = event.target.result + } + reader.onerror = reject + reader.readAsDataURL(bmpFile) + }) + } + + updateSubmitButtonVisibility () { + if (!this.valueInput.value) { + this.submitButton.style.display = 'none' + this.placeholderButton.style.display = 'block' + } else { + this.submitButton.style.display = 'block' + this.placeholderButton.style.display = 'none' + } + } + + get submitButton () { + return this.querySelector('button[type="submit"]') + } + + get clearButton () { + return this.querySelector('button[aria-label="Clear"]') + } + + get placeholderButton () { + return this.querySelector('button[disabled]') + } + + get valueInput () { + return this.querySelector('input[name^="values"]') + } + + get inputFile () { + return this.querySelector('input[id="file"]') + } + + get icon () { + return this.querySelector('#file-photo-icon') + } + + get loading () { + return this.querySelector('#file-photo-loading') + } + + get form () { + return this.querySelector('form') + } + + get success () { + return this.querySelector('#success') + } +}) diff --git a/app/javascript/submission_form/dropzone.vue b/app/javascript/submission_form/dropzone.vue index 6bb256ed..981231df 100644 --- a/app/javascript/submission_form/dropzone.vue +++ b/app/javascript/submission_form/dropzone.vue @@ -1,7 +1,8 @@ diff --git a/app/javascript/template_builder/field_settings.vue b/app/javascript/template_builder/field_settings.vue index df673356..c56ea58b 100644 --- a/app/javascript/template_builder/field_settings.vue +++ b/app/javascript/template_builder/field_settings.vue @@ -284,6 +284,20 @@ {{ t('with_logo') }} +
  • + +
  • + \ No newline at end of file diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js index d38c6385..390ba550 100644 --- a/app/javascript/template_builder/i18n.js +++ b/app/javascript/template_builder/i18n.js @@ -158,7 +158,8 @@ const en = { some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.', learn_more: 'Learn more', and: 'and', - or: 'or' + or: 'or', + only_with_camera: 'Only with camera' } const es = { @@ -321,7 +322,8 @@ const es = { some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.', learn_more: 'Aprende más', and: 'y', - or: 'o' + or: 'o', + only_with_camera: 'Sólo con cámara' } const it = { @@ -484,7 +486,8 @@ const it = { some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.', learn_more: 'Scopri di più', and: 'e', - or: 'o' + or: 'o', + only_with_camera: 'Solo con fotocamera' } const pt = { @@ -647,7 +650,8 @@ const pt = { some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.', learn_more: 'Saiba mais', and: 'e', - or: 'ou' + or: 'ou', + only_with_camera: 'Somente com câmera' } const fr = { @@ -810,7 +814,8 @@ const fr = { some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.', learn_more: 'En savoir plus', and: 'et', - or: 'ou' + or: 'ou', + only_with_camera: 'Uniquement avec la caméra' } const de = { @@ -973,7 +978,8 @@ const de = { some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.', learn_more: 'Erfahren Sie mehr', and: 'und', - or: 'oder' + or: 'oder', + only_with_camera: 'Nur mit Kamera' } export { en, es, it, pt, fr, de } diff --git a/app/views/icons/_camera.html.erb b/app/views/icons/_camera.html.erb new file mode 100644 index 00000000..403797eb --- /dev/null +++ b/app/views/icons/_camera.html.erb @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/views/submit_form_take_photo/show.html.erb b/app/views/submit_form_take_photo/show.html.erb new file mode 100644 index 00000000..9e1caba0 --- /dev/null +++ b/app/views/submit_form_take_photo/show.html.erb @@ -0,0 +1,67 @@ + + + + <%= render 'layouts/head_tags' %> + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <% if ENV['ROLLBAR_CLIENT_TOKEN'] %> + + <%= javascript_pack_tag 'rollbar', 'photo', defer: true %> + <% else %> + <%= javascript_pack_tag 'photo', defer: true %> + <% end %> + <%= stylesheet_pack_tag 'form', media: 'all' %> + <%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %> + + + <% field = (@submitter.submission.template_fields || @submitter.template.fields).find { |f| f['type'] == 'image' && f['uuid'].starts_with?(params[:f]) } %> + + <%= form_for '', url: submit_form_path(params[:slug]), html: { style: 'max-width: 900px; width: 100%; margin-bottom: 120px' }, method: :put do |f| %> + + <% if field['description'] %> +
    + <%= field['description'] %> +
    + <% end %> + +
    + + <%= f.button button_title(title: t('submit')), class: 'base-button w-full', style: 'display: none' %> +
    + <% end %> + +
    + + diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 2bd3c902..da1f3c7a 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -394,6 +394,7 @@ en: &en draw_signature: Draw Signature clear: Clear signature_uploaded: Signature Uploaded + photo_uploaded: Photo Uploaded submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'Submission deletion is irreversible and will permanently remove all associated signed documents with it. Are you sure?' return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Return back to your desktop device to complete the form or continue on mobile' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: Template deletion is irreversible and will permanently remove all associated signed documents with it. Are you sure? @@ -1121,6 +1122,7 @@ es: &es draw_signature: Dibujar firma clear: Limpiar signature_uploaded: Firma subida + photo_uploaded: Foto subida submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'La eliminación del envío es irreversible y eliminará permanentemente todos los documentos firmados asociados. ¿Estás seguro?' return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Vuelve a tu dispositivo de escritorio para completar el formulario o continúa en móvil' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: La eliminación de la plantilla es irreversible y eliminará permanentemente todos los documentos firmados asociados. ¿Estás seguro? @@ -1847,6 +1849,7 @@ it: &it draw_signature: Disegna firma clear: Cancella signature_uploaded: Firma caricata + photo_uploaded: Foto caricata submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: "La cancellazione dell'invio è irreversibile e rimuoverà permanentemente tutti i documenti firmati associati. Sei sicuro?" return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Torna al tuo dispositivo desktop per completare il modulo o continua su mobile' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: La cancellazione del modello è irreversibile e rimuoverà permanentemente tutti i documenti firmati associati. Sei sicuro? @@ -2575,6 +2578,7 @@ fr: &fr draw_signature: Dessiner la signature clear: Effacer signature_uploaded: Signature téléchargée + photo_uploaded: Photo téléchargée submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'La suppression de la soumission est irréversible et supprimera définitivement tous les documents signés associés. Êtes-vous sûr?' return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Retournez à votre appareil de bureau pour compléter le formulaire ou continuez sur mobile' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: La suppression du modèle est irréversible et supprimera définitivement tous les documents signés associés. Êtes-vous sûr? @@ -3302,6 +3306,7 @@ pt: &pt draw_signature: Desenhar assinatura clear: Limpar signature_uploaded: Assinatura enviada + photo_uploaded: Foto enviada submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'A exclusão da submissão é irreversível e removerá permanentemente todos os documentos assinados associados a ela. Tem certeza?' return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Volte para seu dispositivo desktop para concluir o formulário ou continue no celular' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: A exclusão do modelo é irreversível e removerá permanentemente todos os documentos assinados associados a ele. Tem certeza? @@ -4029,6 +4034,7 @@ de: &de draw_signature: Unterschrift zeichnen clear: Löschen signature_uploaded: Unterschrift hochgeladen + photo_uploaded: Foto hochgeladen submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: 'Das Löschen der Einreichung ist unwiderruflich und wird alle zugehörigen signierten Dokumente dauerhaft entfernen. Bist du sicher?' return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html: 'Gehe zurück zu deinem Desktop-Gerät, um das Formular abzuschließen, oder fahre auf dem Handy fort' template_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_: Die Vorlagenlöschung ist unwiderruflich und wird alle zugehörigen signierten Dokumente dauerhaft entfernen. Bist du sicher? diff --git a/config/routes.rb b/config/routes.rb index f5672d4f..57e02300 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -144,6 +144,8 @@ Rails.application.routes.draw do resources :submit_form_draw_signature, only: %i[show], path: 'p', param: 'slug' + resources :submit_form_take_photo, only: %i[show], path: 't', param: 'slug' + resources :submissions_preview, only: %i[show], path: 'e', param: 'slug' do get :completed end