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