Undangan/proyek-frontend/app/components/templates/khitan/GuestBook.vue
2025-10-24 16:43:47 +07:00

451 lines
14 KiB
Vue

<!-- components/shared/GuestBook.vue -->
<template>
<div class="min-h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-x-hidden overflow-y-auto">
<!-- Background Pattern -->
<div class="absolute inset-0 bg-pattern opacity-30"></div>
<!-- Main Content -->
<div class="relative z-10 h-full flex items-center justify-center px-6 py-18">
<div class="w-full max-w-6xl mx-auto">
<!-- Title -->
<div class="text-center mb-8 animate-fade-in-down">
<h1 class="text-yellow-400 text-4xl md:text-5xl font-bold mb-4 font-script">
Do'a & Ucapan
</h1>
</div>
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 h-full max-h-[600px]">
<!-- Left Side - Input Form -->
<div class="animate-fade-in-left animation-delay-300">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-8 border border-yellow-400/30 h-full">
<h2 class="text-yellow-400 text-2xl font-bold mb-6">Say Something!</h2>
<form @submit.prevent="submitMessage" class="space-y-6">
<!-- Name Input -->
<div>
<label class="block text-white text-sm font-medium mb-2">Name</label>
<input
v-model="form.name"
type="text"
required
class="w-full px-4 py-3 bg-white/90 text-blue-900 rounded-lg border border-transparent focus:border-yellow-400 focus:ring-2 focus:ring-yellow-400/20 transition-all duration-300 placeholder-blue-400"
placeholder="Enter your name"
/>
</div>
<!-- Message Input -->
<div>
<label class="block text-white text-sm font-medium mb-2">Message</label>
<textarea
v-model="form.message"
required
rows="4"
class="w-full px-4 py-3 bg-white/90 text-blue-900 rounded-lg border border-transparent focus:border-yellow-400 focus:ring-2 focus:ring-yellow-400/20 transition-all duration-300 resize-none placeholder-blue-400"
placeholder="Write your message or prayer..."
></textarea>
</div>
<!-- Attendance Selection -->
<div>
<label class="block text-white text-sm font-medium mb-3">Attendance</label>
<div class="flex space-x-2">
<button
v-for="option in attendanceOptions"
:key="option.value"
type="button"
@click="form.attendance = option.value"
:class="[
'px-6 py-2 rounded-full font-medium transition-all duration-300',
form.attendance === option.value
? 'bg-yellow-400 text-blue-900'
: 'bg-white/20 text-white hover:bg-white/30'
]"
>
{{ option.label }}
</button>
</div>
</div>
<!-- Submit Button -->
<button
type="submit"
:disabled="isSubmitting"
class="w-full bg-yellow-400 hover:bg-yellow-500 disabled:bg-yellow-400/50 text-blue-900 font-bold py-3 px-6 rounded-full transition-all duration-300 transform hover:scale-105 disabled:transform-none disabled:cursor-not-allowed"
>
<span v-if="!isSubmitting">Send Now !</span>
<span v-else class="flex items-center justify-center">
<svg class="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Sending...
</span>
</button>
</form>
<!-- Success Message -->
<div v-if="showSuccess" class="mt-4 p-3 bg-green-500/20 border border-green-400 rounded-lg">
<p class="text-green-400 text-sm">Thank you for your message!</p>
</div>
</div>
</div>
<!-- Right Side - Messages List -->
<div class="animate-fade-in-right animation-delay-300">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-8 border border-yellow-400/30 h-full overflow-hidden">
<div class="h-full flex flex-col">
<div class="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-yellow-400/50 scrollbar-track-transparent">
<div class="space-y-4">
<!-- Sample Messages -->
<div
v-for="message in messages"
:key="message.id"
class="bg-white/5 rounded-lg p-4 border border-white/10 hover:border-yellow-400/30 transition-all duration-300"
>
<div class="flex items-start justify-between mb-2">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-yellow-400 rounded-full flex items-center justify-center">
<span class="text-blue-900 text-sm font-bold">
{{ message.name.charAt(0).toUpperCase() }}
</span>
</div>
<div>
<h4 class="text-yellow-400 font-semibold">{{ message.name }}</h4>
<p class="text-white/60 text-xs">{{ message.time }}</p>
</div>
</div>
<span
:class="[
'px-2 py-1 rounded-full text-xs font-medium',
message.attendance === 'yes'
? 'bg-green-500/20 text-green-400 border border-green-400/50'
: message.attendance === 'maybe'
? 'bg-yellow-500/20 text-yellow-400 border border-yellow-400/50'
: 'bg-red-500/20 text-red-400 border border-red-400/50'
]"
>
{{ message.attendance === 'yes' ? 'Attend' : message.attendance === 'maybe' ? 'Maybe' : 'Can\'t' }}
</span>
</div>
<p class="text-white text-sm leading-relaxed">{{ message.message }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Decorative Elements -->
<div class="absolute inset-0 pointer-events-none overflow-hidden">
<!-- Top left decoration -->
<div class="absolute top-8 left-8 opacity-20">
<svg viewBox="0 0 50 50" class="w-12 h-12 text-yellow-400">
<circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="2"/>
<path d="M15 25 L25 15 L35 25 L25 35 Z" fill="currentColor"/>
</svg>
</div>
<!-- Top right decoration -->
<div class="absolute top-8 right-8 opacity-20">
<svg viewBox="0 0 50 50" class="w-12 h-12 text-yellow-400">
<rect x="10" y="10" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2" rx="5"/>
<circle cx="25" cy="25" r="8" fill="currentColor"/>
</svg>
</div>
<!-- Bottom decorations -->
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 opacity-10">
<div class="flex space-x-2">
<div class="w-2 h-2 bg-yellow-400 rounded-full"></div>
<div class="w-2 h-2 bg-yellow-400 rounded-full"></div>
<div class="w-2 h-2 bg-yellow-400 rounded-full"></div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// Props
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
// Reactive data
const form = ref({
name: '',
message: '',
attendance: 'yes'
})
const isSubmitting = ref(false)
const showSuccess = ref(false)
const attendanceOptions = [
{ value: 'yes', label: 'Yes' },
{ value: 'maybe', label: 'Maybe' },
{ value: 'no', label: 'No' }
]
// Sample messages - replace with actual data from backend
const messages = ref([
{
id: 1,
name: 'HWD FRENDSS ^^',
message: 'Selamat ya! Semoga menjadi anak yang sholeh dan berbakti kepada orang tua.',
attendance: 'yes',
time: '2 hours ago'
},
{
id: 2,
name: 'Tio SMAN6BDG',
message: 'Congratsss!',
attendance: 'yes',
time: '3 hours ago'
},
{
id: 3,
name: 'Muthia Rahma',
message: 'Happy wedd my sisstaa!',
attendance: 'maybe',
time: '5 hours ago'
},
{
id: 4,
name: 'Alia Sena P',
message: 'Barakallahu fiikum. Semoga menjadi keluarga yang sakinah mawadah wa rahmah.',
attendance: 'yes',
time: '6 hours ago'
},
{
id: 5,
name: 'Ahmad Rizki',
message: 'Selamat menjalani sunnah Rasul! Semoga Allah senantiasa melindungi.',
attendance: 'yes',
time: '1 day ago'
},
{
id: 6,
name: 'Siti Aminah',
message: 'Alhamdulillah, semoga menjadi awal yang baik untuk masa depan yang cerah.',
attendance: 'maybe',
time: '1 day ago'
}
])
// Methods
const submitMessage = async () => {
if (!form.value.name.trim() || !form.value.message.trim()) {
return
}
isSubmitting.value = true
try {
// Simulate API call - replace with actual backend integration
await new Promise(resolve => setTimeout(resolve, 1500))
// Add new message to the list (in real implementation, this would come from backend)
const newMessage = {
id: Date.now(),
name: form.value.name,
message: form.value.message,
attendance: form.value.attendance,
time: 'Just now'
}
messages.value.unshift(newMessage)
// Reset form
form.value = {
name: '',
message: '',
attendance: 'yes'
}
// Show success message
showSuccess.value = true
setTimeout(() => {
showSuccess.value = false
}, 3000)
} catch (error) {
console.error('Error submitting message:', error)
} finally {
isSubmitting.value = false
}
}
// Lifecycle
onMounted(() => {
// Load messages from props if available
if (props.data?.messages && props.data.messages.length > 0) {
messages.value = props.data.messages
}
})
</script>
<style scoped>
/* Background Pattern */
.bg-pattern {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="islamic" x="0" y="0" width="25" height="25" patternUnits="userSpaceOnUse"><g fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"><path d="M12.5 0 L25 12.5 L12.5 25 L0 12.5 Z M6.25 6.25 L18.75 6.25 L18.75 18.75 L6.25 18.75 Z"/><circle cx="12.5" cy="12.5" r="3"/></g></pattern></defs><rect width="100%" height="100%" fill="url(%23islamic)"/></svg>');
}
/* Animations */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-fade-in-down {
animation: fadeInDown 0.8s ease-out forwards;
}
.animate-fade-in-left {
animation: fadeInLeft 0.8s ease-out forwards;
}
.animate-fade-in-right {
animation: fadeInRight 0.8s ease-out forwards;
}
/* Animation Delays */
.animation-delay-300 {
animation-delay: 0.3s;
opacity: 0;
animation-fill-mode: forwards;
}
/* Custom fonts */
.font-script {
font-family: 'Times New Roman', serif;
font-style: italic;
}
/* Custom Scrollbar */
.scrollbar-thin {
scrollbar-width: thin;
}
.scrollbar-thin::-webkit-scrollbar {
width: 4px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgba(251, 191, 36, 0.5);
border-radius: 2px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background-color: rgba(251, 191, 36, 0.7);
}
/* Input Focus States */
input:focus,
textarea:focus {
outline: none;
}
/* Responsive */
@media (max-width: 1024px) {
.grid-cols-1.lg\:grid-cols-2 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.max-h-\[600px\] {
max-height: none;
}
}
@media (max-width: 640px) {
.text-4xl.md\:text-5xl {
font-size: 2rem;
}
.px-8 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-8 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.space-x-2 > * + * {
margin-left: 0.25rem;
}
.px-6 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.flex.space-x-2 {
flex-wrap: wrap;
gap: 0.5rem;
}
.flex.space-x-2 > * {
margin-left: 0;
}
}
@media (max-width: 480px) {
.grid {
gap: 1rem;
}
.px-4 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
}
</style>