feat: edit contact modal confirm discard (#199)

* feat(contact): add confirmation dialog for discarding unsaved changes in edit contact modal

* chore: add GitHub Copilot instructions for project guidelines

* feat(contact): update confirmation dialog logic for discarding unsaved changes in edit contact modal
This commit is contained in:
Gabriel Jablonski 2026-01-27 20:03:42 -03:00 committed by GitHub
parent b1aaf58097
commit 4ca0ef22c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 7 deletions

8
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,8 @@
# GitHub Copilot Instructions
- Always include pt-BR translations for any new text added to the project.
- fazer.ai is always styled as-is, with a dot and lowercase letters. Never use Fazer.ai
- Always check if adding specs is necessary when modifying code.
- Evaluate if specs added are actually needed and not redundant. Specs should not be for documentation purposes only, they should cover expected behavior.
- Always evaluate if frontend changes are needed when modifying backend code, and vice versa.
- NEVER use `--` in `pnpm test -- <file>`. Just do `pnpm test <file>` directly

View File

@ -61,7 +61,13 @@
"EDIT_CONTACT": {
"BUTTON_LABEL": "Edit Contact",
"TITLE": "Edit contact",
"DESC": "Edit contact details"
"DESC": "Edit contact details",
"CONFIRM_DISCARD": {
"TITLE": "Discard changes?",
"MESSAGE": "You have unsaved changes. Are you sure you want to discard them?",
"YES": "Discard",
"NO": "Keep editing"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "Delete Contact",

View File

@ -61,7 +61,13 @@
"EDIT_CONTACT": {
"BUTTON_LABEL": "Editar Contato",
"TITLE": "Editar contato",
"DESC": "Alterar detalhes do contato"
"DESC": "Alterar detalhes do contato",
"CONFIRM_DISCARD": {
"TITLE": "Descartar alterações?",
"MESSAGE": "Você tem alterações não salvas. Tem certeza de que deseja descartá-las?",
"YES": "Descartar",
"NO": "Continuar editando"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "Excluir Contato",

View File

@ -64,6 +64,7 @@ export default {
{ key: 'github', prefixURL: 'https://github.com/' },
{ key: 'tiktok', prefixURL: 'https://tiktok.com/@' },
],
initialData: null,
};
},
validations: {
@ -100,8 +101,27 @@ export default {
}
return '';
},
hasUnsavedChanges() {
if (!this.initialData) return false;
const socialProfilesChanged = this.socialProfileKeys.some(
({ key }) =>
(this.socialProfileUserNames[key] || '') !==
(this.initialData.socialProfileUserNames[key] || '')
);
return (
this.name !== this.initialData.name ||
this.email !== this.initialData.email ||
this.phoneNumber !== this.initialData.phoneNumber ||
this.companyName !== this.initialData.companyName ||
this.description !== this.initialData.description ||
this.city !== this.initialData.city ||
(this.country?.id || '') !== (this.initialData.countryId || '') ||
this.avatarFile !== null ||
socialProfilesChanged
);
},
setPhoneNumber() {
if (this.parsePhoneNumber && this.parsePhoneNumber.countryCallingCode) {
if (this.parsePhoneNumber?.countryCallingCode) {
return this.phoneNumber;
}
if (this.phoneNumber === '' && this.activeDialCode !== '') {
@ -175,6 +195,16 @@ export default {
github: socialProfiles.github || '',
instagram: socialProfiles.instagram || '',
};
this.initialData = {
name: this.name,
email: this.email,
phoneNumber: this.phoneNumber,
companyName: this.companyName,
description: this.description,
city: this.city,
countryId: this.country.id,
socialProfileUserNames: { ...this.socialProfileUserNames },
};
},
getContactObject() {
if (this.country === null) {
@ -253,7 +283,7 @@ export default {
},
async handleAvatarDelete() {
try {
if (this.contact && this.contact.id) {
if (this.contact?.id) {
await this.$store.dispatch('contacts/deleteAvatar', this.contact.id);
useAlert(this.$t('CONTACT_FORM.DELETE_AVATAR.API.SUCCESS_MESSAGE'));
}

View File

@ -32,7 +32,15 @@ export default {
},
methods: {
onCancel() {
async onClose() {
const hasChanges = this.$refs.contactForm?.hasUnsavedChanges;
if (hasChanges) {
const shouldDiscard =
await this.$refs.confirmDiscardDialog.showConfirmation();
if (!shouldDiscard) {
return;
}
}
this.$emit('cancel');
},
onSuccess() {
@ -52,7 +60,7 @@ export default {
<template>
<woot-modal
v-model:show="localShow"
:on-close="onCancel"
:on-close="onClose"
modal-type="right-aligned"
>
<div class="flex flex-col h-auto overflow-auto">
@ -63,12 +71,20 @@ export default {
:header-content="$t('EDIT_CONTACT.DESC')"
/>
<ContactForm
ref="contactForm"
:contact="contact"
:in-progress="uiFlags.isUpdating"
:on-submit="onSubmit"
@success="onSuccess"
@cancel="onCancel"
@cancel="onClose"
/>
</div>
</woot-modal>
<woot-confirm-modal
ref="confirmDiscardDialog"
:title="$t('EDIT_CONTACT.CONFIRM_DISCARD.TITLE')"
:description="$t('EDIT_CONTACT.CONFIRM_DISCARD.MESSAGE')"
:confirm-label="$t('EDIT_CONTACT.CONFIRM_DISCARD.YES')"
:cancel-label="$t('EDIT_CONTACT.CONFIRM_DISCARD.NO')"
/>
</template>