diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue
index 17e6a248b5..2fcfc57042 100644
--- a/app/javascript/template_builder/builder.vue
+++ b/app/javascript/template_builder/builder.vue
@@ -346,6 +346,7 @@
@close="isShowSigningOrderModal = false"
/>
+
@@ -364,6 +365,7 @@ import DocumentControls from './controls'
import MobileFields from './mobile_fields'
import FieldSubmitter from './field_submitter'
import SigningOrderModal from './signing_order_modal'
+import Toast from './toast'
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
import { v4 } from 'uuid'
import { ref, computed, toRaw, watch } from 'vue'
@@ -393,7 +395,8 @@ export default {
IconAdjustments,
IconEye,
IconDeviceFloppy,
- SigningOrderModal
+ SigningOrderModal,
+ Toast
},
provide () {
return {
@@ -1524,7 +1527,7 @@ export default {
method: 'POST',
body: formData
}).then(async (resp) => {
- this.updateFromUpload(await resp.json())
+ this.updateFromUpload(await resp.json(), { showToast: false })
}).finally(() => {
this.isLoadingBlankPage = false
})
@@ -1533,7 +1536,11 @@ export default {
onUploadFailed (error) {
if (error) alert(error)
},
- updateFromUpload (data) {
+ updateFromUpload (data, { showToast = true } = {}) {
+ if (showToast) {
+ this.$refs.toast.show(this.t('document_uploaded_successfully'))
+ }
+
this.template.schema.push(...data.schema)
this.template.documents.push(...data.documents)
@@ -1606,7 +1613,7 @@ export default {
this.save()
}
},
- onDocumentReplace (data) {
+ onDocumentReplace (data, { showToast = true } = {}) {
const { replaceSchemaItem, schema, documents } = data
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...replaceSchemaItem, ...schema[0] })
@@ -1651,6 +1658,10 @@ export default {
this.onUpload(this.template)
}
+ if (showToast) {
+ this.$refs.toast.show(this.t('document_uploaded_successfully'))
+ }
+
this.save()
},
onDocumentsReplace (data) {
@@ -1662,16 +1673,20 @@ export default {
replaceSchemaItem: existingSchemaItem,
schema: [schemaItem],
documents: [data.documents.find((doc) => doc.uuid === schemaItem.attachment_uuid)]
- })
+ }, { showToast: false })
} else {
this.updateFromUpload({
schema: [schemaItem],
documents: [data.documents.find((doc) => doc.uuid === schemaItem.attachment_uuid)],
fields: data.fields,
submitters: data.submitters
- })
+ }, { showToast: false })
}
})
+
+ if (data.schema.length) {
+ this.$refs.toast.show(this.t('document_uploaded_successfully'))
+ }
},
onDocumentsReplaceAndTemplateClone (template) {
window.Turbo.visit(`/templates/${template.id}/edit`)
diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js
index 93ddf1c7e1..a2bd4f3abd 100644
--- a/app/javascript/template_builder/i18n.js
+++ b/app/javascript/template_builder/i18n.js
@@ -130,6 +130,7 @@ const en = {
replace: 'Replace',
uploading_: 'Uploading...',
add_document: 'Add Document',
+ document_uploaded_successfully: 'Document uploaded successfully',
none: 'None',
ssn: 'SSN',
ein: 'EIN',
@@ -276,6 +277,7 @@ const es = {
replace: 'Reemplazar',
uploading_: 'Subiendo...',
add_document: 'Subir',
+ document_uploaded_successfully: 'Documento cargado correctamente',
any: 'Cualquier',
drawn: 'Dibujado',
drawn_or_typed: 'Dibujado o Escrito',
@@ -432,6 +434,7 @@ const it = {
replace: 'Sostituisci',
uploading_: 'Caricamento in corso...',
add_document: 'Aggiungi',
+ document_uploaded_successfully: 'Documento caricato correttamente',
none: 'Nessuno',
ssn: 'SSN',
ein: 'EIN',
@@ -578,6 +581,7 @@ const pt = {
replace: 'Substituir',
uploading_: 'Carregando...',
add_document: 'Enviar',
+ document_uploaded_successfully: 'Documento enviado com sucesso',
any: 'Qualquer',
drawn: 'Desenhado',
drawn_or_typed: 'Desenhado ou Digitado',
@@ -729,6 +733,7 @@ const fr = {
replace: 'Remplacer',
uploading_: 'Téléchargement en cours...',
add_document: 'Télécharger',
+ document_uploaded_successfully: 'Document téléchargé avec succès',
any: 'Tout',
drawn: 'Dessiné',
drawn_or_typed: 'Dessiné ou Tapé',
@@ -880,6 +885,7 @@ const de = {
replace: 'Ersetzen',
uploading_: 'Hochladen...',
add_document: 'Hochladen',
+ document_uploaded_successfully: 'Dokument erfolgreich hochgeladen',
any: 'Jede',
drawn: 'Gezeichnet',
drawn_or_typed: 'Gezeichnet oder getippt',
diff --git a/app/javascript/template_builder/toast.vue b/app/javascript/template_builder/toast.vue
new file mode 100644
index 0000000000..5517a62695
--- /dev/null
+++ b/app/javascript/template_builder/toast.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
diff --git a/spec/system/template_builder_spec.rb b/spec/system/template_builder_spec.rb
index d14db058a2..f24291b7d4 100644
--- a/spec/system/template_builder_spec.rb
+++ b/spec/system/template_builder_spec.rb
@@ -27,6 +27,31 @@
end.to change { template.documents.count }.by(1)
expect(page).to have_content('sample-image')
+ expect(page).to have_css('#upload_success_toast', text: 'Document uploaded successfully')
+ expect(page).to have_css('#upload_success_toast[role="status"][aria-live="polite"]')
+ end
+ end
+
+ context 'when uploading to an empty template' do
+ let(:template) { create(:template, account:, author:, attachment_count: 0) }
+
+ before do
+ visit edit_template_path(template)
+ end
+
+ it 'shows a success toast after the first document upload' do
+ expect(page).to have_css('#document_dropzone')
+
+ expect do
+ find('#document_dropzone input[type="file"]', visible: false)
+ .attach_file(Rails.root.join('spec/fixtures/sample-image.png'))
+
+ page.driver.wait_for_network_idle
+ end.to change { template.reload.documents.count }.by(1)
+
+ expect(page).to have_content('sample-image')
+ expect(page).to have_css('#upload_success_toast', text: 'Document uploaded successfully')
+ expect(page).to have_css('#upload_success_toast[role="status"][aria-live="polite"]')
end
end