mirror of https://github.com/docusealco/docuseal
				
				
				
			
							parent
							
								
									91f3d8ed7e
								
							
						
					
					
						commit
						f6c061a57b
					
				@ -0,0 +1,21 @@
 | 
				
			||||
# frozen_string_literal: true
 | 
				
			||||
 | 
				
			||||
class SubmitFormDrawSignatureController < 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,21 @@
 | 
				
			||||
# frozen_string_literal: true
 | 
				
			||||
 | 
				
			||||
class SubmitFormValuesController < ApplicationController
 | 
				
			||||
  skip_before_action :authenticate_user!
 | 
				
			||||
  skip_authorization_check
 | 
				
			||||
 | 
				
			||||
  def index
 | 
				
			||||
    submitter = Submitter.find_by!(slug: params[:submit_form_slug])
 | 
				
			||||
 | 
				
			||||
    return render json: {} if submitter.completed_at?
 | 
				
			||||
    return render json: {} if submitter.submission.template.archived_at? || submitter.submission.archived_at?
 | 
				
			||||
 | 
				
			||||
    value = submitter.values[params['field_uuid']]
 | 
				
			||||
    attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?
 | 
				
			||||
 | 
				
			||||
    render json: {
 | 
				
			||||
      value:,
 | 
				
			||||
      attachment: attachment&.as_json(only: %i[uuid], methods: %i[url filename content_type])
 | 
				
			||||
    }, head: :ok
 | 
				
			||||
  end
 | 
				
			||||
end
 | 
				
			||||
@ -0,0 +1,113 @@
 | 
				
			||||
import SignaturePad from 'signature_pad'
 | 
				
			||||
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
 | 
				
			||||
 | 
				
			||||
window.customElements.define('signature-form', class extends HTMLElement {
 | 
				
			||||
  connectedCallback () {
 | 
				
			||||
    const scale = 3
 | 
				
			||||
 | 
				
			||||
    this.canvas.width = this.canvas.parentNode.clientWidth * scale
 | 
				
			||||
    this.canvas.height = this.canvas.parentNode.clientHeight * scale
 | 
				
			||||
 | 
				
			||||
    this.canvas.getContext('2d').scale(scale, scale)
 | 
				
			||||
 | 
				
			||||
    this.pad = new SignaturePad(this.canvas)
 | 
				
			||||
 | 
				
			||||
    this.pad.addEventListener('endStroke', () => {
 | 
				
			||||
      this.updateSubmitButtonVisibility()
 | 
				
			||||
    })
 | 
				
			||||
 | 
				
			||||
    this.clearButton.addEventListener('click', (e) => {
 | 
				
			||||
      e.preventDefault()
 | 
				
			||||
 | 
				
			||||
      this.clearSignaturePad()
 | 
				
			||||
    })
 | 
				
			||||
 | 
				
			||||
    this.form.addEventListener('submit', (e) => {
 | 
				
			||||
      e.preventDefault()
 | 
				
			||||
 | 
				
			||||
      this.submitButton.disabled = true
 | 
				
			||||
 | 
				
			||||
      this.submitImage().then((data) => {
 | 
				
			||||
        this.valueInput.value = data.uuid
 | 
				
			||||
 | 
				
			||||
        return 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
 | 
				
			||||
      })
 | 
				
			||||
    })
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  clearSignaturePad () {
 | 
				
			||||
    this.pad.clear()
 | 
				
			||||
    this.updateSubmitButtonVisibility()
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  updateSubmitButtonVisibility () {
 | 
				
			||||
    if (this.pad.isEmpty()) {
 | 
				
			||||
      this.submitButton.style.display = 'none'
 | 
				
			||||
      this.placeholderButton.style.display = 'block'
 | 
				
			||||
    } else {
 | 
				
			||||
      this.submitButton.style.display = 'block'
 | 
				
			||||
      this.placeholderButton.style.display = 'none'
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  async submitImage () {
 | 
				
			||||
    return new Promise((resolve, reject) => {
 | 
				
			||||
      cropCanvasAndExportToPNG(this.canvas, { errorOnTooSmall: true }).then(async (blob) => {
 | 
				
			||||
        const file = new File([blob], 'signature.png', { type: 'image/png' })
 | 
				
			||||
 | 
				
			||||
        const formData = new FormData()
 | 
				
			||||
 | 
				
			||||
        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((attachment) => {
 | 
				
			||||
          return resolve(attachment)
 | 
				
			||||
        })
 | 
				
			||||
      }).catch((error) => {
 | 
				
			||||
        return reject(error)
 | 
				
			||||
      })
 | 
				
			||||
    })
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  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 canvas () {
 | 
				
			||||
    return this.querySelector('canvas')
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  get valueInput () {
 | 
				
			||||
    return this.querySelector('input[name^="values"]')
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  get form () {
 | 
				
			||||
    return this.querySelector('form')
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  get success () {
 | 
				
			||||
    return this.querySelector('#success')
 | 
				
			||||
  }
 | 
				
			||||
})
 | 
				
			||||
@ -0,0 +1,51 @@
 | 
				
			||||
<!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', 'draw', defer: true %>
 | 
				
			||||
    <% else %>
 | 
				
			||||
      <%= javascript_pack_tag 'draw', defer: true %>
 | 
				
			||||
    <% end %>
 | 
				
			||||
    <%= stylesheet_pack_tag 'form', media: 'all' %>
 | 
				
			||||
    <%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %>
 | 
				
			||||
  </head>
 | 
				
			||||
  <body>
 | 
				
			||||
    <signature-form 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[<%= (@submitter.submission.template_fields || @submitter.template.fields).find { |f| f['type'] == 'signature' && f['uuid'].starts_with?(params[:f]) }['uuid'] %>]">
 | 
				
			||||
        <div class="font-semibold text-4xl text-center w-full mb-2">
 | 
				
			||||
          Draw Signature
 | 
				
			||||
        </div>
 | 
				
			||||
        <div class="w-full bg-white rounded-2xl border relative" style="height: 300px">
 | 
				
			||||
          <canvas class="w-full"></canvas>
 | 
				
			||||
          <button aria-label="Clear" class="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">Clear</span>
 | 
				
			||||
          </button>
 | 
				
			||||
        </div>
 | 
				
			||||
        <div class="mt-4">
 | 
				
			||||
          <button disabled class="base-button w-full">
 | 
				
			||||
            Submit
 | 
				
			||||
          </button>
 | 
				
			||||
          <%= f.button button_title(title: '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>
 | 
				
			||||
            Signature Uploaded
 | 
				
			||||
          </span>
 | 
				
			||||
        </div>
 | 
				
			||||
        <div>
 | 
				
			||||
          Return back to your desktop device to complete the form or <a class="link" href="<%= submit_form_path(params[:slug]) %>">continue on mobile</a>
 | 
				
			||||
        </div>
 | 
				
			||||
      </div>
 | 
				
			||||
    </signature-form>
 | 
				
			||||
  </body>
 | 
				
			||||
</html>
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue