up
This commit is contained in:
@@ -2,18 +2,105 @@
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{{-- viewport-fit=cover expone las safe areas del notch/Dynamic Island --}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="color-scheme" content="dark">
|
||||
{{-- Apple PWA / standalone --}}
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Chat">
|
||||
<title>Chat en vivo | {{ config('app.name') }}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
/* ── Reset iOS/Safari ───────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
html {
|
||||
/* Altura real del viewport incluyendo safe areas */
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: #111b21;
|
||||
/* Evita el scroll del body — solo scrollea el área de mensajes */
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
/* Altura: dvh con fallback para Safari < 15.4 */
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
height: -webkit-fill-available;
|
||||
/* Elimina el tap highlight azul en iOS */
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* Evita el zoom Safari en inputs < 16px */
|
||||
input, textarea, select {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
/* Scroll suave tipo nativo en iOS */
|
||||
.ios-scroll {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
/* Deshabilitar appearance por defecto en iOS */
|
||||
input[type="tel"],
|
||||
input[type="text"],
|
||||
textarea {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
button {
|
||||
-webkit-appearance: none;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Safe area — rellena el notch arriba y el home indicator abajo */
|
||||
.safe-top { padding-top: env(safe-area-inset-top, 0px); }
|
||||
.safe-bottom { padding-bottom: env(safe-area-inset-bottom, 0px); }
|
||||
.safe-left { padding-left: env(safe-area-inset-left, 0px); }
|
||||
.safe-right { padding-right: env(safe-area-inset-right, 0px); }
|
||||
</style>
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="bg-[#111b21] min-h-screen flex items-center justify-center" style="font-family:'Inter',sans-serif;">
|
||||
<body>
|
||||
|
||||
<livewire:chat.public-chat />
|
||||
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
@livewireScripts
|
||||
|
||||
<script>
|
||||
/**
|
||||
* visualViewport API: cuando el teclado iOS aparece, el viewport se reduce.
|
||||
* Ajustamos la altura del chat dinámicamente para que el input quede visible.
|
||||
*/
|
||||
function initViewportFix() {
|
||||
const root = document.getElementById('chat-root');
|
||||
if (!root || !window.visualViewport) return;
|
||||
|
||||
const update = () => {
|
||||
const h = window.visualViewport.height;
|
||||
root.style.height = h + 'px';
|
||||
// Scroll al último mensaje al aparecer teclado
|
||||
const msgs = document.getElementById('pub-messages');
|
||||
if (msgs) setTimeout(() => { msgs.scrollTop = msgs.scrollHeight; }, 50);
|
||||
};
|
||||
|
||||
window.visualViewport.addEventListener('resize', update);
|
||||
window.visualViewport.addEventListener('scroll', update);
|
||||
update();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initViewportFix);
|
||||
document.addEventListener('livewire:load', initViewportFix);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<div class="w-full max-w-md mx-auto flex flex-col" style="height:100dvh">
|
||||
{{--
|
||||
id="chat-root" es el ancla que usa el visualViewport JS para ajustar altura cuando
|
||||
aparece el teclado iOS. Ocupa todo el viewport disponible (cuerpo del body).
|
||||
--}}
|
||||
<div id="chat-root"
|
||||
class="w-full flex flex-col bg-[#111b21]"
|
||||
style="height: 100vh; height: 100dvh; height: -webkit-fill-available; max-width: 480px; margin: 0 auto;">
|
||||
|
||||
{{-- ══ PASO 1: Formulario de inicio ══ --}}
|
||||
@if ($paso === 'telefono')
|
||||
<div class="flex flex-col items-center justify-center h-full px-6">
|
||||
<div class="w-full bg-[#202c33] rounded-2xl shadow-2xl p-8">
|
||||
<div class="flex flex-col items-center justify-center flex-1 px-5 safe-top safe-bottom safe-left safe-right">
|
||||
<div class="w-full bg-[#202c33] rounded-2xl shadow-2xl p-7">
|
||||
|
||||
<div class="flex flex-col items-center mb-8">
|
||||
<div class="flex flex-col items-center mb-7">
|
||||
<div class="w-20 h-20 bg-[#00a884] rounded-full flex items-center justify-center mb-4 shadow-lg">
|
||||
<svg class="w-10 h-10 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
|
||||
@@ -17,20 +23,36 @@
|
||||
|
||||
<form wire:submit.prevent="iniciar" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-[#8696a0] text-xs font-medium mb-1 uppercase tracking-wider">Teléfono *</label>
|
||||
<input wire:model.defer="telefono" type="tel" placeholder="Ej: 3001234567"
|
||||
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3 text-sm outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]">
|
||||
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">
|
||||
Teléfono *
|
||||
</label>
|
||||
{{-- font-size 16px (via CSS global) evita el zoom automático en Safari --}}
|
||||
<input wire:model.defer="telefono"
|
||||
type="tel"
|
||||
inputmode="tel"
|
||||
autocomplete="tel"
|
||||
placeholder="Ej: 3001234567"
|
||||
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]"
|
||||
style="color-scheme: dark;">
|
||||
@error('telefono')
|
||||
<span class="text-red-400 text-xs mt-1 block">{{ $message }}</span>
|
||||
@enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[#8696a0] text-xs font-medium mb-1 uppercase tracking-wider">Nombre (opcional)</label>
|
||||
<input wire:model.defer="nombre" type="text" placeholder="Tu nombre"
|
||||
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3 text-sm outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]">
|
||||
<label class="block text-[#8696a0] text-xs font-medium mb-1.5 uppercase tracking-wider">
|
||||
Nombre (opcional)
|
||||
</label>
|
||||
<input wire:model.defer="nombre"
|
||||
type="text"
|
||||
autocomplete="name"
|
||||
placeholder="Tu nombre"
|
||||
class="w-full bg-[#2a3942] text-white rounded-xl px-4 py-3.5 outline-none border border-transparent focus:border-[#00a884] transition placeholder-[#8696a0]"
|
||||
style="color-scheme: dark;">
|
||||
</div>
|
||||
{{-- min-height 48px = cumple Apple HIG de target táctil --}}
|
||||
<button type="submit"
|
||||
class="w-full bg-[#00a884] hover:bg-[#06cf9c] text-white font-semibold py-3 rounded-xl transition text-sm shadow-lg">
|
||||
class="w-full bg-[#00a884] active:bg-[#06cf9c] text-white font-semibold rounded-xl transition shadow-lg"
|
||||
style="min-height: 48px;">
|
||||
<span wire:loading.remove wire:target="iniciar">Comenzar chat →</span>
|
||||
<span wire:loading wire:target="iniciar">Conectando...</span>
|
||||
</button>
|
||||
@@ -43,73 +65,87 @@
|
||||
@else
|
||||
<div class="flex flex-col h-full bg-[#0b141a]"
|
||||
x-data
|
||||
x-on:scroll-chat.window="$nextTick(() => { let el = document.getElementById('pub-messages'); if(el) el.scrollTop = el.scrollHeight; })">
|
||||
x-on:scroll-chat.window="$nextTick(() => {
|
||||
const el = document.getElementById('pub-messages');
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
})">
|
||||
|
||||
{{-- Header --}}
|
||||
<div class="bg-[#202c33] px-4 py-3 flex items-center gap-3 shadow-md flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-[#00a884] rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-white font-semibold text-sm truncate">Soporte en línea</p>
|
||||
<p class="text-[#8696a0] text-xs">
|
||||
@if ($estadoConv === 'agente') Atendido por un agente
|
||||
@elseif ($estadoConv === 'cerrada') Conversación cerrada
|
||||
@else Bot activo
|
||||
@endif
|
||||
</p>
|
||||
{{-- Header + safe area top (para el notch / Dynamic Island) --}}
|
||||
<div class="bg-[#202c33] shadow-md flex-shrink-0 safe-top safe-left safe-right">
|
||||
<div class="flex items-center gap-3 px-4 py-3">
|
||||
<div class="w-10 h-10 bg-[#00a884] rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-white font-semibold text-sm truncate">Soporte en línea</p>
|
||||
<p class="text-[#8696a0] text-xs">
|
||||
@if ($estadoConv === 'agente') Atendido por un agente
|
||||
@elseif ($estadoConv === 'cerrada') Conversación cerrada
|
||||
@else Bot activo
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Mensajes --}}
|
||||
<div class="flex-1 overflow-y-auto px-4 py-4 space-y-2" id="pub-messages"
|
||||
wire:poll.3000ms="cargarMensajes">
|
||||
{{-- Mensajes — ios-scroll da momentum nativo en iPhone --}}
|
||||
<div class="flex-1 overflow-y-auto px-4 py-4 space-y-2 ios-scroll"
|
||||
id="pub-messages"
|
||||
wire:poll.3000ms="cargarMensajes"
|
||||
style="overscroll-behavior-y: contain;">
|
||||
@forelse($mensajes as $msg)
|
||||
@if ($msg['tipo'] === 'usuario')
|
||||
<div class="flex justify-end">
|
||||
<div class="max-w-[78%] bg-[#005c4b] text-white rounded-xl rounded-tr-sm px-4 py-2 shadow text-sm">
|
||||
<p class="whitespace-pre-line break-words">{{ $msg['contenido'] }}</p>
|
||||
<span class="text-[#8696a0] text-[10px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
|
||||
<div class="max-w-[80%] bg-[#005c4b] text-white rounded-xl rounded-tr-sm px-4 py-2 shadow">
|
||||
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;">{{ $msg['contenido'] }}</p>
|
||||
<span class="text-[#8696a0] text-[11px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex justify-start">
|
||||
<div class="max-w-[78%] bg-[#202c33] text-white rounded-xl rounded-tl-sm px-4 py-2 shadow text-sm">
|
||||
<div class="max-w-[80%] bg-[#202c33] text-white rounded-xl rounded-tl-sm px-4 py-2 shadow">
|
||||
@if ($msg['tipo'] === 'agente')
|
||||
<span class="text-amber-400 text-[10px] font-semibold block mb-0.5">Agente</span>
|
||||
<span class="text-amber-400 text-[11px] font-semibold block mb-0.5">Agente</span>
|
||||
@endif
|
||||
<p class="whitespace-pre-line break-words">{{ $msg['contenido'] }}</p>
|
||||
<span class="text-[#8696a0] text-[10px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
|
||||
<p class="whitespace-pre-line break-words leading-relaxed" style="font-size:15px;">{{ $msg['contenido'] }}</p>
|
||||
<span class="text-[#8696a0] text-[11px] float-right mt-1 ml-2">{{ $msg['created_at'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@empty
|
||||
<div class="text-center text-[#8696a0] text-sm py-10">Iniciando chat...</div>
|
||||
@endforelse
|
||||
{{-- Ancla para scroll al fondo --}}
|
||||
<div id="pub-messages-end"></div>
|
||||
</div>
|
||||
|
||||
{{-- Input --}}
|
||||
{{-- Input + safe area bottom (home indicator iPhone X+) --}}
|
||||
@if ($estadoConv !== 'cerrada')
|
||||
<div class="bg-[#202c33] px-3 py-3 flex items-end gap-2 flex-shrink-0">
|
||||
<textarea
|
||||
wire:model.defer="input"
|
||||
wire:keydown.enter.prevent="enviar"
|
||||
rows="1"
|
||||
placeholder="Escribe un mensaje..."
|
||||
class="flex-1 bg-[#2a3942] text-white text-sm rounded-xl px-4 py-3 resize-none outline-none placeholder-[#8696a0] max-h-28"
|
||||
style="overflow-y:auto;"
|
||||
></textarea>
|
||||
<button wire:click="enviar"
|
||||
class="w-11 h-11 bg-[#00a884] hover:bg-[#06cf9c] rounded-full flex items-center justify-center transition flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="bg-[#202c33] flex-shrink-0 safe-bottom safe-left safe-right">
|
||||
<div class="flex items-end gap-2 px-3 py-2.5">
|
||||
<textarea
|
||||
wire:model.defer="input"
|
||||
wire:keydown.enter.prevent="enviar"
|
||||
rows="1"
|
||||
placeholder="Escribe un mensaje..."
|
||||
{{-- font-size 16px evita zoom; border-radius cubre -webkit-appearance --}}
|
||||
class="flex-1 bg-[#2a3942] text-white rounded-xl px-4 py-3 resize-none outline-none placeholder-[#8696a0]"
|
||||
style="font-size:16px; overflow-y:auto; max-height:7rem; line-height:1.4; color-scheme:dark;"
|
||||
></textarea>
|
||||
{{-- 44x44 mínimo Apple HIG --}}
|
||||
<button wire:click="enviar"
|
||||
class="bg-[#00a884] active:bg-[#06cf9c] rounded-full flex items-center justify-center transition flex-shrink-0"
|
||||
style="width:44px; height:44px; min-width:44px;">
|
||||
<svg class="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="bg-[#202c33] px-4 py-4 text-center text-[#8696a0] text-sm flex-shrink-0">
|
||||
<div class="bg-[#202c33] px-4 py-4 text-center text-[#8696a0] text-sm flex-shrink-0 safe-bottom">
|
||||
Esta conversación ha sido cerrada.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user