Fix: Usar URL interna para debug webhook receiver (bypass authentik)
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s

This commit is contained in:
2025-12-02 21:27:45 -06:00
parent 80d0042c7e
commit 8f44826e64
14 changed files with 642 additions and 21 deletions

View File

@@ -27,10 +27,13 @@
</div>
</div>
<div class="flex items-center justify-between">
<p class="text-sm text-[var(--wa-text-muted)] truncate">{{ chat.lastMessage }}</p>
<div class="flex items-center gap-1 text-sm text-[var(--wa-text-muted)] truncate">
<UIcon v-if="messageTypeIcon" :name="messageTypeIcon" class="w-4 h-4 flex-shrink-0" />
<span class="truncate">{{ lastMessagePreview }}</span>
</div>
<span
v-if="chat.unreadCount > 0"
class="bg-[var(--wa-green-light)] text-white text-xs rounded-full px-2 py-0.5"
class="bg-[var(--wa-green-light)] text-white text-xs rounded-full px-2 py-0.5 flex-shrink-0"
>
{{ chat.unreadCount }}
</span>
@@ -66,7 +69,9 @@ interface Chat {
profilePicture?: string
lastMessage: string
lastMessageAt: Date
lastMessageType?: string
unreadCount: number
isGroup?: boolean
}
interface Props {
@@ -74,13 +79,57 @@ interface Props {
active?: boolean
}
defineProps<Props>()
const props = defineProps<Props>()
defineEmits<{
click: []
}>()
const showDebug = ref(false)
// Message type to icon mapping
const messageTypeIcons: Record<string, string> = {
image: 'i-lucide-image',
video: 'i-lucide-video',
audio: 'i-lucide-music',
document: 'i-lucide-file',
sticker: 'i-lucide-sticker',
contact: 'i-lucide-contact',
location: 'i-lucide-map-pin'
}
// Message type to placeholder text
const messageTypePlaceholders: Record<string, string> = {
image: 'Foto',
video: 'Video',
audio: 'Audio',
document: 'Documento',
sticker: 'Sticker',
contact: 'Contacto',
location: 'Ubicación'
}
const messageTypeIcon = computed(() => {
const type = props.chat.lastMessageType
if (!type || type === 'text') return null
return messageTypeIcons[type] || null
})
const lastMessagePreview = computed(() => {
const type = props.chat.lastMessageType
// If there's text content, show it
if (props.chat.lastMessage) {
return props.chat.lastMessage
}
// Otherwise show placeholder based on type
if (type && messageTypePlaceholders[type]) {
return messageTypePlaceholders[type]
}
return ''
})
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text)

View File

@@ -154,14 +154,22 @@
>
<UIcon name="i-lucide-reply" class="w-4 h-4" />
</button>
<button
class="p-1 rounded-full hover:bg-black/10"
:class="message.fromMe ? 'text-white/60 hover:text-white' : 'text-[var(--wa-text-muted)] hover:text-[var(--wa-text)]'"
title="Reaccionar"
@click="$emit('react', message)"
>
<UIcon name="i-lucide-smile-plus" class="w-4 h-4" />
</button>
<div class="relative">
<button
class="p-1 rounded-full hover:bg-black/10"
:class="message.fromMe ? 'text-white/60 hover:text-white' : 'text-[var(--wa-text-muted)] hover:text-[var(--wa-text)]'"
title="Reaccionar"
@click.stop="showReactionPicker = !showReactionPicker"
>
<UIcon name="i-lucide-smile-plus" class="w-4 h-4" />
</button>
<ReactionPicker
:visible="showReactionPicker"
position="bottom"
@select="handleReaction"
@close="showReactionPicker = false"
/>
</div>
<button
class="p-1 rounded-full hover:bg-black/10"
:class="message.fromMe ? 'text-white/60 hover:text-white' : 'text-[var(--wa-text-muted)] hover:text-[var(--wa-text)]'"
@@ -206,6 +214,7 @@ import MessageDocument from './content/MessageDocument.vue'
import MessageSticker from './content/MessageSticker.vue'
import MessageContact from './content/MessageContact.vue'
import MessageLocation from './content/MessageLocation.vue'
import ReactionPicker from './ReactionPicker.vue'
interface Props {
message: Message
@@ -217,13 +226,20 @@ const props = withDefaults(defineProps<Props>(), {
isGroup: false
})
defineEmits<{
const emit = defineEmits<{
reply: [message: Message]
react: [message: Message]
react: [message: Message, emoji: string]
scrollToMessage: [id: string]
}>()
const showDebug = ref(false)
const showReactionPicker = ref(false)
// Handle reaction selection
const handleReaction = (emoji: string) => {
emit('react', props.message, emoji)
showReactionPicker.value = false
}
// Computed properties
const senderName = computed(() => {

View File

@@ -117,6 +117,7 @@
:maxrows="5"
class="bg-[var(--wa-bg-light)]"
@keydown.enter.exact.prevent="handleSend"
@input="emit('typing')"
/>
</div>
@@ -165,6 +166,8 @@ const emit = defineEmits<{
send: [content: string, files: File[], caption: string, quotedId?: string]
sendVoice: [audioFile: File]
cancelReply: []
typing: []
recording: [isRecording: boolean]
}>()
// Refs for file inputs
@@ -266,18 +269,21 @@ const handleSend = () => {
const startRecording = async () => {
const success = await startAudioRecording()
if (!success) {
// Show error toast
if (success) {
emit('recording', true)
} else {
console.error('Failed to start recording')
}
}
const cancelRecording = () => {
cancelAudioRecording()
emit('recording', false)
}
const sendVoiceMessage = () => {
stopRecording()
emit('recording', false)
// Wait a bit for the blob to be ready
setTimeout(() => {

View File

@@ -0,0 +1,125 @@
<template>
<div
v-if="visible"
class="absolute z-50 bg-[var(--wa-bg-dark)] rounded-full shadow-lg border border-[var(--wa-border)] p-1 flex items-center gap-1"
:class="position === 'top' ? 'bottom-full mb-2' : 'top-full mt-2'"
@click.stop
>
<button
v-for="emoji in quickReactions"
:key="emoji"
class="w-8 h-8 flex items-center justify-center text-lg hover:bg-[var(--wa-bg-light)] rounded-full transition-transform hover:scale-125"
@click="selectReaction(emoji)"
>
{{ emoji }}
</button>
<button
class="w-8 h-8 flex items-center justify-center text-lg hover:bg-[var(--wa-bg-light)] rounded-full"
@click="showFullPicker = true"
title="Más emojis"
>
<UIcon name="i-lucide-plus" class="w-4 h-4 text-[var(--wa-text-muted)]" />
</button>
</div>
<!-- Full emoji picker modal -->
<UModal v-model="showFullPicker">
<div class="p-4">
<h3 class="text-lg font-semibold mb-4 text-[var(--wa-text)]">Elegir reacción</h3>
<div class="grid grid-cols-8 gap-2 max-h-64 overflow-y-auto">
<button
v-for="emoji in allEmojis"
:key="emoji"
class="w-10 h-10 flex items-center justify-center text-2xl hover:bg-[var(--wa-bg-light)] rounded transition-transform hover:scale-110"
@click="selectReaction(emoji); showFullPicker = false"
>
{{ emoji }}
</button>
</div>
<div class="mt-4 flex justify-end">
<UButton variant="ghost" @click="showFullPicker = false">
Cancelar
</UButton>
</div>
</div>
</UModal>
</template>
<script setup lang="ts">
interface Props {
visible: boolean
position?: 'top' | 'bottom'
}
withDefaults(defineProps<Props>(), {
visible: false,
position: 'top'
})
const emit = defineEmits<{
select: [emoji: string]
close: []
}>()
const showFullPicker = ref(false)
// Quick reactions (WhatsApp style)
const quickReactions = ['👍', '❤️', '😂', '😮', '😢', '🙏']
// Extended emoji list
const allEmojis = [
// Smileys
'😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂',
'🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩',
'😘', '😗', '😚', '😙', '🥲', '😋', '😛', '😜',
'🤪', '😝', '🤑', '🤗', '🤭', '🤫', '🤔', '🤐',
'🤨', '😐', '😑', '😶', '😏', '😒', '🙄', '😬',
'😮‍💨', '🤥', '😌', '😔', '😪', '🤤', '😴', '😷',
// Gestures
'👍', '👎', '👌', '🤌', '🤏', '✌️', '🤞', '🤟',
'🤘', '🤙', '👈', '👉', '👆', '👇', '☝️', '👋',
'🤚', '🖐️', '✋', '🖖', '👏', '🙌', '👐', '🤲',
'🤝', '🙏', '✍️', '💪', '🦾', '🦿', '🦵', '🦶',
// Hearts
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍',
'🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖',
'💘', '💝', '💟', '♥️', '💌', '💋', '👄', '👅',
// Objects
'🎉', '🎊', '🎈', '🎁', '🏆', '🥇', '🥈', '🥉',
'⚽', '🏀', '🏈', '⚾', '🎾', '🏐', '🎱', '🎮',
'🎵', '🎶', '🎤', '🎧', '🎸', '🎹', '🎺', '🎻',
'🍕', '🍔', '🍟', '🌭', '🍿', '🧁', '🍰', '🎂',
'☕', '🍵', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸',
// Nature
'🌸', '🌺', '🌹', '🌷', '🌻', '🌼', '💐', '🌿',
'☀️', '🌙', '⭐', '🌟', '✨', '💫', '🔥', '💧',
'🌈', '☁️', '⛈️', '❄️', '☃️', '⚡', '🌊', '🌍',
// Animals
'🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼',
'🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🐔',
'🦄', '🐝', '🦋', '🐌', '🐞', '🐜', '🦗', '🕷️'
]
const selectReaction = (emoji: string) => {
emit('select', emoji)
emit('close')
}
// Close on click outside
onMounted(() => {
const handleClickOutside = () => {
emit('close')
}
// Delay to avoid immediate close
setTimeout(() => {
document.addEventListener('click', handleClickOutside)
}, 100)
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
})
</script>