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
public function getByInvitationCode($code)
{
$data = Guest::with('pelanggan.template')
$data = Guest::with('pelanggan.template', 'pelanggan.rsvp')
->where('kode_invitasi', $code)
->first();

View File

@ -33,7 +33,7 @@ class RsvpController extends Controller
// 🔹 Ambil semua RSVP berdasarkan kode undangan (GET)
public function show($invitationCode)
{
$pelanggan = Pelanggan::where('kode_invitasi', $invitationCode)->first();
$pelanggan = Pelanggan::where('invitation_code', $invitationCode)->first();
if (!$pelanggan) {
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');
}
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>
<section class="bg-[#FFF8EC] py-12 text-center">
<!-- Title -->
<div class="mb-8">
<div class="flex items-center justify-center gap-2 mb-2">
<span class="h-[1px] w-12 bg-yellow-400"></span>
<span class="text-yellow-600 text-2xl">🌸</span>
<span class="h-[1px] w-12 bg-yellow-400"></span>
</div>
<h2 class="text-2xl md:text-3xl font-bold text-yellow-700">Gallery</h2>
<section class="bg-transparent py-12 px-4">
<!-- Gallery Grid - Masonry Style -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-6xl mx-auto px-4" data-aos="fade-up">
<img
v-for="(img, index) in displayImages"
:key="index"
:src="img"
alt="Gallery photo"
class="rounded-lg object-cover w-full shadow-lg hover:shadow-xl transition-shadow duration-300 cursor-pointer"
:class="getGridClass(index)"
@click="openLightbox(index)"
/>
</div>
<!-- Gallery Grid -->
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 max-w-4xl mx-auto px-4">
<img v-for="(img, index) in images" :key="index" :src="img" alt="gallery"
class="rounded-lg object-cover w-full h-full shadow-md" :class="getGridClass(index)" />
</div>
<!-- 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"
<!-- Quote Section -->
<div class="max-w-3xl mx-auto mt-12 px-6 text-center" data-aos="fade-up" data-aos-delay="200">
<p class="text-gray-600 text-sm md:text-base leading-relaxed">
"And among His Signs is this, that He created for you mates from among yourselves,
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."
</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>
<!-- Bottom Decoration -->
<div class="flex items-center justify-center gap-2 mt-6">
<span class="h-[1px] w-12 bg-yellow-400"></span>
<span class="text-yellow-600 text-2xl">🌸</span>
<span class="h-[1px] w-12 bg-yellow-400"></span>
<!-- Lightbox Modal -->
<Teleport to="body">
<div
v-if="lightboxOpen"
class="fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-4"
@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>
</template>
<script setup>
const images = [
"/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
]
import { ref, computed } from 'vue'
// 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) => {
switch (index) {
case 0: return "row-span-2 h-[400px]" // tinggi besar
case 4: return "col-span-2 h-[250px]" // lebar besar
case 3: return "col-span-2 h-[250px]" // melebar
default: return "h-[200px]"
const pattern = index % 8
switch (pattern) {
case 0:
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>
<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>
<section class="bg-[#FFF8EC] py-12 px-4">
<!-- Judul -->
<div class="flex items-center justify-center gap-2 mb-8">
<span class="h-[1px] w-12 bg-yellow-400"></span>
<span class="text-yellow-600 text-2xl">💌</span>
<span class="h-[1px] w-12 bg-yellow-400"></span>
<section class="bg-gradient-to-b from-amber-50 to-white py-16 px-4 md:px-6 lg:px-8">
<!-- Full-width container -->
<div class="w-full">
<!-- Judul (tetap terpusat) -->
<div class="text-center mb-12">
<div class="flex items-center justify-center gap-2 mb-6">
<span class="h-[1px] w-12 bg-amber-400"></span>
<span class="text-amber-500 text-2xl">Flower</span>
<span class="h-[1px] w-12 bg-amber-400"></span>
</div>
<h2 class="text-2xl md:text-3xl font-bold text-yellow-600 text-center mb-10">
<h2 class="text-3xl md:text-4xl font-bold text-amber-600">
Guest Book
</h2>
</div>
<!-- Layout Form + Ucapan (konten terpusat) -->
<div class="max-w-5xl mx-auto">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
<!-- Layout Form + Ucapan -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
<!-- 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>
<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>
<form class="space-y-4">
<form @submit.prevent="handleSubmit" class="space-y-5">
<!-- Nama -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Your Name</label>
<input
v-model="form.name"
type="text"
placeholder="Your Name"
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-yellow-400 focus:outline-none"
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
rows="3"
placeholder="Write your message..."
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-yellow-400 focus:outline-none"
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"
class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200"
: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"
>
Send Now!
<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>
<!-- Daftar Ucapan -->
<div class="bg-white shadow-lg rounded-2xl p-6">
<div class="flex items-center gap-2 mb-4">
<span class="text-gray-500">💬</span>
<span class="text-gray-700 font-medium">04 Messages</span>
<div
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 -->
<div class="space-y-3">
<div class="p-3 bg-gray-100 rounded-lg">
<p class="font-semibold text-gray-800">Tia SMAN6BDG</p>
<p class="text-sm text-gray-600">Congrats!!</p>
<!-- 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>
<div class="p-3 bg-gray-100 rounded-lg">
<p class="font-semibold text-gray-800">Muthia Rahma</p>
<p class="text-sm text-gray-600">Happy wedd my sisstaa!</p>
<!-- 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>
@ -66,5 +156,88 @@
</template>
<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>
<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>
<section class="bg-[#FFF8EC] py-12 px-4">
<section class="bg-transparent py-12 px-4">
<!-- Title -->
<div class="text-center mb-12" data-aos="fade-up">
<h2 class="text-3xl md:text-4xl font-serif text-rose-800 mb-4">Ucapan</h2>
<p class="text-gray-600">Berikan ucapan & doa restu untuk kami</p>
<div class="text-center mb-16" data-aos="fade-up">
<div class="flex items-center justify-center gap-3 mb-4">
<div class="w-450 h-px bg-amber-400"></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 -->
<div class="max-w-4xl mx-auto grid md:grid-cols-2 gap-8">
<!-- 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>
<form @submit.prevent="handleSubmit" class="space-y-4">
@ -18,7 +22,8 @@
v-model="form.name"
type="text"
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 -->
@ -26,7 +31,8 @@
v-model="form.message"
placeholder="Message"
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>
<!-- Attendance -->
@ -60,7 +66,8 @@
<!-- Submit -->
<button
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!
</button>
@ -68,13 +75,14 @@
</div>
<!-- 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">
<span class="text-xl">💬</span>
<span class="text-xl">Chat</span>
<span>{{ comments.length }}</span>
</div>
<div v-for="(c, i) in comments" :key="i" class="mb-4">
<div class="space-y-4">
<div v-for="(c, i) in comments" :key="i">
<div class="flex items-center justify-between mb-1">
<span class="font-semibold text-gray-800">{{ c.name }}</span>
<span
@ -89,7 +97,14 @@
</span>
</div>
<p class="text-gray-600 text-sm">{{ c.message }}</p>
<hr class="mt-3" />
<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>
</div>
</div>
@ -128,3 +143,9 @@ const attendanceClass = (val) => {
];
};
</script>
<style scoped>
.space-y-4 > div:last-child hr {
display: none;
}
</style>

View File

@ -1,21 +1,49 @@
<!-- components/invitation/templates/wedding/WeddingA.vue -->
<template>
<div class="wedding-template-a min-h-screen bg-gradient-to-b from-rose-50 to-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/40"></div>
<div class="relative text-center text-white p-8">
<p class="text-sm uppercase tracking-widest mb-4 animate-fade-in">The Wedding of</p>
<h1 class="text-5xl md:text-7xl font-serif mb-6 animate-slide-up">
{{ data.bride?.nickname || 'Bride' }} & {{ data.groom?.nickname || 'Groom' }}
<div class="wedding-template-a min-h-screen bg-white">
<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-6 md:p-8 max-w-2xl">
<p class="text-xs uppercase tracking-widest mb-2 opacity-90 font-light"
style="font-family: 'Playfair Display', serif; letter-spacing: 0.2em;">
You are invited to the big day
</p>
<p class="text-sm mb-6 opacity-80" style="font-family: 'Playfair Display', serif;">
We invite you to celebrate our wedding
</p>
<p class="text-lg uppercase tracking-widest mb-4 opacity-90"
style="font-family: 'Playfair Display', serif; letter-spacing: 0.15em;">
THE WEDDING OF
</p>
<h1 class="text-5xl md:text-7xl font-serif mb-8 italic leading-tight"
style="font-family: 'Great Vibes', cursive;">
{{ data.groom?.nickname || 'Asep' }} & {{ data.bride?.nickname || 'Putri' }}
</h1>
<p class="text-lg mb-8 animate-fade-in-delay">{{ formatDate(data.eventDate) }}</p>
<p class="text-xl mb-10 tracking-wide"
style="font-family: 'Playfair Display', serif; letter-spacing: 0.05em;">
{{ formatSimpleDate(data.eventDate) }}
</p>
<p class="text-sm uppercase tracking-widest mb-3 opacity-90"
style="font-family: 'Playfair Display', serif; letter-spacing: 0.15em;">
Kepada Yth.
</p>
<p class="text-base mb-8 font-medium" style="font-family: 'Playfair Display', serif;">
Bapak/Ibu/Saudara/i
</p>
<button @click="openInvitation"
class="px-8 py-3 bg-white text-rose-600 rounded-full font-semibold hover:bg-rose-50 transition-all duration-300 animate-pulse-slow">
<Icon name="mdi:envelope-open-outline" class="mr-2" />
Buka Undangan
class="px-10 py-3 bg-transparent border-2 border-white text-white rounded-md font-medium text-sm uppercase tracking-wider hover:bg-white hover:text-gray-800 transition-all duration-300"
style="font-family: 'Playfair Display', serif; letter-spacing: 0.1em;">
Open Invitation
</button>
</div>
</section>
@ -23,75 +51,78 @@
<!-- Main Content -->
<div v-show="isOpened" class="animate-fade-in">
<!-- Hero Section -->
<section class="relative h-screen flex items-center justify-center overflow-hidden">
<div class="absolute inset-0 bg-cover bg-center"
<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-gradient-to-b from-transparent via-black/20 to-black/40"></div>
</div>
<div class="absolute inset-0 bg-black/40"></div>
<div class="relative text-center text-white px-4 z-10">
<p class="text-sm uppercase tracking-widest mb-4" data-aos="fade-down">
{{ data.greeting || 'Dengan Memohon Rahmat dan Ridho Allah SWT' }}
</p>
<h2 class="text-4xl md:text-6xl font-serif mb-4" data-aos="zoom-in">
{{ data.bride.nickname }} & {{ data.groom.nickname }}
</h2>
<div class="w-24 h-0.5 bg-white mx-auto mb-4" data-aos="zoom-in" data-aos-delay="200"></div>
<p class="text-lg" data-aos="fade-up" data-aos-delay="300">
{{ formatDate(data.eventDate) }}
</p>
<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>
<!-- Scroll Indicator -->
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 animate-bounce">
<Icon name="mdi:chevron-double-down" class="text-white text-3xl" />
<Icon name="mdi:chevron-down" class="text-white text-3xl" />
</div>
</section>
<!-- Couple Section -->
<section class="py-20 bg-white rounded-2xl shadow-lg max-w-5xl mx-auto px-6">
<div class="text-center mb-12" data-aos="fade-up">
<h2 class="text-3xl md:text-4xl font-bold text-yellow-600 mb-4">Meet The Happy Couple</h2>
<p class="text-gray-600 max-w-2xl mx-auto">
Glory be to Allah SWT who has created creatures in pairs. Ya Allah,
please accept and bless us
<section class="py-20 bg-gradient-to-b from-amber-50 via-white to-amber-50">
<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">Meet The Happy Couple</h2>
<p class="text-gray-600 max-w-2xl mx-auto text-sm">
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>
<div class="grid md:grid-cols-2 gap-12 items-center">
<div class="grid md:grid-cols-2 gap-12 max-w-2xl mx-auto">
<!-- Groom -->
<div class="text-center" data-aos="fade-right">
<div class="relative inline-block mb-6">
<div
class="w-40 h-40 md:w-48 md:h-48 rounded-full border-4 border-yellow-400 mx-auto flex items-center justify-center overflow-hidden">
<div class="w-40 h-40 rounded-full bg-amber-100 mx-auto overflow-hidden border-4 border-white shadow-xl">
<img :src="data.groom.photo || '/pria.jpg'" :alt="data.groom.fullname"
class="w-full h-full object-cover" />
</div>
<!-- Ornament -->
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-4">
<Icon name="mdi:flower" class="text-yellow-500 text-5xl" />
</div>
</div>
<h3 class="text-2xl font-[GreatVibes] text-yellow-600 mb-2">{{ data.groom.fullname }}</h3>
<p class="text-gray-600">Son of</p>
<p class="text-gray-700 font-medium">
{{ data.groom.fatherName }} & {{ data.groom.motherName }}
<h3 class="text-2xl font-bold text-amber-600 mb-2 italic" style="font-family: 'Great Vibes', cursive;">
{{ data.groom.nickname }}
</h3>
<p class="text-gray-800 font-semibold mb-1">{{ data.groom.fullname }}</p>
<p class="text-sm text-gray-600 mb-3">
Son of Mr. {{ data.groom.fatherName }} & Mrs. {{ data.groom.motherName }}
</p>
<div class="flex justify-center mt-4 space-x-4">
<div class="flex justify-center space-x-3">
<a v-if="data.groom.instagram" :href="`https://instagram.com/${data.groom.instagram}`"
target="_blank">
<Icon name="mdi:instagram"
class="text-yellow-600 text-2xl hover:text-yellow-700 transition-colors" />
target="_blank" class="w-8 h-8 flex items-center justify-center bg-amber-100 rounded-full hover:bg-amber-200 transition-colors">
<Icon name="mdi:instagram" class="text-amber-600 text-lg" />
</a>
<a v-if="data.groom.twitter" :href="`https://twitter.com/${data.groom.twitter}`"
target="_blank">
<Icon name="mdi:twitter"
class="text-yellow-600 text-2xl hover:text-yellow-700 transition-colors" />
target="_blank" class="w-8 h-8 flex items-center justify-center bg-amber-100 rounded-full hover:bg-amber-200 transition-colors">
<Icon name="mdi:twitter" class="text-amber-600 text-lg" />
</a>
<a v-if="data.groom.facebook" :href="`https://facebook.com/${data.groom.facebook}`"
target="_blank">
<Icon name="mdi:facebook"
class="text-yellow-600 text-2xl hover:text-yellow-700 transition-colors" />
target="_blank" class="w-8 h-8 flex items-center justify-center bg-amber-100 rounded-full hover:bg-amber-200 transition-colors">
<Icon name="mdi:facebook" class="text-amber-600 text-lg" />
</a>
</div>
</div>
@ -99,36 +130,30 @@
<!-- Bride -->
<div class="text-center" data-aos="fade-left">
<div class="relative inline-block mb-6">
<div
class="w-40 h-40 md:w-48 md:h-48 rounded-full border-4 border-yellow-400 mx-auto flex items-center justify-center overflow-hidden">
<div class="w-40 h-40 rounded-full bg-amber-100 mx-auto overflow-hidden border-4 border-white shadow-xl">
<img :src="data.bride.photo || '/wanita.jpg'" :alt="data.bride.fullname"
class="w-full h-full object-cover" />
</div>
<!-- Ornament -->
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-4">
<Icon name="mdi:flower" class="text-yellow-500 text-5xl" />
</div>
</div>
<h3 class="text-2xl font-[GreatVibes] text-yellow-600 mb-2">{{ data.bride.fullname }}</h3>
<p class="text-gray-600">Daughter of</p>
<p class="text-gray-700 font-medium">
{{ data.bride.fatherName }} & {{ data.bride.motherName }}
<h3 class="text-2xl font-bold text-amber-600 mb-2 italic" style="font-family: 'Great Vibes', cursive;">
{{ data.bride.nickname }}
</h3>
<p class="text-gray-800 font-semibold mb-1">{{ data.bride.fullname }}</p>
<p class="text-sm text-gray-600 mb-3">
Daughter of Mr. {{ data.bride.fatherName }} & Mrs. {{ data.bride.motherName }}
</p>
<div class="flex justify-center mt-4 space-x-4">
<div class="flex justify-center space-x-3">
<a v-if="data.bride.instagram" :href="`https://instagram.com/${data.bride.instagram}`"
target="_blank">
<Icon name="mdi:instagram"
class="text-yellow-600 text-2xl hover:text-yellow-700 transition-colors" />
target="_blank" class="w-8 h-8 flex items-center justify-center bg-amber-100 rounded-full hover:bg-amber-200 transition-colors">
<Icon name="mdi:instagram" class="text-amber-600 text-lg" />
</a>
<a v-if="data.bride.twitter" :href="`https://twitter.com/${data.bride.twitter}`"
target="_blank">
<Icon name="mdi:twitter"
class="text-yellow-600 text-2xl hover:text-yellow-700 transition-colors" />
target="_blank" class="w-8 h-8 flex items-center justify-center bg-amber-100 rounded-full hover:bg-amber-200 transition-colors">
<Icon name="mdi:twitter" class="text-amber-600 text-lg" />
</a>
<a v-if="data.bride.facebook" :href="`https://facebook.com/${data.bride.facebook}`"
target="_blank">
<Icon name="mdi:facebook"
class="text-yellow-600 text-2xl hover:text-yellow-700 transition-colors" />
target="_blank" class="w-8 h-8 flex items-center justify-center bg-amber-100 rounded-full hover:bg-amber-200 transition-colors">
<Icon name="mdi:facebook" class="text-amber-600 text-lg" />
</a>
</div>
</div>
@ -136,283 +161,224 @@
<!-- Our Story Button -->
<div class="text-center mt-12">
<button
class="bg-yellow-500 hover:bg-yellow-600 text-white px-6 py-3 rounded-md font-medium transition-colors">
Our Story
<button @click="showStory = !showStory"
class="bg-amber-500 hover:bg-amber-600 text-white px-8 py-3 rounded-md font-medium transition-colors shadow-md">
{{ showStory ? 'Hide Story' : 'Our Story' }}
</button>
</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-rose-50">
<div class="max-w-4xl mx-auto px-4">
<div class="text-center mb-12" data-aos="fade-up">
<h2 class="text-3xl md:text-4xl font-serif text-rose-800 mb-4">Waktu & Tempat</h2>
<p class="text-gray-600">Merupakan suatu kehormatan dan kebahagiaan bagi kami apabila
Bapak/Ibu/Saudara/i berkenan hadir</p>
<section class="py-20 bg-gradient-to-b from-amber-50 to-amber-50">
<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>
<!-- Akad Nikah -->
<div class="bg-white rounded-2xl shadow-lg p-8 mb-6" data-aos="fade-up">
<div class="text-center">
<div
class="inline-flex items-center justify-center w-16 h-16 bg-rose-100 rounded-full mb-4">
<Icon name="mdi:mosque" class="text-rose-600 text-2xl" />
</div>
<h3 class="text-2xl font-serif text-gray-800 mb-4">Akad Nikah</h3>
<div class="space-y-2 text-gray-600">
<p class="flex items-center justify-center">
<Icon name="mdi:calendar" class="mr-2" />
{{ formatDate(data.akad.date) }}
</p>
<p class="flex items-center justify-center">
<Icon name="mdi:clock-outline" class="mr-2" />
{{ data.akad.time }} - Selesai
</p>
<p class="flex items-center justify-center">
<Icon name="mdi:map-marker" class="mr-2" />
{{ data.akad.place }}
</p>
<p class="text-sm">{{ data.akad.address }}</p>
</div>
</div>
</div>
<!-- Resepsi -->
<div class="bg-white rounded-2xl shadow-lg p-8" data-aos="fade-up" data-aos-delay="200">
<div class="text-center">
<div
class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4">
<Icon name="mdi:party-popper" class="text-blue-600 text-2xl" />
</div>
<h3 class="text-2xl font-serif text-gray-800 mb-4">Resepsi</h3>
<div class="space-y-2 text-gray-600">
<p class="flex items-center justify-center">
<Icon name="mdi:calendar" class="mr-2" />
{{ formatDate(data.resepsi.date) }}
</p>
<p class="flex items-center justify-center">
<Icon name="mdi:clock-outline" class="mr-2" />
{{ data.resepsi.time }} - Selesai
</p>
<p class="flex items-center justify-center">
<Icon name="mdi:map-marker" class="mr-2" />
{{ data.resepsi.place }}
</p>
<p class="text-sm">{{ data.resepsi.address }}</p>
</div>
</div>
</div>
<!-- Countdown Timer -->
<div class="mt-12" data-aos="zoom-in">
<!-- 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">
<div class="max-w-6xl mx-auto px-4">
<section class="py-20 bg-amber-50">
<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>
<!-- Maps -->
<section class="bg-[#FFF8EC] py-12 text-center">
<div class="max-w-5xl mx-auto px-4 text-center">
<!-- Judul -->
<div class="mb-8">
<div class="flex items-center justify-center gap-2 mb-2">
<span class="h-[1px] w-12 bg-yellow-400"></span>
<span class="text-yellow-600 text-2xl">🌸</span>
<span class="h-[1px] w-12 bg-yellow-400"></span>
</div>
<h2 class="text-3xl md:text-4xl font-serif text-rose-800 mb-4">Lokasi Acara</h2>
</div>
<div class="grid md:grid-cols-2 gap-6">
<div data-aos="fade-right">
<h3 class="font-semibold text-gray-800 mb-3">Akad Nikah</h3>
<Maps :location="data.akad.mapUrl" />
<a :href="data.akad.mapUrl" target="_blank"
class="inline-block mt-4 px-6 py-2 bg-rose-600 text-white rounded-lg hover:bg-rose-700 transition-colors">
<Icon name="mdi:map" class="mr-2" />
Buka di Google Maps
</a>
</div>
<div data-aos="fade-left">
<h3 class="font-semibold text-gray-800 mb-3">Resepsi</h3>
<Maps :location="data.resepsi.mapUrl" />
<a :href="data.resepsi.mapUrl" target="_blank"
class="inline-block mt-4 px-6 py-2 bg-rose-600 text-white rounded-lg hover:bg-rose-700 transition-colors">
<Icon name="mdi:map" class="mr-2" />
Buka di Google Maps
</a>
</div>
</div>
</div>
</section>
<!-- Ucapan -->
<section class="py-20">
<div class="max-w-2xl mx-auto px-4">
<!-- RSVP Form -->
<RSVP :invitationId="data.id" @submitted="handleRSVPSubmit" />
</div>
</section>
<section>
<!-- Gift Section -->
<section class="py-20 bg-[#FFF8F0]">
<div class="max-w-5xl mx-auto px-4 text-center">
<!-- Judul -->
<div class="mb-10">
<div class="flex items-center justify-center mb-4">
<span class="w-20 h-px bg-orange-300"></span>
<span class="mx-2 text-orange-400 text-2xl">🌸</span>
<span class="w-20 h-px bg-orange-300"></span>
<section class="py-20 bg-gradient-to-b from-amber-50 to-amber-50">
<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-3xl md:text-4xl font-bold text-orange-500">Give a Gift</h2>
<p class="text-gray-600 mt-4 max-w-2xl mx-auto">
Bagi keluarga dan sahabat yang ingin berbagi tanda kasih, kami dengan senang hati
menerimanya sebagai bagian dari doa dan restu yang tulus. Terima kasih telah menjadi
bagian dari hari istimewa kami.
<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>
<!-- Card Container -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="grid md:grid-cols-2 gap-6">
<!-- Digital Wallet -->
<div class="bg-white rounded-2xl shadow-md p-6 text-left">
<h3 class="text-xl font-semibold text-gray-700 mb-4">Digital Wallet</h3>
<p class="text-sm text-gray-400 mb-6">Note: Tap to copy bank number</p>
<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>
<!-- Akun 1 -->
<div class="mb-6">
<p class="font-semibold text-orange-500 flex items-center space-x-2">
<span>Asep Irawan</span>
<img src="/logo1.png" alt="BNI" class="h-5" />
</p>
<div class="flex items-center mt-2 bg-white shadow-md rounded-xl p-3">
<input class="flex-1 text-gray-700 font-mono outline-none"
value="009 - 0222 2444 21" readonly />
<button @click="copyToClipboard('009 - 0222 2444 21')">
<Icon name="mdi:content-copy" class="text-gray-500" />
</button>
<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>
<!-- Akun 2 -->
<div>
<p class="font-semibold text-orange-500 flex items-center space-x-2">
<span>Putri Amanda</span>
<img src="/logo2.png" alt="BNI" class="h-5" />
</p>
<div class="flex items-center mt-2 bg-white shadow-md rounded-xl p-3">
<input class="flex-1 text-gray-700 font-mono outline-none"
value="009 - 0222 2444 21" readonly />
<button @click="copyToClipboard('009 - 0222 2444 21')">
<Icon name="mdi:content-copy" class="text-gray-500" />
</button>
</div>
</div>
</div>
<!-- Offline Gift -->
<div class="bg-white rounded-2xl shadow-md p-6 text-left">
<h3 class="text-xl font-semibold text-gray-700 mb-4">Offline Gift</h3>
<p class="text-gray-600 mb-6">
Jl. Terusan Jakarta No.53, Cicaheum, Kec. Kiaracondong, Kota Bandung, Jawa Barat
40291
<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>
<div class="flex items-center space-x-4">
<Icon name="mdi:map-marker" class="text-orange-500 text-2xl" />
<a href="https://maps.google.com/?q=Jl.+Terusan+Jakarta+No.53,+Cicaheum"
target="_blank"
class="bg-orange-400 hover:bg-orange-500 text-white px-6 py-2 rounded-xl shadow transition">
Open Map
<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>
<!-- Garis Dekorasi Bawah -->
<div class="flex items-center justify-center mt-10">
<span class="w-20 h-px bg-orange-300"></span>
<span class="mx-2 text-orange-400 text-2xl">🌸</span>
<span class="w-20 h-px bg-orange-300"></span>
</div>
</div>
</section>
</section>
<!-- Closing -->
<section class="py-20 text-center px-4">
<div class="max-w-2xl mx-auto" data-aos="fade-up">
<p class="text-gray-600 mb-4">
Merupakan suatu kehormatan dan kebahagiaan bagi kami apabila Bapak/Ibu/Saudara/i
berkenan hadir untuk memberikan doa restu kepada kedua mempelai.
</p>
<p class="text-gray-600 mb-8">
Atas kehadiran serta doa restu Bapak/Ibu/Saudara/i, kami ucapkan terima kasih.
</p>
<p class="font-serif text-2xl text-rose-800 mb-2">Wassalamu'alaikum Wr. Wb.</p>
<p class="text-gray-700 font-semibold mt-8">
{{ data.bride.nickname }} & {{ data.groom.nickname }}
</p>
<!-- RSVP Section -->
<section class="py-20 bg-gradient-to-b from-amber-50 to-amber-50">
<div class="w-full">
<div class="max-w-2xl mx-auto px-6">
<RSVP :invitationId="data.id" @submitted="handleRSVPSubmit" />
</div>
</div>
</section>
<!-- Footer -->
<footer class="relative text-center bg-cover bg-center bg-no-repeat"
style="background-image: url('/wedding1.png');">
<!-- Overlay gelap biar teks lebih kontras -->
<div class="absolute inset-0 bg-black/40"></div>
<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>
<!-- Konten footer -->
<div class="relative z-10">
<!-- Kotak Nama Mempelai -->
<div class="flex items-center justify-center space-x-4 -mt-12">
<div class="px-8 py-3 border border-white rounded-lg bg-white/20 backdrop-blur-sm shadow">
<p class="font-semibold italic text-2xl text-white">{{ data.groom.nickname }}</p>
<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>
<span class="text-3xl font-bold text-white">-</span>
<div class="px-8 py-3 border border-white rounded-lg bg-white/20 backdrop-blur-sm shadow">
<p class="font-semibold italic text-2xl text-white">{{ data.bride.nickname }}</p>
<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>
<!-- Ikon bunga -->
<div class="flex justify-center mt-6">
<span class="text-white text-3xl"></span>
</div>
<Icon name="mdi:flower" class="text-3xl mb-6" />
<!-- Logo -->
<div class="flex justify-center mt-4">
<img src="/ABBAUF.png" alt="Logo" class="h-12 object-contain" />
</div>
<img src="/ABBAUF.png" alt="Logo" class="h-12 mx-auto mb-4 opacity-90" />
<!-- Copyright -->
<p class="text-xs mt-4 text-white/80">
© 2024 All rights reserved
</p>
<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>
@ -420,110 +386,133 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import AOS from 'aos'
import 'aos/dist/aos.css'
// Import shared components
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 GuestBook from '~/components/templates/wedding/GuestBook.vue'
import MusicPlayer from '~/components/templates/wedding/MusicPlayer.vue'
// Props untuk menerima data dari parent/API
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({
id: '',
coverImage: '',
heroImage: '',
coverImage: '/wedding1.png',
heroImage: '/wedding1.png',
footerImage: '/wedding1.png',
greeting: '',
eventDate: new Date(),
bride: {
fullname: '',
nickname: '',
photo: '',
orderChild: '',
fatherName: '',
motherName: '',
instagram: ''
instagram: '',
twitter: '',
facebook: ''
},
groom: {
fullname: '',
nickname: '',
photo: '',
orderChild: '',
fatherName: '',
motherName: '',
instagram: ''
instagram: '',
twitter: '',
facebook: ''
},
akad: {
date: new Date(),
time: '',
place: '',
address: '',
mapUrl: ''
mapUrl: '',
image: ''
},
resepsi: {
date: new Date(),
time: '',
place: '',
address: '',
mapUrl: ''
mapUrl: '',
image: ''
},
story: {
image1: '',
text1: '',
image2: '',
text2: ''
},
gallery: [],
gift: {
bankName: '',
accountNumber: '',
accountName: ''
address: '',
mapUrl: ''
},
musicUrl: ''
})
}
})
// State
const isOpened = ref(false)
const showStory = ref(false)
const guestMessages = ref([])
// Methods
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' }
])
// Map foto_1 to foto_8 to gallery array
const galleryImages = computed(() => {
const images = [
props.data.foto_1,
props.data.foto_2,
props.data.foto_3,
props.data.foto_4,
props.data.foto_5,
props.data.foto_6,
props.data.foto_7,
props.data.foto_8
].filter(img => img && img !== ''); // Filter out undefined or empty strings
return images.length > 0 ? images : [
'/wedding1.png',
'/pria.jpg',
'/wanita.jpg'
];
})
const openInvitation = () => {
isOpened.value = true
// Play music if available
if (props.data.musicUrl) {
// Music will autoplay through MusicPlayer component
}
console.log('coverImage:', props.data.coverImage)
console.log('gallery:', galleryImages.value) // Debug gallery images
}
const formatDate = (date) => {
const formatSimpleDate = (date) => {
if (!date) return ''
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}
return new Date(date).toLocaleDateString('id-ID', options)
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)
// Show toast notification
alert('Nomor rekening berhasil disalin!')
alert('Account number copied!')
} catch (err) {
console.error('Failed to copy:', err)
}
}
const handleRSVPSubmit = (data) => {
// Handle RSVP submission
console.log('RSVP submitted:', data)
// Add to guest messages
if (data.message) {
guestMessages.value.unshift({
name: data.name,
@ -534,98 +523,44 @@ const handleRSVPSubmit = (data) => {
}
}
// Load guest messages from API
const loadGuestMessages = async () => {
try {
// Fetch from your Laravel API
// const response = await $fetch(`/api/invitations/${props.data.id}/messages`)
// guestMessages.value = response.data
} catch (error) {
console.error('Error loading guest messages:', error)
}
}
// Lifecycle
onMounted(() => {
// Initialize AOS
console.log('props.data:', props.data) // Debug data saat komponen dimuat
AOS.init({
duration: 1000,
once: true,
offset: 100
offset: 100,
easing: 'ease-out-cubic'
})
// Load guest messages
loadGuestMessages()
})
</script>
<style scoped>
/* Custom animations */
@import url('https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap');
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-up {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes pulse-slow {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fade-in 1s ease-out;
}
.animate-fade-in-delay {
animation: fade-in 1s ease-out 0.5s both;
}
.animate-slide-up {
animation: slide-up 1s ease-out;
}
.animate-pulse-slow {
animation: pulse-slow 2s ease-in-out infinite;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
width: 10px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
background: #fef3c7;
}
::-webkit-scrollbar-thumb {
background: #f43f5e;
border-radius: 4px;
background: #f59e0b;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #e11d48;
background: #d97706;
}
</style>
```

View File

@ -9,9 +9,9 @@
</template>
<script setup>
import { computed } from 'vue'
import WeddingA from '~/components/templates/wedding/weddingA.vue'
// Props dari parent (/p/[code].vue)
const props = defineProps({
data: {
type: Object,
@ -19,51 +19,75 @@ const props = defineProps({
}
})
// Format data dari backend agar cocok dengan struktur WeddingA.vue
const formattedData = computed(() => {
const f = props.data.form || {}
return {
id: props.data.id,
coverImage: f.cover_image || '/default-cover.jpg',
heroImage: f.hero_image || '/default-hero.jpg',
id: props.data.id || '',
coverImage: f.cover_image ? `http://localhost:8000/storage/${f.cover_image}` : '/default-cover.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...',
eventDate: f.hari_tanggal_resepsi,
eventDate: f.hari_tanggal_resepsi || new Date(),
bride: {
fullname: f.nama_lengkap_wanita,
nickname: f.nama_panggilan_wanita,
fullname: f.nama_lengkap_wanita || '',
nickname: f.nama_panggilan_wanita || '',
photo: f.foto_wanita ? `http://localhost:8000/storage/${f.foto_wanita}` : '/wanita.jpg',
fatherName: f.nama_ayah_wanita || f.nama_bapak_wanita, // fleksibel
motherName: f.nama_ibu_wanita,
instagram: f.instagram_wanita || ''
fatherName: f.nama_ayah_wanita || f.nama_bapak_wanita || '',
motherName: f.nama_ibu_wanita || '',
instagram: f.instagram_wanita || '',
twitter: f.twitter_wanita || '',
facebook: f.facebook_wanita || ''
},
groom: {
fullname: f.nama_lengkap_pria,
nickname: f.nama_panggilan_pria,
fullname: f.nama_lengkap_pria || '',
nickname: f.nama_panggilan_pria || '',
photo: f.foto_pria ? `http://localhost:8000/storage/${f.foto_pria}` : '/pria.jpg',
fatherName: f.nama_ayah_pria || f.nama_bapak_pria, // fleksibel
motherName: f.nama_ibu_pria,
instagram: f.instagram_pria || ''
fatherName: f.nama_ayah_pria || f.nama_bapak_pria || '',
motherName: f.nama_ibu_pria || '',
instagram: f.instagram_pria || '',
twitter: f.twitter_pria || '',
facebook: f.facebook_pria || ''
},
akad: {
date: f.hari_tanggal_akad,
time: f.waktu_akad,
place: f.tempat_akad,
address: f.alamat_akad,
mapUrl: f.link_gmaps_akad
date: f.hari_tanggal_akad || new Date(),
time: f.waktu_akad || '',
place: f.tempat_akad || '',
address: f.alamat_akad || '',
mapUrl: f.link_gmaps_akad || '',
image: f.akad_image ? `http://localhost:8000/storage/${f.akad_image}` : '/wedding1.png'
},
resepsi: {
date: f.hari_tanggal_resepsi,
time: f.waktu_resepsi,
place: f.tempat_resepsi,
address: f.alamat_resepsi,
mapUrl: f.link_gmaps_resepsi
date: f.hari_tanggal_resepsi || new Date(),
time: f.waktu_resepsi || '',
place: f.tempat_resepsi || '',
address: f.alamat_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}`])
.filter(Boolean)
.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>