Undangan/proyek-frontend/app/components/templates/wedding/GuestBook.vue
2025-10-24 17:05:32 +07:00

243 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<section class="bg-gradient-to-b from-amber-50 to-white py-16 px-4 md:px-6 lg:px-8">
<!-- Full-width container -->
<div class="w-full">
<!-- Judul (tetap terpusat) -->
<div class="text-center mb-12">
<div class="flex items-center justify-center gap-2 mb-6">
<span class="h-[1px] w-12 bg-amber-400"></span>
<span class="text-amber-500 text-2xl">Flower</span>
<span class="h-[1px] w-12 bg-amber-400"></span>
</div>
<h2 class="text-3xl md:text-4xl font-bold text-amber-600">
Guest Book
</h2>
</div>
<!-- Layout Form + Ucapan (konten terpusat) -->
<div class="max-w-5xl mx-auto">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
<!-- Form -->
<div
class="bg-white shadow-xl rounded-2xl p-6 sm:p-8 border border-amber-100 order-2 md:order-1"
data-aos="fade-right"
>
<h3 class="text-xl font-semibold text-amber-600 mb-6 flex items-center gap-2">
<Icon name="mdi:message-text" class="text-2xl" />
Say Something!
</h3>
<form @submit.prevent="handleSubmit" class="space-y-5">
<!-- Nama -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Your Name</label>
<input
v-model="form.name"
type="text"
placeholder="Enter your name"
required
maxlength="50"
class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-400 focus:border-amber-400 focus:outline-none transition-all"
/>
</div>
<!-- Ucapan -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Your Message <span class="text-xs text-gray-500">({{ form.message.length }}/280)</span>
</label>
<textarea
v-model="form.message"
rows="4"
placeholder="Write your wishes and prayers..."
required
maxlength="280"
class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-400 focus:border-amber-400 focus:outline-none transition-all resize-none"
></textarea>
</div>
<!-- Attendance -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Will you attend?</label>
<div class="flex gap-4 bg-amber-50 rounded-full p-2">
<label class="flex-1 flex items-center justify-center cursor-pointer">
<input
v-model="form.attendance"
type="radio"
value="yes"
class="hidden"
/>
<span :class="form.attendance === 'yes' ? 'bg-amber-500 text-white' : 'text-gray-700'"
class="px-4 py-2 rounded-full text-sm font-medium transition-all">
<Icon name="mdi:check-circle" class="inline mr-1 text-lg" /> Yes
</span>
</label>
<label class="flex-1 flex items-center justify-center cursor-pointer">
<input
v-model="form.attendance"
type="radio"
value="no"
class="hidden"
/>
<span :class="form.attendance === 'no' ? 'bg-red-500 text-white' : 'text-gray-700'"
class="px-4 py-2 rounded-full text-sm font-medium transition-all">
<Icon name="mdi:close-circle" class="inline mr-1 text-lg" /> No
</span>
</label>
</div>
</div>
<!-- Tombol -->
<button
type="submit"
:disabled="isSubmitting"
class="w-full bg-amber-500 hover:bg-amber-600 text-white font-semibold py-3 px-6 rounded-lg transition duration-200 shadow-md disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
<Icon v-if="isSubmitting" name="mdi:loading" class="animate-spin" />
<span v-else><Icon name="mdi:send" class="inline" /> Send Message</span>
<span v-if="isSubmitting">Sending...</span>
</button>
</form>
</div>
<!-- Daftar Ucapan -->
<div
class="bg-white shadow-xl rounded-2xl p-6 sm:p-8 border border-amber-100 order-1 md:order-2"
data-aos="fade-left"
ref="messagesContainer"
>
<div class="flex items-center gap-2 mb-6">
<Icon name="mdi:chat-outline" class="text-amber-500 text-2xl" />
<span class="text-gray-700 font-semibold">{{ messages.length }} Messages</span>
</div>
<!-- List Ucapan dengan Scroll -->
<div class="space-y-4 max-h-96 overflow-y-auto pr-2 custom-scrollbar">
<template v-if="messages.length > 0">
<div
v-for="(msg, i) in messages"
:key="i"
class="p-4 bg-gradient-to-br from-amber-50 to-white rounded-xl border border-amber-100 hover:shadow-md transition-all slide-in"
>
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-9 h-9 bg-amber-400 rounded-full flex items-center justify-center text-white font-bold text-sm">
{{ msg.name.charAt(0).toUpperCase() }}
</div>
<div>
<p class="font-semibold text-gray-800 text-sm">{{ msg.name }}</p>
<p class="text-xs text-gray-500">{{ formatDate(msg.createdAt) }}</p>
</div>
</div>
<span
class="px-3 py-1 text-xs font-semibold rounded-full flex items-center gap-1"
:class="msg.attendance === 'yes' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'"
>
<Icon :name="msg.attendance === 'yes' ? 'mdi:check' : 'mdi:close'" class="text-sm" />
{{ msg.attendance === 'yes' ? 'Attending' : 'Can\'t attend' }}
</span>
</div>
<p class="text-sm text-gray-700 leading-relaxed ps-11">{{ msg.message }}</p>
</div>
</template>
<!-- Empty State -->
<div v-else class="text-center py-12">
<Icon name="mdi:message-outline" class="text-gray-300 text-6xl mb-4" />
<p class="text-gray-400 text-sm">No messages yet. Be the first to leave a message!</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup>
import { ref, nextTick, watch } from 'vue'
const props = defineProps({
invitationId: { type: String, required: true }
})
const emit = defineEmits(['submitted'])
const form = ref({
name: '',
message: '',
attendance: 'yes'
})
const isSubmitting = ref(false)
const messagesContainer = ref(null)
const messages = ref([
{ name: 'Tia SMAN6BDG', message: 'Congratulations! Wishing you a lifetime of love!', attendance: 'yes', createdAt: new Date('2024-01-15') },
{ name: 'Muthia Rahma', message: 'Happy wedding! So happy for you both!', attendance: 'yes', createdAt: new Date('2024-01-14') },
{ name: 'Ahmad Fauzi', message: 'Barakallah! Semoga sakinah, mawaddah, warahmah', attendance: 'no', createdAt: new Date('2024-01-13') },
{ name: 'Sarah Amelia', message: 'Congrats! Cant wait to celebrate!', attendance: 'yes', createdAt: new Date('2024-01-12') },
])
// Auto-scroll ke atas saat ada pesan baru
watch(messages, async () => {
await nextTick()
if (messagesContainer.value) {
messagesContainer.value.scrollTop = 0
}
}, { deep: true })
const handleSubmit = async () => {
if (!form.value.name.trim() || !form.value.message.trim()) {
alert('Please fill in name and message')
return
}
if (form.value.message.length > 280) {
alert('Message too long (max 280 characters)')
return
}
isSubmitting.value = true
try {
await new Promise(r => setTimeout(r, 800))
const newMsg = { ...form.value, createdAt: new Date() }
messages.value.unshift(newMsg)
emit('submitted', newMsg)
form.value = { name: '', message: '', attendance: 'yes' }
} catch (err) {
alert('Failed to send message')
} finally {
isSubmitting.value = false
}
}
const formatDate = (date) => {
const d = new Date(date)
const now = new Date()
const diffMins = Math.floor((now - d) / 60000)
const diffHours = Math.floor(diffMins / 60)
const diffDays = Math.floor(diffHours / 24)
if (diffMins < 1) return 'Just now'
if (diffMins < 60) return `${diffMins}m ago`
if (diffHours < 24) return `${diffHours}h ago`
if (diffDays < 7) return `${diffDays}d ago`
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
}
</script>
<style scoped>
.custom-scrollbar::-webkit-scrollbar { width: 6px; }
.custom-scrollbar::-webkit-scrollbar-track { background: #fef3c7; border-radius: 10px; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #f59e0b; border-radius: 10px; }
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #d97706; }
.slide-in { animation: slideIn 0.4s ease-out; }
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>