This commit is contained in:
Farhaan4 2025-10-24 17:05:32 +07:00
parent 510651f7ed
commit 05343a5cb9
9 changed files with 1455 additions and 618 deletions

View File

@ -118,7 +118,7 @@ class GuestApiController extends Controller
// Ambil undangan berdasarkan invitation code // Ambil undangan berdasarkan invitation code
public function getByInvitationCode($code) public function getByInvitationCode($code)
{ {
$data = Guest::with('pelanggan.template') $data = Guest::with('pelanggan.template', 'pelanggan.rsvp')
->where('kode_invitasi', $code) ->where('kode_invitasi', $code)
->first(); ->first();

View File

@ -33,7 +33,7 @@ class RsvpController extends Controller
// 🔹 Ambil semua RSVP berdasarkan kode undangan (GET) // 🔹 Ambil semua RSVP berdasarkan kode undangan (GET)
public function show($invitationCode) public function show($invitationCode)
{ {
$pelanggan = Pelanggan::where('kode_invitasi', $invitationCode)->first(); $pelanggan = Pelanggan::where('invitation_code', $invitationCode)->first();
if (!$pelanggan) { if (!$pelanggan) {
return response()->json(['message' => 'Kode undangan tidak ditemukan'], 404); return response()->json(['message' => 'Kode undangan tidak ditemukan'], 404);

View File

@ -36,4 +36,17 @@ class Pelanggan extends Model
{ {
return $this->hasMany(Guest::class, 'id_pelanggan'); return $this->hasMany(Guest::class, 'id_pelanggan');
} }
public function rsvp()
{
return $this->hasManyThrough(
Rsvp::class,
Guest::class,
'id_pelanggan',
'guest_id',
'id',
'id'
);
}
} }

View File

@ -0,0 +1,532 @@
<!-- components/invitation/templates/wedding/WeddingA.vue -->
<template>
<div class="wedding-template-a min-h-screen bg-white">
<!-- Opening Cover -->
<section v-if="!isOpened" class="fixed inset-0 z-50 flex items-center justify-center bg-cover bg-center"
:style="{ backgroundImage: `url(${data.coverImage || '/wedding1.png'})` }">
<div class="absolute inset-0 bg-black/50"></div>
<div class="relative text-center text-white p-8">
<p class="text-xs uppercase tracking-widest mb-2 opacity-90">You are invited to the big day</p>
<p class="text-sm mb-6 opacity-80">We invited you to celebrate our wedding</p>
<p class="text-lg uppercase tracking-widest mb-4">THE WEDDING OF</p>
<h1 class="text-5xl md:text-7xl font-serif mb-8 italic" style="font-family: 'Great Vibes', cursive;">
{{ data.groom?.nickname || 'Asep' }} & {{ data.bride?.nickname || 'Putri' }}
</h1>
<p class="text-xl mb-8">{{ formatSimpleDate(data.eventDate) }}</p>
<button @click="openInvitation"
class="px-8 py-3 bg-transparent border-2 border-white text-white rounded-md font-medium hover:bg-white hover:text-gray-800 transition-all duration-300">
Open Invitation
</button>
</div>
</section>
<!-- Main Content -->
<div v-show="isOpened" class="animate-fade-in">
<!-- Hero Section -->
<section class="relative h-screen flex items-center justify-center overflow-hidden bg-cover bg-center"
:style="{ backgroundImage: `url(${data.heroImage || '/wedding1.png'})` }">
<div class="absolute inset-0 bg-black/40"></div>
<div class="relative text-center text-white px-4 z-10">
<div class="flex items-center justify-center gap-4 mb-6">
<div class="w-12 h-px bg-white"></div>
<Icon name="mdi:leaf" class="text-2xl" />
<div class="w-12 h-px bg-white"></div>
</div>
<h2 class="text-6xl md:text-8xl font-serif mb-6 italic" style="font-family: 'Great Vibes', cursive;">
{{ data.groom.nickname }} & {{ data.bride.nickname }}
</h2>
<p class="text-lg mb-6" data-aos="fade-up">
#{{ data.groom.nickname }}{{ data.bride.nickname }} #WishesAndWilderness #WeddedintheWoods
</p>
<p class="text-2xl font-light">{{ formatSimpleDate(data.eventDate) }}</p>
</div>
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 animate-bounce">
<Icon name="mdi:chevron-down" class="text-white text-3xl" />
</div>
</section>
<!-- Couple Section -->
<section class="py-20 bg-white">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-16 h-px bg-amber-400"></div>
<span class="text-amber-500 text-2xl">🌸</span>
<div class="w-16 h-px bg-amber-400"></div>
</div>
<h2 class="text-3xl md:text-4xl font-bold text-amber-500 mb-4">Meet The Happy Couple</h2>
<p class="text-gray-600 max-w-2xl mx-auto text-sm leading-relaxed">
Glory be to God, who has created beings in pairs. With God as guide, we invite you to share this
special day with us as we join together in marriage
</p>
</div>
<div class="grid md:grid-cols-2 gap-16 max-w-3xl mx-auto">
<!-- Groom -->
<div class="text-center" data-aos="fade-right">
<div class="relative inline-block mb-6">
<div class="w-36 h-36 md:w-44 md:h-44 rounded-full bg-white mx-auto overflow-hidden border-8 border-amber-100 shadow-lg">
<img :src="data.groom.photo || '/pria.jpg'" :alt="data.groom.fullname"
class="w-full h-full object-cover" />
</div>
</div>
<h3 class="text-3xl md:text-4xl font-bold text-amber-500 mb-2 italic" style="font-family: 'Great Vibes', cursive;">
{{ data.groom.nickname }}
</h3>
<p class="text-gray-800 font-semibold mb-1 text-sm">{{ data.groom.fullname }}</p>
<p class="text-xs text-gray-500 mb-4">
Son of Mr. {{ data.groom.fatherName }} & Mrs. {{ data.groom.motherName }}
</p>
<div class="flex justify-center space-x-2">
<a v-if="data.groom.instagram" :href="`https://instagram.com/${data.groom.instagram}`"
target="_blank" class="w-7 h-7 flex items-center justify-center bg-amber-400 text-white rounded-full hover:bg-amber-500 transition-colors">
<Icon name="mdi:instagram" class="text-sm" />
</a>
<a v-if="data.groom.twitter" :href="`https://twitter.com/${data.groom.twitter}`"
target="_blank" class="w-7 h-7 flex items-center justify-center bg-amber-400 text-white rounded-full hover:bg-amber-500 transition-colors">
<Icon name="mdi:twitter" class="text-sm" />
</a>
<a v-if="data.groom.facebook" :href="`https://facebook.com/${data.groom.facebook}`"
target="_blank" class="w-7 h-7 flex items-center justify-center bg-amber-400 text-white rounded-full hover:bg-amber-500 transition-colors">
<Icon name="mdi:facebook" class="text-sm" />
</a>
</div>
</div>
<!-- Bride -->
<div class="text-center" data-aos="fade-left">
<div class="relative inline-block mb-6">
<div class="w-36 h-36 md:w-44 md:h-44 rounded-full bg-white mx-auto overflow-hidden border-8 border-amber-100 shadow-lg">
<img :src="data.bride.photo || '/wanita.jpg'" :alt="data.bride.fullname"
class="w-full h-full object-cover" />
</div>
</div>
<h3 class="text-3xl md:text-4xl font-bold text-amber-500 mb-2 italic" style="font-family: 'Great Vibes', cursive;">
{{ data.bride.nickname }}
</h3>
<p class="text-gray-800 font-semibold mb-1 text-sm">{{ data.bride.fullname }}</p>
<p class="text-xs text-gray-500 mb-4">
Daughter of Mr. {{ data.bride.fatherName }} & Mrs. {{ data.bride.motherName }}
</p>
<div class="flex justify-center space-x-2">
<a v-if="data.bride.instagram" :href="`https://instagram.com/${data.bride.instagram}`"
target="_blank" class="w-7 h-7 flex items-center justify-center bg-amber-400 text-white rounded-full hover:bg-amber-500 transition-colors">
<Icon name="mdi:instagram" class="text-sm" />
</a>
<a v-if="data.bride.twitter" :href="`https://twitter.com/${data.bride.twitter}`"
target="_blank" class="w-7 h-7 flex items-center justify-center bg-amber-400 text-white rounded-full hover:bg-amber-500 transition-colors">
<Icon name="mdi:twitter" class="text-sm" />
</a>
<a v-if="data.bride.facebook" :href="`https://facebook.com/${data.bride.facebook}`"
target="_blank" class="w-7 h-7 flex items-center justify-center bg-amber-400 text-white rounded-full hover:bg-amber-500 transition-colors">
<Icon name="mdi:facebook" class="text-sm" />
</a>
</div>
</div>
</div>
<!-- Decorative Line with Leaf -->
<div class="flex items-center justify-center gap-3 mt-12 mb-8">
<div class="w-20 h-px bg-amber-300"></div>
<span class="text-amber-400 text-xl">🌸</span>
<div class="w-20 h-px bg-amber-300"></div>
</div>
<!-- Our Story Button -->
<div class="text-center">
<button @click="showStory = !showStory"
class="bg-amber-400 hover:bg-amber-500 text-white px-10 py-3 rounded-full font-semibold transition-colors shadow-lg text-sm">
{{ showStory ? 'Hide Our Story' : 'Our Story' }}
</button>
</div>
</div>
</section>
<!-- Our Story Section (Toggle) -->
<section v-if="showStory" class="py-20 bg-white">
<div class="max-w-4xl mx-auto px-6">
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-16 h-px bg-amber-400"></div>
<Icon name="mdi:leaf" class="text-amber-500 text-2xl" />
<div class="w-16 h-px bg-amber-400"></div>
</div>
<h2 class="text-4xl md:text-5xl font-bold text-amber-600 mb-4">Our Story</h2>
</div>
<div class="grid md:grid-cols-2 gap-8">
<!-- Story with Image -->
<div data-aos="fade-right">
<img :src="data.story?.image1 || '/wedding1.png'" alt="Our Story" class="w-full h-80 object-cover rounded-lg shadow-lg mb-4" />
<p class="text-gray-700 leading-relaxed text-sm">
{{ data.story?.text1 || 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.' }}
</p>
</div>
<div data-aos="fade-left">
<p class="text-gray-700 leading-relaxed text-sm mb-4">
{{ data.story?.text2 || 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.' }}
</p>
<img :src="data.story?.image2 || '/wedding1.png'" alt="Our Story" class="w-full h-80 object-cover rounded-lg shadow-lg" />
</div>
</div>
</div>
</section>
<!-- Event Details -->
<section class="py-20 bg-gradient-to-b from-amber-50 to-white">
<div class="max-w-5xl mx-auto px-6">
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-16 h-px bg-amber-400"></div>
<Icon name="mdi:leaf" class="text-amber-500 text-2xl" />
<div class="w-16 h-px bg-amber-400"></div>
</div>
<h2 class="text-4xl md:text-5xl font-bold text-amber-600 mb-4">We Are Getting Married</h2>
<p class="text-3xl font-serif italic mb-4" style="font-family: 'Great Vibes', cursive;">
#{{ data.groom.nickname }}And{{ data.bride.nickname }} #WishesAndWilderness #WeddedintheWoods
</p>
<p class="text-xl text-gray-700">{{ formatSimpleDate(data.eventDate) }}</p>
</div>
<!-- Countdown -->
<div class="mb-12">
<CountdownTimer :targetDate="data.eventDate" />
</div>
<!-- Event Cards -->
<div class="text-center mb-12">
<div class="flex items-center justify-center gap-3 mb-6">
<div class="w-12 h-px bg-amber-400"></div>
<Icon name="mdi:leaf" class="text-amber-500 text-xl" />
<div class="w-12 h-px bg-amber-400"></div>
</div>
<h3 class="text-3xl font-bold text-amber-600">Our Wedding Celebration</h3>
</div>
<div class="grid md:grid-cols-2 gap-6 max-w-3xl mx-auto">
<!-- Wedding Ceremony -->
<div class="relative bg-cover bg-center h-64 rounded-lg overflow-hidden shadow-xl"
:style="{ backgroundImage: `url(${data.akad.image || '/wedding1.png'})` }" data-aos="fade-right">
<div class="absolute inset-0 bg-gradient-to-b from-black/40 to-black/70"></div>
<div class="relative h-full flex flex-col justify-center items-center text-white p-6">
<Icon name="mdi:ring" class="text-4xl mb-3" />
<h4 class="text-2xl font-bold mb-2">Wedding Ceremony</h4>
<p class="text-sm mb-1">{{ formatSimpleDate(data.akad.date) }}</p>
<p class="text-sm mb-1">{{ data.akad.time }}</p>
<p class="text-sm text-center">{{ data.akad.place }}</p>
</div>
</div>
<!-- Wedding Party -->
<div class="relative bg-cover bg-center h-64 rounded-lg overflow-hidden shadow-xl"
:style="{ backgroundImage: `url(${data.resepsi.image || '/wedding1.png'})` }" data-aos="fade-left">
<div class="absolute inset-0 bg-gradient-to-b from-black/40 to-black/70"></div>
<div class="relative h-full flex flex-col justify-center items-center text-white p-6">
<Icon name="mdi:party-popper" class="text-4xl mb-3" />
<h4 class="text-2xl font-bold mb-2">Wedding Party</h4>
<p class="text-sm mb-1">{{ formatSimpleDate(data.resepsi.date) }}</p>
<p class="text-sm mb-1">{{ data.resepsi.time }}</p>
<p class="text-sm text-center">{{ data.resepsi.place }}</p>
</div>
</div>
</div>
<!-- Maps -->
<div class="mt-12 grid md:grid-cols-2 gap-6 max-w-3xl mx-auto">
<div data-aos="fade-up">
<Maps :location="data.akad.mapUrl" />
</div>
<div data-aos="fade-up">
<Maps :location="data.resepsi.mapUrl" />
</div>
</div>
</div>
</section>
<!-- Gallery -->
<section class="py-20 bg-white">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-16 h-px bg-amber-400"></div>
<Icon name="mdi:leaf" class="text-amber-500 text-2xl" />
<div class="w-16 h-px bg-amber-400"></div>
</div>
<h2 class="text-4xl md:text-5xl font-bold text-amber-600 mb-4">Gallery</h2>
</div>
<Gallery :images="data.gallery" />
<p class="text-center text-sm text-gray-600 mt-8 max-w-2xl mx-auto">
Thank you to everyone who helped make our special day so wonderful. We are blessed to have you celebrate with us and wish there
could be more smiles, laughs, good food, and company we could share. Truly blessed.
</p>
</div>
</section>
<!-- Gift Section -->
<section class="py-20 bg-gradient-to-b from-amber-50 to-white">
<div class="max-w-4xl mx-auto px-6">
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-16 h-px bg-amber-400"></div>
<Icon name="mdi:leaf" class="text-amber-500 text-2xl" />
<div class="w-16 h-px bg-amber-400"></div>
</div>
<h2 class="text-4xl md:text-5xl font-bold text-amber-600 mb-4">Give a Gift</h2>
<p class="text-gray-600 text-sm">
Your love, laughter, and company on our wedding day is the greatest gift of all. However if you wish
to honor us with a gift, we will gracefully accept it. Thank you!
</p>
</div>
<div class="grid md:grid-cols-2 gap-6">
<!-- Digital Wallet -->
<div class="bg-white rounded-xl shadow-lg p-8 border border-amber-100" data-aos="fade-right">
<h3 class="text-xl font-bold text-amber-600 mb-6">Digital Wallet</h3>
<p class="text-xs text-gray-500 mb-6">Tap card number to copy</p>
<div class="space-y-4">
<div v-for="(account, index) in digitalWallets" :key="index">
<div class="flex items-center gap-2 mb-2">
<span class="font-semibold text-gray-800">{{ account.name }}</span>
<img :src="account.logo" :alt="account.bank" class="h-5" />
</div>
<div class="flex items-center bg-amber-50 rounded-lg p-3 border border-amber-200 cursor-pointer hover:bg-amber-100 transition-colors"
@click="copyToClipboard(account.number)">
<span class="flex-1 text-gray-700 font-mono text-sm">{{ account.number }}</span>
<Icon name="mdi:content-copy" class="text-amber-600 text-lg" />
</div>
</div>
</div>
</div>
<!-- Offline Gift -->
<div class="bg-white rounded-xl shadow-lg p-8 border border-amber-100" data-aos="fade-left">
<h3 class="text-xl font-bold text-amber-600 mb-6">Offline Gift</h3>
<p class="text-gray-700 text-sm mb-6">
{{ data.gift?.address || 'Jl. Terusan Jakarta No.53, Cicaheum, Kec. Kiaracondong, Kota Bandung, Jawa Barat 40291' }}
</p>
<a :href="data.gift?.mapUrl || 'https://maps.google.com'" target="_blank"
class="flex items-center justify-center gap-2 w-full bg-amber-500 hover:bg-amber-600 text-white px-6 py-3 rounded-lg transition-colors shadow-md">
<Icon name="mdi:map-marker" class="text-xl" />
<span>Open Map</span>
</a>
</div>
</div>
</div>
</section>
<!-- RSVP Section -->
<section class="py-20 bg-white">
<div class="max-w-2xl mx-auto px-6">
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-16 h-px bg-amber-400"></div>
<Icon name="mdi:leaf" class="text-amber-500 text-2xl" />
<div class="w-16 h-px bg-amber-400"></div>
</div>
<h2 class="text-4xl md:text-5xl font-bold text-amber-600 mb-4">Say Something!</h2>
</div>
<RSVP :invitationId="data.id" @submitted="handleRSVPSubmit" />
</div>
</section>
<!-- Footer -->
<footer class="relative py-20 bg-cover bg-center" :style="{ backgroundImage: `url(${data.footerImage || '/wedding1.png'})` }">
<div class="absolute inset-0 bg-gradient-to-b from-black/60 to-black/80"></div>
<div class="relative z-10 text-center text-white px-6">
<div class="mb-8">
<Icon name="mdi:heart" class="text-5xl mb-6" />
</div>
<div class="flex items-center justify-center gap-6 mb-8">
<div class="px-8 py-4 bg-white/20 backdrop-blur-sm rounded-lg border border-white/30">
<p class="text-3xl font-serif italic" style="font-family: 'Great Vibes', cursive;">{{ data.groom.nickname }}</p>
</div>
<span class="text-2xl">-</span>
<div class="px-8 py-4 bg-white/20 backdrop-blur-sm rounded-lg border border-white/30">
<p class="text-3xl font-serif italic" style="font-family: 'Great Vibes', cursive;">{{ data.bride.nickname }}</p>
</div>
</div>
<Icon name="mdi:flower" class="text-3xl mb-6" />
<img src="/ABBAUF.png" alt="Logo" class="h-12 mx-auto mb-4 opacity-90" />
<p class="text-sm opacity-70">© 2024 All rights reserved</p>
</div>
</footer>
<!-- Music Player -->
<MusicPlayer v-if="data.musicUrl" :url="data.musicUrl" :autoplay="true" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import AOS from 'aos'
import 'aos/dist/aos.css'
import CountdownTimer from '~/components/templates/wedding/CountdownTimer.vue'
import Gallery from '~/components/templates/wedding/Gallery.vue'
import Maps from '~/components/templates/wedding/Maps.vue'
import RSVP from '~/components/templates/wedding/RSVP.vue'
import MusicPlayer from '~/components/templates/wedding/MusicPlayer.vue'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({
id: '',
coverImage: '',
heroImage: '',
footerImage: '',
greeting: '',
eventDate: new Date(),
bride: {
fullname: '',
nickname: '',
photo: '',
fatherName: '',
motherName: '',
instagram: '',
twitter: '',
facebook: ''
},
groom: {
fullname: '',
nickname: '',
photo: '',
fatherName: '',
motherName: '',
instagram: '',
twitter: '',
facebook: ''
},
akad: {
date: new Date(),
time: '',
place: '',
address: '',
mapUrl: '',
image: ''
},
resepsi: {
date: new Date(),
time: '',
place: '',
address: '',
mapUrl: '',
image: ''
},
story: {
image1: '',
text1: '',
image2: '',
text2: ''
},
gallery: [],
gift: {
address: '',
mapUrl: ''
},
musicUrl: ''
})
}
})
const isOpened = ref(false)
const showStory = ref(false)
const guestMessages = ref([])
const digitalWallets = ref([
{ name: 'Asep Irawan', bank: 'BNI', number: '009 - 0222 2444 21', logo: '/logo1.png' },
{ name: 'Putri Amanda', bank: 'BCA', number: '009 - 0222 2444 21', logo: '/logo2.png' }
])
const openInvitation = () => {
isOpened.value = true
}
const formatSimpleDate = (date) => {
if (!date) return ''
const d = new Date(date)
const day = d.getDate().toString().padStart(2, '0')
const month = d.getMonth() + 1
const year = d.getFullYear()
return `${day}.${month.toString().padStart(2, '0')}.${year}`
}
const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text)
alert('Account number copied!')
} catch (err) {
console.error('Failed to copy:', err)
}
}
const handleRSVPSubmit = (data) => {
console.log('RSVP submitted:', data)
if (data.message) {
guestMessages.value.unshift({
name: data.name,
message: data.message,
attendance: data.attendance,
createdAt: new Date()
})
}
}
onMounted(() => {
AOS.init({
duration: 1000,
once: true,
offset: 100,
easing: 'ease-out-cubic'
})
})
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap');
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fade-in 1s ease-out;
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #fef3c7;
}
::-webkit-scrollbar-thumb {
background: #f59e0b;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #d97706;
}
</style>

View File

@ -1,58 +1,197 @@
<template> <template>
<section class="bg-[#FFF8EC] py-12 text-center"> <section class="bg-transparent py-12 px-4">
<!-- Title --> <!-- Gallery Grid - Masonry Style -->
<div class="mb-8"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-6xl mx-auto px-4" data-aos="fade-up">
<div class="flex items-center justify-center gap-2 mb-2"> <img
v-for="(img, index) in displayImages"
<span class="h-[1px] w-12 bg-yellow-400"></span> :key="index"
<span class="text-yellow-600 text-2xl">🌸</span> :src="img"
<span class="h-[1px] w-12 bg-yellow-400"></span> alt="Gallery photo"
</div> class="rounded-lg object-cover w-full shadow-lg hover:shadow-xl transition-shadow duration-300 cursor-pointer"
<h2 class="text-2xl md:text-3xl font-bold text-yellow-700">Gallery</h2> :class="getGridClass(index)"
@click="openLightbox(index)"
/>
</div> </div>
<!-- Gallery Grid --> <!-- Quote Section -->
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 max-w-4xl mx-auto px-4"> <div class="max-w-3xl mx-auto mt-12 px-6 text-center" data-aos="fade-up" data-aos-delay="200">
<img v-for="(img, index) in images" :key="index" :src="img" alt="gallery" <p class="text-gray-600 text-sm md:text-base leading-relaxed">
class="rounded-lg object-cover w-full h-full shadow-md" :class="getGridClass(index)" /> "And among His Signs is this, that He created for you mates from among yourselves,
</div> that ye may dwell in tranquility with them, and He has put love and mercy between your (hearts):
verily in that are Signs for those who reflect."
<!-- Quote -->
<div class="max-w-3xl mx-auto mt-8 px-6">
<p class="text-gray-600 italic">
"And among His verses is that He has created for you wives of your own kind,
so that you may feel comfortable in them, and He has made between you mawadah and mercy.
Verily in that are signs for the people who think"
</p> </p>
<p class="mt-4 text-gray-700 font-semibold">- AR-RUM 21 -</p> <p class="mt-4 text-amber-600 font-semibold text-sm">- QS. Ar-Rum: 21 -</p>
</div> </div>
<!-- Bottom Decoration --> <!-- Lightbox Modal -->
<div class="flex items-center justify-center gap-2 mt-6"> <Teleport to="body">
<span class="h-[1px] w-12 bg-yellow-400"></span> <div
<span class="text-yellow-600 text-2xl">🌸</span> v-if="lightboxOpen"
<span class="h-[1px] w-12 bg-yellow-400"></span> class="fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-4"
</div> @click="closeLightbox"
>
<button
class="absolute top-4 right-4 text-white text-4xl hover:text-amber-400 transition-colors"
@click="closeLightbox"
>
&times;
</button>
<button
v-if="currentImageIndex > 0"
class="absolute left-4 text-white text-4xl hover:text-amber-400 transition-colors"
@click.stop="prevImage"
>
</button>
<img
:src="displayImages[currentImageIndex]"
class="max-w-full max-h-[90vh] object-contain rounded-lg"
@click.stop
/>
<button
v-if="currentImageIndex < displayImages.length - 1"
class="absolute right-4 text-white text-4xl hover:text-amber-400 transition-colors"
@click.stop="nextImage"
>
</button>
<div class="absolute bottom-4 text-white text-sm">
{{ currentImageIndex + 1 }} / {{ displayImages.length }}
</div>
</div>
</Teleport>
</section> </section>
</template> </template>
<script setup> <script setup>
const images = [ import { ref, computed } from 'vue'
"/logo1.png", // kiri atas tinggi
"/logo2.png", // atas tengah
"/pria.jpg", // atas kanan
"/wanita.jpg", // bawah kiri
"/iphone.png", // bawah tengah lebar
"/templat.jpg" // bawah kanan
]
// kasih ukuran custom seperti masonry const props = defineProps({
images: {
type: Array,
default: () => [
"/logo1.png",
"/logo2.png",
"/pria.jpg",
"/wanita.jpg",
"/iphone.png",
"/templat.jpg",
"/logo1.png",
"/logo2.png"
]
}
})
const lightboxOpen = ref(false)
const currentImageIndex = ref(0)
const displayImages = computed(() => {
const images = props.images && props.images.length > 0 ? props.images : [
"/images/logo1.png",
"/images/logo2.png",
"/images/pria.jpg",
"/images/wanita.jpg",
"/images/iphone.png",
"/images/templat.jpg",
"/images/logo1.png",
"/images/logo2.png"
]
console.log('displayImages:', images)
return images
})
// Masonry grid layout pattern
const getGridClass = (index) => { const getGridClass = (index) => {
switch (index) { const pattern = index % 8
case 0: return "row-span-2 h-[400px]" // tinggi besar
case 4: return "col-span-2 h-[250px]" // lebar besar switch (pattern) {
case 3: return "col-span-2 h-[250px]" // melebar case 0:
default: return "h-[200px]" return "col-span-1 row-span-2 h-[400px]" // Tall left
case 1:
return "col-span-1 h-[195px]" // Small top right
case 2:
return "col-span-1 h-[195px]" // Small middle
case 3:
return "col-span-2 h-[250px]" // Wide bottom
case 4:
return "col-span-1 h-[200px]" // Standard
case 5:
return "col-span-1 row-span-2 h-[400px]" // Tall right
case 6:
return "col-span-2 h-[195px]" // Wide top
case 7:
return "col-span-1 h-[200px]" // Standard
default:
return "h-[200px]"
} }
} }
const openLightbox = (index) => {
currentImageIndex.value = index
lightboxOpen.value = true
document.body.style.overflow = 'hidden'
}
const closeLightbox = () => {
lightboxOpen.value = false
document.body.style.overflow = ''
}
const nextImage = () => {
if (currentImageIndex.value < displayImages.value.length - 1) {
currentImageIndex.value++
}
}
const prevImage = () => {
if (currentImageIndex.value > 0) {
currentImageIndex.value--
}
}
// Keyboard navigation
const handleKeydown = (e) => {
if (!lightboxOpen.value) return
if (e.key === 'Escape') closeLightbox()
if (e.key === 'ArrowRight') nextImage()
if (e.key === 'ArrowLeft') prevImage()
}
if (typeof window !== 'undefined') {
window.addEventListener('keydown', handleKeydown)
}
</script> </script>
<style scoped>
/* Smooth transitions */
img {
transition: transform 0.3s ease, shadow 0.3s ease;
}
img:hover {
transform: scale(1.02);
}
/* Custom scrollbar for lightbox if needed */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #f59e0b;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #d97706;
}
</style>

View File

@ -1,63 +1,153 @@
<template> <template>
<section class="bg-[#FFF8EC] py-12 px-4"> <section class="bg-gradient-to-b from-amber-50 to-white py-16 px-4 md:px-6 lg:px-8">
<!-- Judul --> <!-- Full-width container -->
<div class="flex items-center justify-center gap-2 mb-8"> <div class="w-full">
<span class="h-[1px] w-12 bg-yellow-400"></span> <!-- Judul (tetap terpusat) -->
<span class="text-yellow-600 text-2xl">💌</span> <div class="text-center mb-12">
<span class="h-[1px] w-12 bg-yellow-400"></span> <div class="flex items-center justify-center gap-2 mb-6">
</div> <span class="h-[1px] w-12 bg-amber-400"></span>
<h2 class="text-2xl md:text-3xl font-bold text-yellow-600 text-center mb-10"> <span class="text-amber-500 text-2xl">Flower</span>
Guest Book <span class="h-[1px] w-12 bg-amber-400"></span>
</h2> </div>
<h2 class="text-3xl md:text-4xl font-bold text-amber-600">
<!-- Layout Form + Ucapan --> Guest Book
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto"> </h2>
<!-- Form -->
<div class="bg-white shadow-lg rounded-2xl p-6">
<h3 class="text-xl font-semibold text-yellow-600 mb-4">Say Something!</h3>
<form class="space-y-4">
<!-- Nama -->
<input
type="text"
placeholder="Your Name"
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-yellow-400 focus:outline-none"
/>
<!-- Ucapan -->
<textarea
rows="3"
placeholder="Write your message..."
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-yellow-400 focus:outline-none"
></textarea>
<!-- Tombol -->
<button
type="submit"
class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200"
>
Send Now!
</button>
</form>
</div> </div>
<!-- Daftar Ucapan --> <!-- Layout Form + Ucapan (konten terpusat) -->
<div class="bg-white shadow-lg rounded-2xl p-6"> <div class="max-w-5xl mx-auto">
<div class="flex items-center gap-2 mb-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
<span class="text-gray-500">💬</span>
<span class="text-gray-700 font-medium">04 Messages</span> <!-- Form -->
</div> <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>
<!-- List Ucapan --> <form @submit.prevent="handleSubmit" class="space-y-5">
<div class="space-y-3"> <!-- Nama -->
<div class="p-3 bg-gray-100 rounded-lg"> <div>
<p class="font-semibold text-gray-800">Tia SMAN6BDG</p> <label class="block text-sm font-medium text-gray-700 mb-2">Your Name</label>
<p class="text-sm text-gray-600">Congrats!!</p> <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> </div>
<div class="p-3 bg-gray-100 rounded-lg"> <!-- Daftar Ucapan -->
<p class="font-semibold text-gray-800">Muthia Rahma</p> <div
<p class="text-sm text-gray-600">Happy wedd my sisstaa!</p> 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>
</div> </div>
@ -66,5 +156,88 @@
</template> </template>
<script setup> <script setup>
// nanti tinggal ganti data dummy jadi fetch API 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> </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>

View File

@ -1,15 +1,19 @@
<template> <template>
<section class="bg-[#FFF8EC] py-12 px-4"> <section class="bg-transparent py-12 px-4">
<!-- Title --> <!-- Title -->
<div class="text-center mb-12" data-aos="fade-up"> <div class="text-center mb-16" data-aos="fade-up">
<h2 class="text-3xl md:text-4xl font-serif text-rose-800 mb-4">Ucapan</h2> <div class="flex items-center justify-center gap-3 mb-4">
<p class="text-gray-600">Berikan ucapan & doa restu untuk kami</p> <div class="w-450 h-px bg-amber-400"></div>
</div> <Icon name="mdi:leaf" class="text-amber-500 text-2xl" />
<div class="w-450 h-px bg-amber-400"></div>
</div>
<h2 class="text-4xl md:text-5xl font-bold text-amber-600 mb-4">Say Something</h2>
</div>
<!-- Layout --> <!-- Layout -->
<div class="max-w-4xl mx-auto grid md:grid-cols-2 gap-8"> <div class="max-w-4xl mx-auto grid md:grid-cols-2 gap-8">
<!-- Left Form --> <!-- Left Form -->
<div class="bg-white rounded-2xl shadow-lg p-6"> <div class="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
<h3 class="text-2xl font-bold text-yellow-500 mb-4">Say Something!</h3> <h3 class="text-2xl font-bold text-yellow-500 mb-4">Say Something!</h3>
<form @submit.prevent="handleSubmit" class="space-y-4"> <form @submit.prevent="handleSubmit" class="space-y-4">
@ -18,7 +22,8 @@
v-model="form.name" v-model="form.name"
type="text" type="text"
placeholder="Name" placeholder="Name"
class="w-full p-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-yellow-400" required
class="w-full p-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-yellow-400 transition-all"
/> />
<!-- Message --> <!-- Message -->
@ -26,7 +31,8 @@
v-model="form.message" v-model="form.message"
placeholder="Message" placeholder="Message"
rows="3" rows="3"
class="w-full p-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-yellow-400" required
class="w-full p-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-yellow-400 transition-all resize-none"
></textarea> ></textarea>
<!-- Attendance --> <!-- Attendance -->
@ -60,7 +66,8 @@
<!-- Submit --> <!-- Submit -->
<button <button
type="submit" type="submit"
class="w-full bg-yellow-400 text-white py-3 rounded-full font-semibold shadow hover:bg-yellow-500 transition" :disabled="!form.name || !form.message || !form.attendance"
class="w-full bg-yellow-400 hover:bg-yellow-500 text-white py-3 rounded-full font-semibold shadow transition disabled:opacity-50 disabled:cursor-not-allowed"
> >
Send Now! Send Now!
</button> </button>
@ -68,28 +75,36 @@
</div> </div>
<!-- Right Comments --> <!-- Right Comments -->
<div class="bg-white rounded-2xl shadow-lg p-6"> <div class="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
<div class="flex items-center gap-2 mb-4 text-gray-600 font-medium"> <div class="flex items-center gap-2 mb-4 text-gray-600 font-medium">
<span class="text-xl">💬</span> <span class="text-xl">Chat</span>
<span>{{ comments.length }}</span> <span>{{ comments.length }}</span>
</div> </div>
<div v-for="(c, i) in comments" :key="i" class="mb-4"> <div class="space-y-4">
<div class="flex items-center justify-between mb-1"> <div v-for="(c, i) in comments" :key="i">
<span class="font-semibold text-gray-800">{{ c.name }}</span> <div class="flex items-center justify-between mb-1">
<span <span class="font-semibold text-gray-800">{{ c.name }}</span>
class="px-3 py-1 text-xs font-medium rounded-full" <span
:class="{ class="px-3 py-1 text-xs font-medium rounded-full"
'bg-green-100 text-green-600': c.attendance === 'Yes', :class="{
'bg-yellow-100 text-yellow-600': c.attendance === 'Maybe', 'bg-green-100 text-green-600': c.attendance === 'Yes',
'bg-gray-200 text-gray-600': c.attendance === 'No' 'bg-yellow-100 text-yellow-600': c.attendance === 'Maybe',
}" 'bg-gray-200 text-gray-600': c.attendance === 'No'
> }"
{{ c.attendance }} >
</span> {{ c.attendance }}
</span>
</div>
<p class="text-gray-600 text-sm">{{ c.message }}</p>
<hr class="mt-3 border-gray-200" />
</div>
<!-- Empty State -->
<div v-if="comments.length === 0" class="text-center py-8 text-gray-400">
<span class="text-5xl">Empty</span>
<p class="text-sm mt-2">Belum ada ucapan. Jadilah yang pertama!</p>
</div> </div>
<p class="text-gray-600 text-sm">{{ c.message }}</p>
<hr class="mt-3" />
</div> </div>
</div> </div>
</div> </div>
@ -128,3 +143,9 @@ const attendanceClass = (val) => {
]; ];
}; };
</script> </script>
<style scoped>
.space-y-4 > div:last-child hr {
display: none;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,9 @@
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'
import WeddingA from '~/components/templates/wedding/weddingA.vue' import WeddingA from '~/components/templates/wedding/weddingA.vue'
// Props dari parent (/p/[code].vue)
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object, type: Object,
@ -19,51 +19,75 @@ const props = defineProps({
} }
}) })
// Format data dari backend agar cocok dengan struktur WeddingA.vue
const formattedData = computed(() => { const formattedData = computed(() => {
const f = props.data.form || {} const f = props.data.form || {}
return { return {
id: props.data.id, id: props.data.id || '',
coverImage: f.cover_image || '/default-cover.jpg', coverImage: f.cover_image ? `http://localhost:8000/storage/${f.cover_image}` : '/default-cover.jpg',
heroImage: f.hero_image || '/default-hero.jpg', heroImage: f.hero_image ? `http://localhost:8000/storage/${f.hero_image}` : '/default-hero.jpg',
footerImage: f.footer_image ? `http://localhost:8000/storage/${f.footer_image}` : '/wedding1.png',
greeting: f.say_something || 'Dengan memohon rahmat dan ridho Allah SWT, kami bermaksud mengundang Anda...', greeting: f.say_something || 'Dengan memohon rahmat dan ridho Allah SWT, kami bermaksud mengundang Anda...',
eventDate: f.hari_tanggal_resepsi, eventDate: f.hari_tanggal_resepsi || new Date(),
bride: { bride: {
fullname: f.nama_lengkap_wanita, fullname: f.nama_lengkap_wanita || '',
nickname: f.nama_panggilan_wanita, nickname: f.nama_panggilan_wanita || '',
photo: f.foto_wanita ? `http://localhost:8000/storage/${f.foto_wanita}` : '/wanita.jpg', photo: f.foto_wanita ? `http://localhost:8000/storage/${f.foto_wanita}` : '/wanita.jpg',
fatherName: f.nama_ayah_wanita || f.nama_bapak_wanita, // fleksibel fatherName: f.nama_ayah_wanita || f.nama_bapak_wanita || '',
motherName: f.nama_ibu_wanita, motherName: f.nama_ibu_wanita || '',
instagram: f.instagram_wanita || '' instagram: f.instagram_wanita || '',
twitter: f.twitter_wanita || '',
facebook: f.facebook_wanita || ''
}, },
groom: { groom: {
fullname: f.nama_lengkap_pria, fullname: f.nama_lengkap_pria || '',
nickname: f.nama_panggilan_pria, nickname: f.nama_panggilan_pria || '',
photo: f.foto_pria ? `http://localhost:8000/storage/${f.foto_pria}` : '/pria.jpg', photo: f.foto_pria ? `http://localhost:8000/storage/${f.foto_pria}` : '/pria.jpg',
fatherName: f.nama_ayah_pria || f.nama_bapak_pria, // fleksibel fatherName: f.nama_ayah_pria || f.nama_bapak_pria || '',
motherName: f.nama_ibu_pria, motherName: f.nama_ibu_pria || '',
instagram: f.instagram_pria || '' instagram: f.instagram_pria || '',
twitter: f.twitter_pria || '',
facebook: f.facebook_pria || ''
}, },
akad: { akad: {
date: f.hari_tanggal_akad, date: f.hari_tanggal_akad || new Date(),
time: f.waktu_akad, time: f.waktu_akad || '',
place: f.tempat_akad, place: f.tempat_akad || '',
address: f.alamat_akad, address: f.alamat_akad || '',
mapUrl: f.link_gmaps_akad mapUrl: f.link_gmaps_akad || '',
image: f.akad_image ? `http://localhost:8000/storage/${f.akad_image}` : '/wedding1.png'
}, },
resepsi: { resepsi: {
date: f.hari_tanggal_resepsi, date: f.hari_tanggal_resepsi || new Date(),
time: f.waktu_resepsi, time: f.waktu_resepsi || '',
place: f.tempat_resepsi, place: f.tempat_resepsi || '',
address: f.alamat_resepsi, address: f.alamat_resepsi || '',
mapUrl: f.link_gmaps_resepsi mapUrl: f.link_gmaps_resepsi || '',
image: f.resepsi_image ? `http://localhost:8000/storage/${f.resepsi_image}` : '/wedding1.png'
}, },
gallery: [1, 2, 3, 4, 5, 6] story: {
image1: f.story_image1 ? `http://localhost:8000/storage/${f.story_image1}` : '/wedding1.png',
text1: f.story_text1 || 'Lorem ipsum dolor sit amet...',
image2: f.story_image2 ? `http://localhost:8000/storage/${f.story_image2}` : '/wedding1.png',
text2: f.story_text2 || 'Lorem ipsum dolor sit amet...'
},
gallery: [1, 2, 3, 4, 5, 6, 7, 8]
.map(i => f[`foto_${i}`]) .map(i => f[`foto_${i}`])
.filter(Boolean) .filter(Boolean)
.map(x => `http://localhost:8000/storage/${x}`), .map(x => `http://localhost:8000/storage/${x}`),
musicUrl: f.link_music || '' gift: {
address: f.gift_address || 'Jl. Terusan Jakarta No.53, Cicaheum, Kec. Kiaracondong, Kota Bandung, Jawa Barat 40291',
mapUrl: f.gift_map_url || 'https://maps.google.com'
},
musicUrl: f.link_music || '',
foto_1: f.foto_1 ? `http://localhost:8000/storage/${f.foto_1}` : '',
foto_2: f.foto_2 ? `http://localhost:8000/storage/${f.foto_2}` : '',
foto_3: f.foto_3 ? `http://localhost:8000/storage/${f.foto_3}` : '',
foto_4: f.foto_4 ? `http://localhost:8000/storage/${f.foto_4}` : '',
foto_5: f.foto_5 ? `http://localhost:8000/storage/${f.foto_5}` : '',
foto_6: f.foto_6 ? `http://localhost:8000/storage/${f.foto_6}` : '',
foto_7: f.foto_7 ? `http://localhost:8000/storage/${f.foto_7}` : '',
foto_8: f.foto_8 ? `http://localhost:8000/storage/${f.foto_8}` : ''
} }
}) })
</script> </script>