Agregar modal personalizado de confirmación de salida
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 55s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 55s
- Reemplazar prompt del navegador por modal personalizado - Modal muestra lista de campos modificados con estilo de la app - Distingue entre almacenamiento local y servidor - Incluye soporte para modo oscuro - Mejora UX con diseño responsive
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
icon="i-heroicons-x-mark"
|
icon="i-heroicons-x-mark"
|
||||||
@click="$emit('close')"
|
@click="handleClose"
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</UButton>
|
</UButton>
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="lg"
|
size="lg"
|
||||||
@click="$emit('close')"
|
@click="handleClose"
|
||||||
:disabled="isSubmitting"
|
:disabled="isSubmitting"
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
@@ -311,6 +311,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|
||||||
|
<!-- Modal de confirmación de salida -->
|
||||||
|
<UModal v-model:open="showExitConfirm">
|
||||||
|
<template #content>
|
||||||
|
<div class="exit-confirm-modal">
|
||||||
|
<div class="exit-modal-header">
|
||||||
|
<UIcon name="i-heroicons-exclamation-triangle" class="w-12 h-12 text-warning-500" />
|
||||||
|
<h3 class="exit-modal-title">¿Estás seguro de salir?</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="exit-modal-body">
|
||||||
|
<p class="exit-modal-message">
|
||||||
|
Tienes cambios pendientes en:
|
||||||
|
</p>
|
||||||
|
<ul class="exit-modal-fields">
|
||||||
|
<li v-for="field in modifiedFieldsList" :key="field" class="exit-field-item">
|
||||||
|
<UIcon name="i-heroicons-pencil-square" class="w-4 h-4 text-warning-500" />
|
||||||
|
{{ field }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="exit-modal-notice">
|
||||||
|
<UIcon name="i-heroicons-information-circle" class="w-5 h-5" />
|
||||||
|
<p>
|
||||||
|
Estos cambios quedarán guardados <strong>localmente en tu navegador</strong>,
|
||||||
|
pero <strong class="text-error-600 dark:text-error-400">NO han sido enviados al servidor</strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="exit-modal-help">
|
||||||
|
<UIcon name="i-heroicons-light-bulb" class="w-5 h-5 text-primary-500" />
|
||||||
|
<p>
|
||||||
|
Para guardar definitivamente, haz clic en <strong>"Guardar cambios"</strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="exit-modal-actions">
|
||||||
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="soft"
|
||||||
|
size="lg"
|
||||||
|
@click="showExitConfirm = false"
|
||||||
|
>
|
||||||
|
<UIcon name="i-heroicons-arrow-left" class="w-5 h-5" />
|
||||||
|
Continuar editando
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
color="error"
|
||||||
|
size="lg"
|
||||||
|
@click="confirmExit"
|
||||||
|
>
|
||||||
|
<UIcon name="i-heroicons-arrow-right-on-rectangle" class="w-5 h-5" />
|
||||||
|
Salir sin guardar
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -319,12 +376,13 @@ const { user } = useAuthentik()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
// Estado del formulario
|
// Estado del formulario
|
||||||
const isSubmitting = ref(false)
|
const isSubmitting = ref(false)
|
||||||
const isUploading = ref(false)
|
const isUploading = ref(false)
|
||||||
const showCamera = ref(false)
|
const showCamera = ref(false)
|
||||||
|
const showExitConfirm = ref(false)
|
||||||
|
|
||||||
// Cookie para persistir cambios del formulario
|
// Cookie para persistir cambios del formulario
|
||||||
const formCookie = useCookie<Record<string, string>>('profile-form-draft', {
|
const formCookie = useCookie<Record<string, string>>('profile-form-draft', {
|
||||||
@@ -536,6 +594,23 @@ const handleAvatarCapture = async (imageBlob: Blob) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manejar intento de cerrar formulario
|
||||||
|
const handleClose = () => {
|
||||||
|
if (hasChanges.value) {
|
||||||
|
// Si hay cambios, mostrar modal de confirmación
|
||||||
|
showExitConfirm.value = true
|
||||||
|
} else {
|
||||||
|
// Si no hay cambios, cerrar directamente
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmar salida sin guardar
|
||||||
|
const confirmExit = () => {
|
||||||
|
showExitConfirm.value = false
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
// Reiniciar formulario a valores originales
|
// Reiniciar formulario a valores originales
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
if (!confirm('¿Estás seguro de descartar todos los cambios?\n\nSe restaurarán los valores originales y se eliminará el borrador guardado.')) {
|
if (!confirm('¿Estás seguro de descartar todos los cambios?\n\nSe restaurarán los valores originales y se eliminará el borrador guardado.')) {
|
||||||
@@ -746,6 +821,120 @@ const removeAvatar = async () => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal de confirmación de salida */
|
||||||
|
.exit-confirm-modal {
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-gray-900);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-message {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-gray-800);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-fields {
|
||||||
|
list-style: none;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
background: rgba(var(--color-warning-500), 0.1);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid rgba(var(--color-warning-500), 0.3);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-field-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-gray-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-notice {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(var(--color-error-500), 0.1);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid rgba(var(--color-error-500), 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-notice p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--color-gray-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-help {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(var(--color-primary-500), 0.1);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid rgba(var(--color-primary-500), 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-help p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--color-gray-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.exit-confirm-modal {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-actions {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-modal-actions button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -827,4 +1016,39 @@ const removeAvatar = async () => {
|
|||||||
.dark .modal-title {
|
.dark .modal-title {
|
||||||
color: var(--color-gray-100) !important;
|
color: var(--color-gray-100) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-title {
|
||||||
|
color: var(--color-gray-100) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-message {
|
||||||
|
color: var(--color-gray-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-field-item {
|
||||||
|
color: var(--color-gray-300) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-fields {
|
||||||
|
background: rgba(var(--color-warning-500), 0.15) !important;
|
||||||
|
border-color: rgba(var(--color-warning-500), 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-notice {
|
||||||
|
background: rgba(var(--color-error-500), 0.15) !important;
|
||||||
|
border-color: rgba(var(--color-error-500), 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-notice p {
|
||||||
|
color: var(--color-gray-300) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-help {
|
||||||
|
background: rgba(var(--color-primary-500), 0.15) !important;
|
||||||
|
border-color: rgba(var(--color-primary-500), 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .exit-modal-help p {
|
||||||
|
color: var(--color-gray-300) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user