Undangan/proyek-frontend/app/components/templates/khitan/Event.vue
2025-10-20 15:42:05 +07:00

362 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="min-h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
<!-- Background Pattern -->
<div class="absolute inset-0 bg-pattern opacity-20"></div>
<!-- Decorative Lanterns -->
<div class="absolute top-4 left-4 md:top-8 md:left-8 animate-sway">
<Lantern />
</div>
<div class="absolute top-4 right-4 md:top-8 md:right-8 animate-sway animation-delay-1000">
<Lantern />
</div>
<!-- Main Content -->
<div class="relative z-10 min-h-screen flex items-center justify-center px-4 md:px-8 py-12">
<div class="text-center max-w-5xl mx-auto">
<!-- Bismillah -->
<div class="mb-10 animate-fade-in-down">
<h1 class="text-yellow-400 text-xl md:text-3xl lg:text-4xl font-bold mb-6 arabic-text">
بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ
</h1>
</div>
<!-- Event Description -->
<div class="mb-12 animate-fade-in-up animation-delay-300">
<p class="text-white text-sm md:text-base lg:text-lg leading-relaxed max-w-3xl mx-auto">
Dengan memohon rahmat dan ridho Allah Subhanahu wa Ta'ala, kami<br>
mengundang Bapak/Ibu/Saudara/i untuk hadir dan berbagi kebahagiaan<br>
dalam acara Khitanan putra kami tercinta, yang insya Allah akan<br>
diselenggarakan pada:
</p>
</div>
<!-- Event Details -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 md:gap-8 mb-12">
<!-- Acara Khitanan -->
<div class="animate-fade-in-left animation-delay-600">
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-6 md:p-8 border border-yellow-400/30 shadow-lg">
<h3 class="text-yellow-400 text-xl md:text-2xl font-bold mb-4 font-script">
Khitanan
</h3>
<div class="text-white text-2xl md:text-3xl lg:text-4xl font-bold mb-6">
{{ eventDate }} ({{ eventDay }})
</div>
<div class="text-white mb-6">
<p class="text-sm md:text-base mb-2">🕓 {{ eventTime }}</p>
<p class="text-sm md:text-base mb-2">📍 {{ eventLocation }}</p>
</div>
<!-- Map -->
<div class="bg-gray-300 rounded-lg h-40 md:h-48 relative overflow-hidden mb-6">
<div class="absolute inset-0 bg-gradient-to-r from-blue-400 to-blue-600 opacity-50"></div>
<div class="absolute inset-0 flex items-center justify-center">
<Icon name="lucide:map-pin" class="w-8 h-8 text-white" />
</div>
<img
:src="eventMap"
alt="Location Map"
class="w-full h-full object-cover opacity-80"
/>
</div>
<!-- Countdown -->
<div class="grid grid-cols-4 gap-3 md:gap-4 mb-6">
<div
v-for="(item, index) in countdownItemsAcara"
:key="index"
class="bg-blue-800/50 rounded-lg p-3 md:p-4 text-center border border-yellow-400/20"
>
<div class="text-yellow-400 text-xl md:text-2xl font-bold">
{{ item.value }}
</div>
<div class="text-white text-xs md:text-sm uppercase tracking-wider">
{{ item.label }}
</div>
</div>
</div>
<!-- Add to Calendar -->
<button
@click="addToCalendar"
class="bg-yellow-400 hover:bg-yellow-500 text-blue-900 px-6 py-2 md:py-3 rounded-full font-semibold transition-all duration-300 transform hover:scale-105"
>
Add to Calendar
</button>
<!-- Map Link -->
<div class="mt-4">
<a
v-if="eventAddress"
:href="eventAddress"
target="_blank"
class="text-yellow-400 text-sm md:text-base underline hover:text-yellow-300"
>
Lihat di Google Maps
</a>
</div>
</div>
</div>
<!-- Acara Syukuran -->
<div class="animate-fade-in-right animation-delay-600">
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-6 md:p-8 border border-yellow-400/30 shadow-lg">
<h3 class="text-yellow-400 text-xl md:text-2xl font-bold mb-4 font-script">
Syukuran
</h3>
<div class="text-white text-2xl md:text-3xl lg:text-4xl font-bold mb-6">
{{ syukuranDate }} ({{ syukuranDay }})
</div>
<div class="text-white mb-6">
<p class="text-sm md:text-base mb-2">🕓 {{ syukuranTime }}</p>
<p class="text-sm md:text-base mb-2">📍 {{ syukuranAddress }}</p>
</div>
<!-- Map -->
<div class="bg-gray-300 rounded-lg h-40 md:h-48 relative overflow-hidden mb-6">
<div class="absolute inset-0 bg-gradient-to-r from-blue-400 to-blue-600 opacity-50"></div>
<div class="absolute inset-0 flex items-center justify-center">
<Icon name="lucide:map-pin" class="w-8 h-8 text-white" />
</div>
<img
:src="syukuranMapImage"
alt="Syukuran Location Map"
class="w-full h-full object-cover opacity-80"
/>
</div>
<!-- Countdown -->
<div class="grid grid-cols-4 gap-3 md:gap-4 mb-6">
<div
v-for="(item, index) in countdownItemsSyukuran"
:key="index"
class="bg-blue-800/50 rounded-lg p-3 md:p-4 text-center border border-yellow-400/20"
>
<div class="text-yellow-400 text-xl md:text-2xl font-bold">
{{ item.value }}
</div>
<div class="text-white text-xs md:text-sm uppercase tracking-wider">
{{ item.label }}
</div>
</div>
</div>
<!-- Add to Calendar -->
<button
@click="addToCalendar"
class="bg-yellow-400 hover:bg-yellow-500 text-blue-900 px-6 py-2 md:py-3 rounded-full font-semibold transition-all duration-300 transform hover:scale-105"
>
Add to Calendar
</button>
<!-- Map Link -->
<div class="mt-4">
<a
v-if="syukuranMap"
:href="syukuranMap"
target="_blank"
class="text-yellow-400 text-sm md:text-base underline hover:text-yellow-300"
>
Lihat di Google Maps
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Decorative Element -->
<div class="absolute inset-0 pointer-events-none overflow-hidden">
<div class="absolute top-0 left-1/2 transform -translate-x-1/2">
<svg viewBox="0 0 200 100" class="w-40 md:w-48 h-20 md:h-24 text-yellow-400 opacity-20">
<path
d="M100 20 Q120 0 140 20 Q160 40 140 60 Q120 40 100 60 Q80 40 60 60 Q40 40 60 20 Q80 0 100 20"
fill="currentColor"
/>
</svg>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
// Props sesuai dengan parent dan JSON
const props = defineProps({
hari_tanggal_acara: { type: String, default: '' },
waktu: { type: String, default: '' },
alamat: { type: String, default: '' },
link_gmaps: { type: String, default: '' },
hitung_mundur_mulai: { type: String, default: '' },
hari_tanggal_syukuran: { type: String, default: '' },
waktu_syukuran: { type: String, default: '' },
alamat_syukuran: { type: String, default: '' },
link_gmaps_syukuran: { type: String, default: '' },
hitung_mundur_selesai: { type: String, default: '' },
nama_panggilan: { type: String, default: '' }
})
/* ==========================
🎯 EVENT KHITAN
========================== */
const eventDate = computed(() => props.hari_tanggal_acara || '2025-10-31')
const eventDay = computed(() => new Date(eventDate.value).toLocaleDateString('id-ID', { weekday: 'long' }))
const eventTime = computed(() => props.waktu || '11:11')
const eventLocation = computed(() => props.alamat || 'Tempat belum ditentukan')
const eventAddress = computed(() => props.link_gmaps || '')
const eventMap = computed(() => `https://maps.googleapis.com/maps/api/staticmap?center=${encodeURIComponent(eventLocation.value)}&zoom=15&size=400x200&markers=color:red|${encodeURIComponent(eventLocation.value)}`)
/* ==========================
🎉 SYUKURAN
========================== */
const syukuranDate = computed(() => props.hari_tanggal_syukuran || '')
const syukuranDay = computed(() => syukuranDate.value ? new Date(syukuranDate.value).toLocaleDateString('id-ID', { weekday: 'long' }) : '')
const syukuranTime = computed(() => props.waktu_syukuran || '')
const syukuranAddress = computed(() => props.alamat_syukuran || '')
const syukuranMap = computed(() => props.link_gmaps_syukuran || '')
const syukuranMapImage = computed(() => `https://maps.googleapis.com/maps/api/staticmap?center=${encodeURIComponent(syukuranAddress.value)}&zoom=15&size=400x200&markers=color:red|${encodeURIComponent(syukuranAddress.value)}`)
/* ==========================
⏱️ COUNTDOWN
========================== */
const countdownAcara = ref({ days: 0, hours: 0, minutes: 0, seconds: 0 })
const countdownSyukuran = ref({ days: 0, hours: 0, minutes: 0, seconds: 0 })
const countdownInterval = ref(null)
const countdownItemsAcara = computed(() => [
{ value: String(countdownAcara.value.days).padStart(2, '0'), label: 'D' },
{ value: String(countdownAcara.value.hours).padStart(2, '0'), label: 'H' },
{ value: String(countdownAcara.value.minutes).padStart(2, '0'), label: 'M' },
{ value: String(countdownAcara.value.seconds).padStart(2, '0'), label: 'S' }
])
const countdownItemsSyukuran = computed(() => [
{ value: String(countdownSyukuran.value.days).padStart(2, '0'), label: 'D' },
{ value: String(countdownSyukuran.value.hours).padStart(2, '0'), label: 'H' },
{ value: String(countdownSyukuran.value.minutes).padStart(2, '0'), label: 'M' },
{ value: String(countdownSyukuran.value.seconds).padStart(2, '0'), label: 'S' }
])
const updateCountdown = () => {
const now = new Date()
// Countdown untuk acara khitanan
if (props.hitung_mundur_mulai) {
const eventDateObj = new Date(props.hitung_mundur_mulai)
const diff = eventDateObj - now
if (diff > 0) {
countdownAcara.value = {
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
minutes: Math.floor((diff / (1000 * 60)) % 60),
seconds: Math.floor((diff / 1000) % 60)
}
} else {
countdownAcara.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
}
}
// Countdown untuk acara syukuran
if (props.hitung_mundur_selesai) {
const syukuranDateObj = new Date(props.hitung_mundur_selesai)
const diff = syukuranDateObj - now
if (diff > 0) {
countdownSyukuran.value = {
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
minutes: Math.floor((diff / (1000 * 60)) % 60),
seconds: Math.floor((diff / 1000) % 60)
}
} else {
countdownSyukuran.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
}
}
}
/* ==========================
📅 ACTIONS
========================== */
const addToCalendar = () => {
const title = `Tasyakuran Khitan ${props.nama_panggilan || 'Putra Kami'}`
const start = new Date(props.hitung_mundur_mulai).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
const end = new Date(new Date(props.hitung_mundur_mulai).getTime() + 3 * 60 * 60 * 1000).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(title)}&dates=${start}/${end}&location=${encodeURIComponent(eventLocation.value)}`
window.open(url, '_blank')
}
/* ==========================
⏳ LIFECYCLE
========================== */
onMounted(() => {
updateCountdown()
countdownInterval.value = setInterval(updateCountdown, 1000)
})
onUnmounted(() => {
if (countdownInterval.value) {
clearInterval(countdownInterval.value)
}
})
</script>
<style scoped>
/* Animations & bg-pattern */
.bg-pattern {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><pattern id='pattern' x='0' y='0' width='20' height='20' patternUnits='userSpaceOnUse'><path d='M10 5 L15 10 L10 15 L5 10 Z' fill='none' stroke='rgba(255,255,255,0.1)' stroke-width='0.5'/></pattern></defs><rect width='100' height='100' fill='url(%23pattern)'/></svg>");
}
.animate-sway {
animation: sway 3s ease-in-out infinite;
}
@keyframes sway {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.animate-fade-in-down {
animation: fadeInDown 1s ease-out forwards;
}
.animate-fade-in-up {
animation: fadeInUp 1s ease-out forwards;
}
.animate-fade-in-left {
animation: fadeInLeft 1s ease-out forwards;
}
.animate-fade-in-right {
animation: fadeInRight 1s ease-out forwards;
}
@keyframes fadeInDown {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInLeft {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes fadeInRight {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
.animation-delay-300 {
animation-delay: 0.3s;
}
.animation-delay-600 {
animation-delay: 0.6s;
}
.animation-delay-1000 {
animation-delay: 1s;
}
.arabic-text {
font-family: 'Noto Naskh Arabic', serif;
}
</style>