add testimoni

This commit is contained in:
Muzakki Parsaoran Siregar 2025-09-11 14:26:39 +07:00
parent 74840a4fcb
commit d550571217
8 changed files with 248 additions and 49 deletions

View File

@ -12,10 +12,11 @@ class ReviewController extends Controller
public function index() public function index()
{ {
$reviews = Review::all(); $reviews = Review::all();
return view('admin.reviews.index', compact('reviews')); return response()->json($reviews, 200);
} }
// Simpan ulasan baru // Simpan ulasan baru
public function store(Request $request) public function store(Request $request)
{ {

View File

@ -7,7 +7,7 @@
<!-- Layout gambar + teks --> <!-- Layout gambar + teks -->
<div class="about-layout"> <div class="about-layout">
<div class="about-image"> <div class="about-image">
<img src="/logo1.png" alt="Tentang Kami - Undangan Digital" /> <img src="/rectangle.png" alt="Tentang Kami - Undangan Digital" />
</div> </div>
<div class="about-text"> <div class="about-text">
<p> <p>

View File

@ -2,7 +2,7 @@
<header class="main-header"> <header class="main-header">
<nav class="container"> <nav class="container">
<div class="logo"> <div class="logo">
<NuxtLink to="/" class="logo-link"> <img src="/ABBAUF.png" alt="Abbauf Tech Logo" class="logo-icon"> <NuxtLink to="/" class="logo-link"> <img src="/abbauflogo.png" alt="Abbauf Tech Logo" class="logo-icon">
<span>ABBAUF TECH</span> <span>ABBAUF TECH</span>
</NuxtLink> </NuxtLink>
</div> </div>
@ -58,7 +58,7 @@ const navLinks = ref([
} }
.logo-icon { .logo-icon {
width: 40px; width: 50px;
margin-right: 10px; margin-right: 10px;
} }

View File

@ -34,7 +34,7 @@
</div> </div>
<div class="w-full lg:w-1/2"> <div class="w-full lg:w-1/2">
<img src="/logo2.png" alt="Tampilan Undangan Digital di Ponsel" class="mx-auto max-w-full"> <img src="/iphone.png" alt="Tampilan Undangan Digital di Ponsel" class="mx-auto max-w-full">
<div class="mt-6 text-center italic text-gray-500"> <div class="mt-6 text-center italic text-gray-500">
<p>"Abadikan momen indahmu dengan undangan digital yang elegan."</p> <p>"Abadikan momen indahmu dengan undangan digital yang elegan."</p>
<p>"Satu aplikasi untuk semua momen spesialmu."</p> <p>"Satu aplikasi untuk semua momen spesialmu."</p>

View File

@ -4,66 +4,264 @@
<h2 class="text-4xl font-extrabold text-gray-800 mb-2"> <h2 class="text-4xl font-extrabold text-gray-800 mb-2">
Apa Kata Mereka? Apa Kata Mereka?
</h2> </h2>
<p class="text-lg text-gray-600 mb-16"> <p class="text-lg text-gray-600 mb-10">
Kisah sukses dari para pengguna yang telah mempercayakan momen spesialnya kepada kami. Kisah sukses dari para pengguna yang telah mempercayakan momen spesialnya kepada kami.
</p> </p>
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3"> <!-- CSS Marquee Scroll -->
<div class="marquee-container mb-10">
<div class="marquee-content" :style="{ '--total-cards': testimonials?.length || 0 }">
<!-- Render original cards -->
<div <div
v-for="testimonial in testimonials" v-for="testimonial in testimonials"
:key="testimonial.id" :key="`original-${testimonial.id}`"
class="flex flex-col rounded-xl bg-white p-8 text-left shadow-lg transition-transform duration-300 hover:-translate-y-2 hover:shadow-2xl" class="testimonial-card"
@click="previewModal = testimonial"
> >
<!-- Rating -->
<div class="mb-4 flex items-center"> <div class="mb-4 flex items-center">
<svg v-for="n in 5" :key="n" class="h-5 w-5" :class="n <= 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"></path></svg> <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> </div>
<p class="mb-6 flex-grow text-gray-600 italic">"{{ testimonial.text }}"</p> <!-- Pesan -->
<p class="mb-6 flex-grow text-gray-600 italic line-clamp-3 min-h-[72px] break-words">
"{{ testimonial.message }}"
</p>
<div class="flex items-center"> <!-- User Info -->
<img class="h-12 w-12 rounded-full object-cover" :src="testimonial.avatar" :alt="testimonial.name"> <div>
<div class="ml-4">
<h4 class="font-bold text-gray-800">{{ testimonial.name }}</h4> <h4 class="font-bold text-gray-800">{{ testimonial.name }}</h4>
<p class="text-sm text-gray-500">{{ testimonial.role }}</p> <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>
</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> </div>
</section> </section>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue'
const testimonials = ref([ const { data: testimonials, refresh } = await useFetch('http://localhost:8000/api/reviews')
{
id: 1, const openModal = ref(false)
name: 'Rizky & Anisa', const previewModal = ref(null)
role: 'Pengantin Baru',
avatar: 'https://i.pravatar.cc/100?u=rizky', const form = ref({
rating: 5, name: '',
text: 'Desainnya elegan dan modern! Proses pembuatannya juga cepat banget. Semua tamu memuji undangannya. Terima kasih Abbauf Tech!' city: '',
}, rating: '',
{ message: ''
id: 2, })
name: 'Budi Santoso',
role: 'Event Organizer', // Submit review
avatar: 'https://i.pravatar.cc/100?u=budi', const submitReview = async () => {
rating: 5, try {
text: 'Sebagai EO, kami butuh platform yang efisien dan hasilnya premium. Abbauf Tech menjawab semua kebutuhan itu. Klien kami sangat puas.' await $fetch('http://localhost:8000/api/reviews', {
}, method: 'POST',
{ body: form.value
id: 3, })
name: 'Citra Lestari', form.value = { name: '', city: '', rating: '', message: '' }
role: 'Ulang Tahun Anak', openModal.value = false
avatar: 'https://i.pravatar.cc/100?u=citra', await refresh()
rating: 4, } catch (err) {
text: 'Fitur RSVP dan pengingat sangat membantu. Tema-tema ulang tahunnya juga lucu dan bisa dikustomisasi. Sangat direkomendasikan!' console.error('Gagal simpan ulasan:', err)
}, }
]); }
</script> </script>
<style scoped> <style scoped>
/* Kosong, semua diatur oleh Tailwind */ /* 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> </style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB