451 lines
14 KiB
Vue
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> |