267 lines
8.3 KiB
Vue
267 lines
8.3 KiB
Vue
<template>
|
|
<section id="testimoni" class="bg-slate-50 py-20 px-5">
|
|
<div class="container mx-auto text-center">
|
|
<h2 class="text-4xl font-extrabold text-gray-800 mb-2">
|
|
Apa Kata Mereka?
|
|
</h2>
|
|
<p class="text-lg text-gray-600 mb-10">
|
|
Kisah sukses dari para pengguna yang telah mempercayakan momen spesialnya kepada kami.
|
|
</p>
|
|
|
|
<!-- CSS Marquee Scroll -->
|
|
<div class="marquee-container mb-10">
|
|
<div class="marquee-content" :style="{ '--total-cards': testimonials?.length || 0 }">
|
|
<!-- Render original cards -->
|
|
<div
|
|
v-for="testimonial in testimonials"
|
|
:key="`original-${testimonial.id}`"
|
|
class="testimonial-card"
|
|
@click="previewModal = testimonial"
|
|
>
|
|
<!-- Rating -->
|
|
<div class="mb-4 flex items-center">
|
|
<svg
|
|
v-for="n in 5"
|
|
:key="n"
|
|
class="h-5 w-5"
|
|
:class="n <= Number(testimonial.rating) ? 'text-yellow-400' : 'text-gray-300'"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07
|
|
3.292a1 1 0 00.95.69h3.462c.969 0 1.371
|
|
1.24.588 1.81l-2.8 2.034a1 1 0
|
|
00-.364 1.118l1.07 3.292c.3.921-.755
|
|
1.688-1.54 1.118l-2.8-2.034a1 1
|
|
0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1
|
|
1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1
|
|
1 0 00.951-.69l1.07-3.292z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Pesan -->
|
|
<p class="mb-6 flex-grow text-gray-600 italic line-clamp-3 min-h-[72px] break-words">
|
|
"{{ testimonial.message }}"
|
|
</p>
|
|
|
|
<!-- User Info -->
|
|
<div>
|
|
<h4 class="font-bold text-gray-800">{{ testimonial.name }}</h4>
|
|
<p class="text-sm text-gray-500">{{ testimonial.city }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Render clone untuk seamless loop -->
|
|
<div
|
|
v-for="testimonial in testimonials"
|
|
:key="`clone-${testimonial.id}`"
|
|
class="testimonial-card"
|
|
@click="previewModal = testimonial"
|
|
>
|
|
<!-- Rating -->
|
|
<div class="mb-4 flex items-center">
|
|
<svg
|
|
v-for="n in 5"
|
|
:key="n"
|
|
class="h-5 w-5"
|
|
:class="n <= Number(testimonial.rating) ? 'text-yellow-400' : 'text-gray-300'"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07
|
|
3.292a1 1 0 00.95.69h3.462c.969 0 1.371
|
|
1.24.588 1.81l-2.8 2.034a1 1 0
|
|
00-.364 1.118l1.07 3.292c.3.921-.755
|
|
1.688-1.54 1.118l-2.8-2.034a1 1
|
|
0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1
|
|
1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1
|
|
1 0 00.951-.69l1.07-3.292z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Pesan -->
|
|
<p class="mb-6 flex-grow text-gray-600 italic line-clamp-3 min-h-[72px] break-words">
|
|
"{{ testimonial.message }}"
|
|
</p>
|
|
|
|
<!-- User Info -->
|
|
<div>
|
|
<h4 class="font-bold text-gray-800">{{ testimonial.name }}</h4>
|
|
<p class="text-sm text-gray-500">{{ testimonial.city }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tombol Berikan Ulasan -->
|
|
<button
|
|
@click="openModal = true"
|
|
class="px-6 py-3 rounded-lg bg-blue-500 text-white font-semibold shadow hover:bg-blue-700 transition"
|
|
>
|
|
Berikan Ulasan
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Modal Form -->
|
|
<div
|
|
v-if="openModal"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-800/30"
|
|
>
|
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-lg p-6 relative">
|
|
<button
|
|
@click="openModal = false"
|
|
class="absolute top-3 right-3 text-gray-500 hover:text-gray-800"
|
|
>
|
|
✕
|
|
</button>
|
|
|
|
<h3 class="text-xl font-bold mb-4 text-gray-800">Tulis Ulasan</h3>
|
|
|
|
<form @submit.prevent="submitReview">
|
|
<div class="mb-4 text-left">
|
|
<label class="block text-sm font-medium mb-1">Nama</label>
|
|
<input v-model="form.name" type="text" class="w-full rounded border px-3 py-2" required />
|
|
</div>
|
|
|
|
<div class="mb-4 text-left">
|
|
<label class="block text-sm font-medium mb-1">Kota</label>
|
|
<input v-model="form.city" type="text" class="w-full rounded border px-3 py-2" required />
|
|
</div>
|
|
|
|
<div class="mb-4 text-left">
|
|
<label class="block text-sm font-medium mb-1">Rating</label>
|
|
<select v-model="form.rating" class="w-full rounded border px-3 py-2" required>
|
|
<option value="">Pilih rating</option>
|
|
<option v-for="n in 5" :key="n" :value="n">{{ n }} ⭐</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-4 text-left">
|
|
<label class="block text-sm font-medium mb-1">Pesan</label>
|
|
<textarea v-model="form.message" class="w-full rounded border px-3 py-2" rows="3" required />
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
class="w-full bg-blue-600 text-white py-2 rounded-lg font-semibold hover:bg-blue-700 transition"
|
|
>
|
|
Kirim Ulasan
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Preview -->
|
|
<div
|
|
v-if="previewModal"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-800/30"
|
|
>
|
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-lg p-6 relative">
|
|
<button
|
|
@click="previewModal = null"
|
|
class="absolute top-3 right-3 text-gray-500 hover:text-gray-800"
|
|
>
|
|
✕
|
|
</button>
|
|
|
|
<h3 class="text-xl font-bold mb-4 text-gray-800">Ulasan Lengkap</h3>
|
|
<p class="text-gray-600 italic mb-4 whitespace-pre-line break-words">
|
|
"{{ previewModal.message }}"
|
|
</p>
|
|
<h4 class="font-bold text-gray-800">{{ previewModal.name }}</h4>
|
|
<p class="text-sm text-gray-500">{{ previewModal.city }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
|
|
const { data: testimonials, refresh } = await useFetch('http://localhost:8000/api/reviews')
|
|
|
|
const openModal = ref(false)
|
|
const previewModal = ref(null)
|
|
|
|
const form = ref({
|
|
name: '',
|
|
city: '',
|
|
rating: '',
|
|
message: ''
|
|
})
|
|
|
|
// Submit review
|
|
const submitReview = async () => {
|
|
try {
|
|
await $fetch('http://localhost:8000/api/reviews', {
|
|
method: 'POST',
|
|
body: form.value
|
|
})
|
|
form.value = { name: '', city: '', rating: '', message: '' }
|
|
openModal.value = false
|
|
await refresh()
|
|
} catch (err) {
|
|
console.error('Gagal simpan ulasan:', err)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Marquee Container */
|
|
.marquee-container {
|
|
overflow: hidden;
|
|
padding: 1rem 0;
|
|
}
|
|
|
|
/* Marquee Content - Contains all cards */
|
|
.marquee-content {
|
|
display: flex;
|
|
gap: 1.5rem;
|
|
animation: marquee calc(var(--total-cards) * 8s) linear infinite;
|
|
width: max-content;
|
|
}
|
|
|
|
/* Individual testimonial card */
|
|
.testimonial-card {
|
|
flex-shrink: 0;
|
|
width: 24rem; /* 384px = w-96 */
|
|
border-radius: 0.75rem;
|
|
background: white;
|
|
padding: 2rem;
|
|
text-align: left;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
transition: transform 300ms, box-shadow 300ms;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.testimonial-card:hover {
|
|
transform: translateY(-0.5rem);
|
|
box-shadow: 0 25px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
/* Marquee animation */
|
|
@keyframes marquee {
|
|
0% {
|
|
transform: translateX(0);
|
|
}
|
|
100% {
|
|
transform: translateX(-50%);
|
|
}
|
|
}
|
|
|
|
/* Pause animation on hover */
|
|
.marquee-container:hover .marquee-content {
|
|
animation-play-state: paused;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 640px) {
|
|
.testimonial-card {
|
|
width: 20rem; /* Smaller on mobile */
|
|
}
|
|
}
|
|
</style> |