pull/402/merge
Vincent Barrier 7 months ago committed by GitHub
commit d1462f87f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

5
.gitignore vendored

@ -37,3 +37,8 @@ yarn-debug.log*
/docuseal
/ee
dump.rdb
/custom/
/docuseal.iml
/.idea/misc.xml
/.idea/modules.xml
/.idea/vcs.xml

@ -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

@ -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')
}
})

@ -1,7 +1,8 @@
<template>
<div
id="dropzone"
class="flex h-32 w-full"
class="flex w-full"
:class="{'h-20': onlyWithCamera, 'h-32': !onlyWithCamera}"
@dragover.prevent
@drop.prevent="onDropFiles"
>
@ -18,6 +19,11 @@
:width="30"
:height="30"
/>
<IconCamera
v-else-if="onlyWithCamera"
:width="30"
:height="30"
/>
<IconCloudUpload
v-else
:width="30"
@ -29,7 +35,7 @@
>
{{ message }}
</div>
<div class="text-xs">
<div class="text-xs" v-if="!onlyWithCamera">
<span class="font-medium">{{ t('click_to_upload') }}</span> {{ t('or_drag_and_drop_files') }}
</div>
</div>
@ -39,6 +45,7 @@
ref="input"
:multiple="multiple"
:accept="accept"
:capture="onlyWithCamera === true ? `camera` : null"
type="file"
class="hidden"
@change="onSelectFiles"
@ -48,11 +55,13 @@
</template>
<script>
import { IconCloudUpload, IconInnerShadowTop } from '@tabler/icons-vue'
import { IconCamera, IconCloudUpload, IconInnerShadowTop } from '@tabler/icons-vue'
import field from "../template_builder/field.vue";
export default {
name: 'FileDropzone',
components: {
IconCamera,
IconCloudUpload,
IconInnerShadowTop
},
@ -71,6 +80,11 @@ export default {
required: false,
default: false
},
onlyWithCamera: {
type: Boolean,
required: false,
default: false
},
accept: {
type: String,
required: false,
@ -89,13 +103,18 @@ export default {
}
},
computed: {
field() {
return field
},
inputId () {
return 'el' + Math.random().toString(32).split('.')[1]
}
},
methods: {
onDropFiles (e) {
if(!this.onlyWithCamera){
this.uploadFiles(e.dataTransfer.files)
}
},
onSelectFiles (e) {
e.preventDefault()

@ -94,7 +94,11 @@ const en = {
upload: 'Upload',
files: 'Files',
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.',
wait_countdown_seconds: 'Wait {countdown} seconds'
wait_countdown_seconds: 'Wait {countdown} seconds',
photo: 'Photo',
take: 'Take',
retake: 'Retake',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scan the QR code with your mobile camera app to open the form and take a photo',
}
const es = {
@ -192,6 +196,10 @@ const es = {
upload: 'Subir',
files: 'Archivos',
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
foto: 'Foto',
tomar: 'Tomar',
retomar: 'Retomar',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Escanea el código QR con la aplicación de cámara de tu móvil para abrir el formulario y tomar una foto',
wait_countdown_seconds: 'Espera {countdown} segundos'
}
@ -290,6 +298,10 @@ const it = {
upload: 'Carica',
files: 'File',
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
foto: 'Foto',
prendere: 'Prendere',
riprendere: 'Riprendere',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: "Scansiona il codice QR con l'app della fotocamera del tuo cellulare per aprire il modulo e scattare una foto",
wait_countdown_seconds: 'Attendi {countdown} secondi'
}
@ -388,6 +400,10 @@ const de = {
upload: 'Hochladen',
files: 'Dateien',
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
Foto: 'Foto',
take: 'Nimm',
Wiederholung: 'Wiederholen',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scannen Sie den QR-Code mit Ihrer mobilen Kamera-App, um das Formular zu öffnen und ein Foto aufzunehmen',
wait_countdown_seconds: 'Warte {countdown} Sekunden'
}
@ -486,7 +502,11 @@ const fr = {
upload: 'Télécharger',
files: 'Fichiers',
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
wait_countdown_seconds: 'Attendez {countdown} secondes'
wait_countdown_seconds: 'Attendez {countdown} secondes',
photo: 'Photo',
take: 'Prendre',
retake: 'Reprendre',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scannez le code QR avec l\'application photo de votre mobile pour ouvrir le formulaire et prendre une photo',
}
const pl = {
@ -583,7 +603,11 @@ const pl = {
reupload: 'Ponowne przesłanie',
upload: 'Przesyłanie',
files: 'Pliki',
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.',
photo: 'Zdjęcie',
take: 'Weź',
retake: 'Odtwórz ponownie',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Zeskanuj kod QR za pomocą aplikacji aparatu mobilnego, aby otworzyć formularz i zrobić zdjęcie',
}
const uk = {
@ -681,6 +705,10 @@ const uk = {
upload: 'Завантажити',
files: 'Файли',
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
photo: 'Фото',
take: 'Взяти',
retake: 'Перезняти',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Відскануйте QR-код за допомогою мобільної програми камери, щоб відкрити форму та зробити фото',
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
}
@ -779,6 +807,10 @@ const cs = {
upload: 'Nahrát',
files: 'Soubory',
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.',
photo: 'Fotografie',
take: 'Vezmi',
retake: 'Znovu získat',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Naskenujte QR kód pomocí mobilní aplikace pro fotoaparát, otevřete formulář a vyfotografujte',
wait_countdown_seconds: 'Počkejte {countdown} sekund'
}
@ -877,6 +909,10 @@ const pt = {
upload: 'Carregar',
files: 'Arquivos',
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.',
photo: 'Foto',
take: 'Pegar',
retake: 'Retomar',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Escaneie o código QR com seu aplicativo de câmera móvel para abrir o formulário e tirar uma foto',
wait_countdown_seconds: 'Aguarde {countdown} segundos'
}
@ -1075,6 +1111,10 @@ const nl = {
upload: 'Uploaden',
files: 'Bestanden',
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.',
photo: 'Foto',
take: 'Neem',
retake: 'Opnieuw nemen',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: 'Scan de QR-code met uw mobiele camera-app om het formulier te openen en een foto te maken',
wait_countdown_seconds: 'Wacht {countdown} seconden'
}
@ -1270,6 +1310,10 @@ const ko = {
upload: '업로드',
files: '파일',
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
photo: '사진',
take: '가져가다',
retake: '재응시',
scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo: '모바일 카메라 앱으로 QR 코드를 스캔하여 양식을 열고 사진을 찍으세요.',
wait_countdown_seconds: '{countdown}초 기다리세요'
}

@ -19,7 +19,7 @@
@click.prevent="remove"
>
<IconReload :width="16" />
{{ t('reupload') }}
{{ field.preferences?.only_with_camera ? t('retake') : t('reupload') }}
</button>
</div>
<div>
@ -45,12 +45,43 @@
<MarkdownContent :string="field.description" />
</div>
<FileDropzone
:message="`${t('upload')} ${(field.title || field.name) || t('image')}${field.required ? '' : ` (${t('optional')})`}`"
v-if="!field.preferences.only_with_camera || (isMobile && field.preferences.only_with_camera)"
:message="`${field.preferences?.only_with_camera ? t('take') : t('upload')} ${(field.title || field.name) || (field.preferences?.only_with_camera ? t('photo') : t('image'))}${field.required ? '' : ` (${t('optional')})`}`"
:submitter-slug="submitterSlug"
:dry-run="dryRun"
:accept="'image/*'"
:only-with-camera="field.preferences?.only_with_camera === true"
@upload="onImageUpload"
/>
<div
v-else
class="relative"
>
<div
class="bg-base-content/10 rounded-2xl"
>
<div
class="flex items-center justify-center w-full h-full p-4"
>
<div
class="bg-white p-4 rounded-xl h-full"
>
<canvas
ref="qrCanvas"
class="h-full"
width="132"
height="132"
/>
</div>
</div>
</div>
<div
dir="auto"
class="text-base-content/60 text-xs text-center w-full mt-1"
>
{{ t('scan_the_qr_code_with_your_mobile_camera_app_to_open_the_form_and_take_a_photo') }}
</div>
</div>
</div>
</template>
@ -101,11 +132,64 @@ export default {
methods: {
remove () {
this.$emit('update:model-value', '')
this.showQr()
},
onImageUpload (attachments) {
this.$emit('attached', attachments[0])
this.$emit('update:model-value', attachments[0].uuid)
this.stopCheckPhoto() //just in case
},
showQr() {
this.$nextTick(() => {
import('qr-creator').then(({ default: Qr }) => {
if (this.$refs.qrCanvas && !this.isMobile) {
Qr.render({
text: `${document.location.origin}/t/${this.submitterSlug}?f=${this.field.uuid.split('-')[0]}`,
radius: 0.0,
ecLevel: 'H',
background: null,
size: 132
}, this.$refs.qrCanvas)
this.startCheckPhoto()
}
})
})
},
startCheckPhoto() {
const after = JSON.stringify(new Date())
this.checkPhotoInterval = setInterval(() => {
this.checkPhoto({ after })
}, 2000)
},
stopCheckPhoto() {
if (this.checkPhotoInterval) {
clearInterval(this.checkPhotoInterval)
}
},
checkPhoto(params = {}) {
return fetch(document.location.origin + '/s/' + this.submitterSlug + '/values?field_uuid=' + this.field.uuid + '&after=' + params.after, {
method: 'GET'
}).then(async (resp) => {
const { attachment } = await resp.json()
if (attachment?.uuid) {
this.$emit('attached', attachment)
this.$emit('update:model-value', attachment.uuid)
this.stopCheckPhoto()
}
})
}
},
mounted() {
this.showQr()
},
unmounted() {
this.stopCheckPhoto()
},
computed: {
isMobile() {
return screen.width <= 760
}
}
}

@ -284,6 +284,20 @@
<span class="label-text">{{ t('with_logo') }}</span>
</label>
</li>
<li
v-if="field.type == 'image'"
@click.stop
>
<label class="cursor-pointer py-1.5">
<input
:checked="field.preferences?.only_with_camera == true"
type="checkbox"
class="toggle toggle-xs"
@change="[field.preferences ||= {}, field.preferences.only_with_camera = $event.target.checked, save()]"
>
<span class="label-text">{{ t('only_with_camera') }}</span>
</label>
</li>
<li
v-if="field.type == 'checkbox'"
@click.stop

@ -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 }

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7h1a2 2 0 0 0 2 -2a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1a2 2 0 0 0 2 2h1a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2" />
<path d="M9 13a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>

After

Width:  |  Height:  |  Size: 446 B

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html data-theme="docuseal" lang="en">
<head>
<%= render 'layouts/head_tags' %>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<% if ENV['ROLLBAR_CLIENT_TOKEN'] %>
<meta name="rollbar-token" content="<%= ENV.fetch('ROLLBAR_CLIENT_TOKEN', nil) %>">
<%= 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'] %>
</head>
<body>
<% field = (@submitter.submission.template_fields || @submitter.template.fields).find { |f| f['type'] == 'image' && f['uuid'].starts_with?(params[:f]) } %>
<file-photo data-slug="<%= params[:slug] %>" class="flex items-center h-screen p-2 justify-center">
<%= form_for '', url: submit_form_path(params[:slug]), html: { style: 'max-width: 900px; width: 100%; margin-bottom: 120px' }, method: :put do |f| %>
<input value="" type="hidden" name="values[<%= field['uuid'] %>]">
<% if field['description'] %>
<div class="w-full mb-2">
<%= field['description'] %>
</div>
<% end %>
<label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center p-2">
<div class="flex flex-col items-center text-center">
<span id="file-photo-icon">
<%= svg_icon('camera', class: 'w-10 h-10') %>
</span>
<span id="file-photo-loading" class="hidden">
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
</span>
<div class="font-medium mb-1">
<%= t('take_photo') %>
</div>
</div>
<button aria-label="Clear" class="hidden btn btn-ghost btn-sm font-medium top-0 right-0 absolute mt-1 mr-1">
<%= svg_icon('reload', class: 'w-5 h-5') %>
<span class="inline"><%= t('clear') %></span>
</button>
<input id="file" class="hidden" name="files[]" type="file" accept="image/*" capture="camera">
</div>
</label>
<div class="mt-4">
<button disabled class="base-button w-full">
<%= t('submit') %>
</button>
<%= f.button button_title(title: t('submit')), class: 'base-button w-full', style: 'display: none' %>
</div>
<% end %>
<div id="success" class="text-center p-2 hidden" style="margin-bottom: 100px">
<div class="flex items-center space-x-1 items-center justify-center text-2xl font-semibold mb-2">
<%= svg_icon('circle_check', class: 'text-green-600') %>
<span>
<%= t('photo_uploaded') %>
</span>
</div>
<div>
<%= t('return_back_to_your_desktop_device_to_complete_the_form_or_continue_on_mobile_html', link: submit_form_path(params[:slug])) %>
</div>
</div>
</file-photo>
</body>
</html>

@ -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 <a class="link" href="%{link}">continue on mobile</a>'
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 <a class="link" href="%{link}">continúa en móvil</a>'
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 <a class="link" href="%{link}">continua su mobile</a>'
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 <a class="link" href="%{link}">continuez sur mobile</a>'
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 <a class="link" href="%{link}">continue no celular</a>'
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 <a class="link" href="%{link}">fahre auf dem Handy fort</a>'
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?

@ -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

Loading…
Cancel
Save