@titas_mallick/wedding-site-gen 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -170
- package/app/api/email-reminders/route.ts +240 -0
- package/app/couple/page.tsx +4 -4
- package/app/game/page.tsx +298 -0
- package/app/guestbook/page.tsx +270 -152
- package/app/invitation/[slug]/layout.tsx +4 -2
- package/app/invitation/[slug]/page.tsx +303 -84
- package/app/invitation/actions.ts +49 -0
- package/app/invitation/maker/auth.js +1 -1
- package/app/invitation/maker/guestAdder.js +4 -0
- package/app/invitation/maker/guestShower.js +39 -8
- package/app/invitation/maker/layout.tsx +1 -1
- package/app/invitation/maker/page.js +9 -7
- package/app/invitation/maker/rsvpViewer.js +90 -8
- package/app/layout.tsx +40 -14
- package/app/mark-the-dates/page.tsx +8 -2
- package/app/page.tsx +7 -1
- package/app/providers.tsx +1 -1
- package/app/sagun/page.tsx +224 -76
- package/app/song-requests/page.tsx +242 -105
- package/app/sukanya/page.tsx +9 -13
- package/app/titas/page.tsx +8 -24
- package/app/travel-guide/page.tsx +361 -120
- package/app/updates/maker/page.js +2 -2
- package/app/updates/overlay/page.tsx +65 -30
- package/app/updates/page.js +3 -3
- package/cli.mjs +26 -15
- package/components/AdminAuth.tsx +145 -0
- package/components/AdminLinks.tsx +120 -0
- package/components/ConciergeBot.tsx +104 -44
- package/components/CountdownTimer.tsx +37 -15
- package/components/Gallery.tsx +1 -1
- package/components/LiveVideos.tsx +27 -15
- package/components/OurStory.tsx +1 -1
- package/components/SchemaMarkup.tsx +74 -0
- package/components/certificate.jsx +287 -300
- package/components/footer.tsx +2 -0
- package/components/hero.tsx +47 -4
- package/components/icons.tsx +45 -0
- package/components/importantNews.js +168 -168
- package/components/navbar.tsx +113 -18
- package/components/updates.tsx +36 -26
- package/config/firebase-admin.js +14 -17
- package/config/firebase.ts +4 -2
- package/config/site.ts +10 -2
- package/firestore.rules +6 -1
- package/next-sitemap.config.js +21 -0
- package/package.json +4 -3
- package/public/corner1-01.svg +0 -0
- package/public/love-birds.png +0 -0
- package/public/next.svg +0 -0
- package/public/pubqr.png +0 -0
- package/public/pw/sample.jpg +0 -0
- package/public/qr.png +0 -0
- package/public/sample.jpg +0 -0
- package/public/vercel.svg +0 -0
- package/vercel.json +1 -0
- package/.recover +0 -9
- package/next-env.d.ts +0 -6
- package/public/DCV.gif +0 -0
- package/public/DCV2.gif +0 -0
- package/public/DCV3.gif +0 -0
- package/public/Images/1.jpg +0 -0
- package/public/Images/11.jpg +0 -0
- package/public/Images/12.jpg +0 -0
- package/public/Images/13.jpg +0 -0
- package/public/Images/14.jpg +0 -0
- package/public/Images/15.jpg +0 -0
- package/public/Images/16.jpg +0 -0
- package/public/Images/17.jpg +0 -0
- package/public/Images/18.jpg +0 -0
- package/public/Images/19.jpg +0 -0
- package/public/Images/2.jpg +0 -0
- package/public/Images/21.jpg +0 -0
- package/public/Images/22.jpg +0 -0
- package/public/Images/3.jpg +0 -0
- package/public/Images/4.jpg +0 -0
- package/public/Images/5.jpg +0 -0
- package/public/Images/6.jpg +0 -0
- package/public/Images/7.jpg +0 -0
- package/public/Images/8.jpg +0 -0
- package/public/Images/9.jpg +0 -0
- package/public/Images/9b.jpg +0 -0
- package/public/Images/Patipatra.jpeg +0 -0
- package/public/audio (1).mp3 +0 -0
- package/public/audio (2).mp3 +0 -0
- package/public/bride.jpg +0 -0
- package/public/groom.jpg +0 -0
- package/public/invite.png +0 -0
- package/public/pw/001.jpg +0 -0
- package/public/pw/002.jpg +0 -0
- package/public/pw/003.jpg +0 -0
- package/public/pw/004.jpg +0 -0
- package/public/pw/005.jpg +0 -0
- package/public/pw/006.jpg +0 -0
- package/public/pw/007.jpg +0 -0
- package/public/pw/008.jpg +0 -0
- package/public/pw/009.jpg +0 -0
- package/public/pw/010.jpg +0 -0
- package/public/pw/011.jpg +0 -0
- package/public/pw/012.jpg +0 -0
- package/public/pw/013.jpg +0 -0
- package/public/pw/014.jpg +0 -0
- package/public/pw/015.jpg +0 -0
- package/public/pw/016.jpg +0 -0
- package/public/pw/017.jpg +0 -0
- package/public/pw/018.jpg +0 -0
- package/public/pw/019.jpg +0 -0
- package/public/pw/020.jpg +0 -0
- package/public/pw/021.jpg +0 -0
- package/public/pw/022.jpg +0 -0
- package/public/pw/023.jpg +0 -0
- package/public/pw/024.jpg +0 -0
- package/public/pw/025.jpg +0 -0
- package/public/pw/026.jpg +0 -0
- package/public/pw/027.jpg +0 -0
- package/public/pw/028.jpg +0 -0
- package/public/pw/029.jpg +0 -0
- package/public/pw/030.jpg +0 -0
- package/public/pw/031.jpg +0 -0
- package/public/pw/032.jpg +0 -0
- package/tsconfig.tsbuildinfo +0 -1
- /package/public/Images/{20.jpg → sample.jpg} +0 -0
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, use } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getFirestore,
|
|
6
|
+
doc,
|
|
7
|
+
getDoc,
|
|
8
|
+
addDoc,
|
|
9
|
+
collection,
|
|
10
|
+
serverTimestamp,
|
|
11
|
+
query,
|
|
12
|
+
where,
|
|
13
|
+
getDocs,
|
|
14
|
+
} from "firebase/firestore";
|
|
5
15
|
import {
|
|
6
16
|
Card,
|
|
7
17
|
CardBody,
|
|
@@ -22,7 +32,7 @@ import { motion, AnimatePresence } from "framer-motion";
|
|
|
22
32
|
|
|
23
33
|
import { fontCursive, fontSans, fontMono } from "@/config/fonts";
|
|
24
34
|
import firebaseApp from "@/config/firebase";
|
|
25
|
-
import { MapPinIcon, CalendarIcon, ClockIcon } from "@/components/icons";
|
|
35
|
+
import { MapPinIcon, CalendarIcon, ClockIcon, CheckIcon } from "@/components/icons";
|
|
26
36
|
|
|
27
37
|
export default function InvitationPage({
|
|
28
38
|
params,
|
|
@@ -36,22 +46,35 @@ export default function InvitationPage({
|
|
|
36
46
|
const [notFound, setNotFound] = useState(false);
|
|
37
47
|
const [isTranslating, setIsTranslating] = useState(false);
|
|
38
48
|
const [translatedData, setTranslatedData] = useState<any>(null);
|
|
39
|
-
|
|
49
|
+
|
|
40
50
|
// RSVP States
|
|
41
51
|
const [showRSVP, setShowRSVP] = useState(false);
|
|
42
|
-
const [rsvpData, setRsvpData] = useState({
|
|
52
|
+
const [rsvpData, setRsvpData] = useState({
|
|
53
|
+
attending: "yes",
|
|
54
|
+
guests: 1,
|
|
55
|
+
food: "non-veg",
|
|
56
|
+
note: "",
|
|
57
|
+
captcha: "",
|
|
58
|
+
});
|
|
43
59
|
const [rsvpLoading, setRsvpLoading] = useState(false);
|
|
44
60
|
const [hasRSVPed, setHasRSVPed] = useState(false);
|
|
61
|
+
const [hasEmailReminder, setHasEmailReminder] = useState(false);
|
|
45
62
|
|
|
46
63
|
const handleRSVP = async () => {
|
|
47
64
|
if (rsvpData.captcha.trim() !== "10") {
|
|
48
|
-
addToast({
|
|
65
|
+
addToast({
|
|
66
|
+
title: "Security Check",
|
|
67
|
+
description: "Please answer correctly (Hint: 10).",
|
|
68
|
+
color: "warning",
|
|
69
|
+
});
|
|
70
|
+
|
|
49
71
|
return;
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
setRsvpLoading(true);
|
|
53
75
|
try {
|
|
54
76
|
const db = getFirestore(firebaseApp());
|
|
77
|
+
|
|
55
78
|
await addDoc(collection(db, "rsvps"), {
|
|
56
79
|
guestId: slug,
|
|
57
80
|
guestName: guest.name,
|
|
@@ -59,17 +82,61 @@ export default function InvitationPage({
|
|
|
59
82
|
timestamp: serverTimestamp(),
|
|
60
83
|
});
|
|
61
84
|
setHasRSVPed(true);
|
|
62
|
-
addToast({
|
|
85
|
+
addToast({
|
|
86
|
+
title: "RSVP Received",
|
|
87
|
+
description: "Thank you for confirming!",
|
|
88
|
+
color: "success",
|
|
89
|
+
});
|
|
63
90
|
} catch (err) {
|
|
64
91
|
console.error("RSVP error", err);
|
|
65
|
-
addToast({
|
|
92
|
+
addToast({
|
|
93
|
+
title: "Error",
|
|
94
|
+
description: "Failed to send RSVP. Please try again.",
|
|
95
|
+
color: "danger",
|
|
96
|
+
});
|
|
66
97
|
} finally {
|
|
67
98
|
setRsvpLoading(false);
|
|
68
99
|
}
|
|
69
100
|
};
|
|
70
101
|
|
|
71
102
|
const translateContent = async () => {
|
|
72
|
-
|
|
103
|
+
if (translatedData) {
|
|
104
|
+
setTranslatedData(null);
|
|
105
|
+
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!guest) return;
|
|
110
|
+
|
|
111
|
+
setIsTranslating(true);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Simulate network delay for effect
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
116
|
+
|
|
117
|
+
const relationMap: Record<string, string> = {
|
|
118
|
+
family: "আত্মীয়",
|
|
119
|
+
friend: "বন্ধু",
|
|
120
|
+
colleague: "সহকর্মী",
|
|
121
|
+
other: "শুভাকাঙ্ক্ষী",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
setTranslatedData({
|
|
125
|
+
guestName: guest.name || "অতিথি",
|
|
126
|
+
familySide: guest.familySide,
|
|
127
|
+
relation: relationMap[guest.relation?.toLowerCase()] || guest.relation || "শুভাকাঙ্ক্ষী",
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("Translation error:", error);
|
|
131
|
+
addToast({
|
|
132
|
+
title: "অনুবাদ ব্যর্থ হয়েছে",
|
|
133
|
+
description: "দুঃখিত, অনুবাদ করার সময় একটি সমস্যা হয়েছে।",
|
|
134
|
+
color: "danger",
|
|
135
|
+
});
|
|
136
|
+
setTranslatedData(null);
|
|
137
|
+
} finally {
|
|
138
|
+
setIsTranslating(false);
|
|
139
|
+
}
|
|
73
140
|
};
|
|
74
141
|
|
|
75
142
|
useEffect(() => {
|
|
@@ -81,11 +148,24 @@ export default function InvitationPage({
|
|
|
81
148
|
|
|
82
149
|
if (docSnap.exists()) {
|
|
83
150
|
const guestData = docSnap.data();
|
|
151
|
+
|
|
84
152
|
setGuest(guestData);
|
|
153
|
+
|
|
154
|
+
// Check for existing email reminders
|
|
155
|
+
const remindersQuery = query(
|
|
156
|
+
collection(db, "email-reminders"),
|
|
157
|
+
where("guestId", "==", slug),
|
|
158
|
+
);
|
|
159
|
+
const remindersSnap = await getDocs(remindersQuery);
|
|
160
|
+
|
|
161
|
+
if (!remindersSnap.empty) {
|
|
162
|
+
setHasEmailReminder(true);
|
|
163
|
+
}
|
|
85
164
|
} else {
|
|
86
165
|
setNotFound(true);
|
|
87
166
|
}
|
|
88
167
|
} catch (error) {
|
|
168
|
+
console.error("Fetch error:", error);
|
|
89
169
|
setNotFound(true);
|
|
90
170
|
} finally {
|
|
91
171
|
setLoading(false);
|
|
@@ -97,31 +177,31 @@ export default function InvitationPage({
|
|
|
97
177
|
|
|
98
178
|
const events: any = {
|
|
99
179
|
registration: {
|
|
100
|
-
title: "Engagement Ceremony",
|
|
101
|
-
date: "23rd November 2025",
|
|
102
|
-
venue: "Srerampore",
|
|
180
|
+
title: translatedData ? "শুভ আংটি বদল" : "Engagement Ceremony",
|
|
181
|
+
date: translatedData ? "২৩শে নভেম্বর, ২০২৫" : "23rd November 2025",
|
|
182
|
+
venue: translatedData ? "শ্রীরামপুর" : "Srerampore",
|
|
103
183
|
map: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d229.96354422747754!2d88.34617843336005!3d22.749911773448982!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x39f89b1ee7f14647%3A0xe44dfacd65bb5f48!2sMohan%20Joyti%20Banquet%20Hall!5e0!3m2!1sen!2sin!4v1758679491438!5m2!1sen!2sin",
|
|
104
184
|
mapHref: "https://maps.app.goo.gl/diSeycey1hyqqtAu8",
|
|
105
|
-
time: "10:00 AM",
|
|
106
|
-
direction: "Near Battala",
|
|
185
|
+
time: translatedData ? "সকাল ১০:০০টা" : "10:00 AM",
|
|
186
|
+
direction: translatedData ? "বটতলার কাছে" : "Near Battala",
|
|
107
187
|
},
|
|
108
188
|
wedding: {
|
|
109
|
-
title: "Wedding Ceremony",
|
|
110
|
-
date: "23rd January 2026",
|
|
111
|
-
venue: "Serampore",
|
|
189
|
+
title: translatedData ? "শুভ বিবাহ" : "Wedding Ceremony",
|
|
190
|
+
date: translatedData ? "২৩শে জানুয়ারি, ২০২৬" : "23rd January 2026",
|
|
191
|
+
venue: translatedData ? "শ্রীরামপুর" : "Serampore",
|
|
112
192
|
map: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3679.3779465759876!2d88.33077587399126!3d22.75135112639763!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x39f89b287ddfb39b%3A0xc67348083f7cea9d!2sAnandamayee%20Bhawan!5e0!3m2!1sen!2sin!4v1765848206729!5m2!1sen!2sin",
|
|
113
193
|
mapHref: "https://maps.app.goo.gl/G5R3bkcTwwa2d54R8",
|
|
114
|
-
time: "06:00 PM",
|
|
115
|
-
direction: "Near Belting Bazar",
|
|
194
|
+
time: translatedData ? "সন্ধ্যা ০৬:০০টা" : "06:00 PM",
|
|
195
|
+
direction: translatedData ? "বেল্টিং বাজারের কাছে" : "Near Belting Bazar",
|
|
116
196
|
},
|
|
117
197
|
reception: {
|
|
118
|
-
title: "Reception Celebration",
|
|
119
|
-
date: "25th January 2026",
|
|
120
|
-
venue: "Konnagar",
|
|
198
|
+
title: translatedData ? "প্রীতিভোজ" : "Reception Celebration",
|
|
199
|
+
date: translatedData ? "২৫শে জানুয়ারি, ২০২৬" : "25th January 2026",
|
|
200
|
+
venue: translatedData ? "কোন্নগর" : "Konnagar",
|
|
121
201
|
map: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3680.8284397695707!2d88.35295167398938!3d22.697429628386114!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x39f89c85a91d38fd%3A0x19dcd2c61895f79f!2sKonnagar%20Friends%20Union%20Club%20Community%20Centre%2FGangadhar%20Chatterjee%20Bhaban!5e0!3m2!1sen!2sin!4v1765848530552!5m2!1sen!2sin",
|
|
122
202
|
mapHref: "https://maps.app.goo.gl/ubdTsy6tnYMsvSSXA",
|
|
123
|
-
time: "06:00 PM",
|
|
124
|
-
direction: "Near GT Road",
|
|
203
|
+
time: translatedData ? "সন্ধ্যা ০৬:০০টা" : "06:00 PM",
|
|
204
|
+
direction: translatedData ? "জি.টি. রোডের কাছে" : "Near GT Road",
|
|
125
205
|
},
|
|
126
206
|
};
|
|
127
207
|
|
|
@@ -136,7 +216,7 @@ export default function InvitationPage({
|
|
|
136
216
|
<p className="text-default-600 dark:text-gray-300">{event.date}</p>
|
|
137
217
|
</div>
|
|
138
218
|
</div>
|
|
139
|
-
|
|
219
|
+
|
|
140
220
|
<div className="flex items-start gap-4">
|
|
141
221
|
<ClockIcon className="w-6 h-6 text-wedding-gold-500 mt-1" />
|
|
142
222
|
<div>
|
|
@@ -149,16 +229,18 @@ export default function InvitationPage({
|
|
|
149
229
|
<MapPinIcon className="w-6 h-6 text-indigo-500 mt-1" />
|
|
150
230
|
<div>
|
|
151
231
|
<p className="font-bold text-default-800 dark:text-white">Venue</p>
|
|
152
|
-
<p className="text-default-600 dark:text-gray-300 font-medium">
|
|
232
|
+
<p className="text-default-600 dark:text-gray-300 font-medium">
|
|
233
|
+
{event.venue}
|
|
234
|
+
</p>
|
|
153
235
|
<p className="text-default-500 text-sm mt-1">{event.direction}</p>
|
|
154
236
|
</div>
|
|
155
237
|
</div>
|
|
156
238
|
|
|
157
239
|
<Button
|
|
158
|
-
as={Link}
|
|
159
|
-
href={event.mapHref}
|
|
160
240
|
isExternal
|
|
241
|
+
as={Link}
|
|
161
242
|
className="w-full bg-wedding-pink-100 text-wedding-pink-700 font-semibold hover:bg-wedding-pink-200 dark:bg-wedding-pink-900/30 dark:text-wedding-pink-200 mt-4"
|
|
243
|
+
href={event.mapHref}
|
|
162
244
|
startContent={<MapPinIcon className="w-4 h-4" />}
|
|
163
245
|
>
|
|
164
246
|
Open in Google Maps
|
|
@@ -168,12 +250,12 @@ export default function InvitationPage({
|
|
|
168
250
|
{/* Map Embed */}
|
|
169
251
|
<div className="h-64 md:h-auto rounded-2xl overflow-hidden shadow-lg border border-default-200 dark:border-default-700">
|
|
170
252
|
<iframe
|
|
171
|
-
|
|
253
|
+
allowFullScreen
|
|
172
254
|
className="w-full h-full"
|
|
173
|
-
src={event.map}
|
|
174
255
|
loading="lazy"
|
|
175
|
-
|
|
176
|
-
|
|
256
|
+
src={event.map}
|
|
257
|
+
title={`${event.title} Map`}
|
|
258
|
+
/>
|
|
177
259
|
</div>
|
|
178
260
|
</div>
|
|
179
261
|
);
|
|
@@ -218,7 +300,7 @@ export default function InvitationPage({
|
|
|
218
300
|
<p
|
|
219
301
|
className={`${fontCursive.className} text-4xl md:text-7xl font-bold drop-shadow-md px-2`}
|
|
220
302
|
>
|
|
221
|
-
|
|
303
|
+
Groom & Bride
|
|
222
304
|
</p>
|
|
223
305
|
<p
|
|
224
306
|
className={`${fontMono.className} mt-2 md:mt-4 text-[10px] md:text-base uppercase tracking-widest opacity-90 px-4`}
|
|
@@ -228,11 +310,11 @@ export default function InvitationPage({
|
|
|
228
310
|
</div>
|
|
229
311
|
{/* Translation Button */}
|
|
230
312
|
<Button
|
|
313
|
+
className="absolute bottom-2 right-2 bg-white/20 text-white backdrop-blur-md border border-white/30 text-[10px] h-7 px-2"
|
|
314
|
+
isLoading={isTranslating}
|
|
231
315
|
size="sm"
|
|
232
316
|
variant="flat"
|
|
233
317
|
onPress={translateContent}
|
|
234
|
-
isLoading={isTranslating}
|
|
235
|
-
className="absolute bottom-2 right-2 bg-white/20 text-white backdrop-blur-md border border-white/30 text-[10px] h-7 px-2"
|
|
236
318
|
>
|
|
237
319
|
{translatedData ? "See English" : "বাংলায় দেখুন"}
|
|
238
320
|
</Button>
|
|
@@ -244,9 +326,12 @@ export default function InvitationPage({
|
|
|
244
326
|
<p
|
|
245
327
|
className={`${fontCursive.className} text-3xl md:text-5xl text-wedding-pink-600 dark:text-wedding-pink-400 mb-4 md:mb-6`}
|
|
246
328
|
>
|
|
247
|
-
{translatedData ? "প্রিয়" : "Dear"}
|
|
329
|
+
{translatedData ? "প্রিয়" : "Dear"}{" "}
|
|
330
|
+
{translatedData?.guestName || guest.name}
|
|
248
331
|
{guest.invitedGuests > 1 && (
|
|
249
|
-
<span className="text-xl md:text-3xl ml-2">
|
|
332
|
+
<span className="text-xl md:text-3xl ml-2">
|
|
333
|
+
{translatedData ? "& পরিবার" : "& Family"}
|
|
334
|
+
</span>
|
|
250
335
|
)}
|
|
251
336
|
,
|
|
252
337
|
</p>
|
|
@@ -257,7 +342,8 @@ export default function InvitationPage({
|
|
|
257
342
|
<p>
|
|
258
343
|
{translatedData ? (
|
|
259
344
|
<>
|
|
260
|
-
{translatedData.welcomeNote ||
|
|
345
|
+
{translatedData.welcomeNote ||
|
|
346
|
+
`${translatedData.familySide === "bride" ? "কনে" : "বর"}-র পক্ষ থেকে একজন প্রিয় ${translatedData.relation} হিসেবে আপনার উপস্থিতি আমাদের কাছে অনেক গুরুত্বপূর্ণ।`}
|
|
261
347
|
</>
|
|
262
348
|
) : (
|
|
263
349
|
<>
|
|
@@ -271,7 +357,9 @@ export default function InvitationPage({
|
|
|
271
357
|
)}
|
|
272
358
|
</p>
|
|
273
359
|
<p>
|
|
274
|
-
{translatedData
|
|
360
|
+
{translatedData
|
|
361
|
+
? "আমরা আপনাকে নিম্নলিখিত অনুষ্ঠানে যোগ দেওয়ার জন্য সাদর আমন্ত্রণ জানাচ্ছি:"
|
|
362
|
+
: `We warmly invite you to join us for the following celebration${guest.invitedFor.length > 1 ? "s" : ""}:`}
|
|
275
363
|
</p>
|
|
276
364
|
</div>
|
|
277
365
|
</div>
|
|
@@ -282,14 +370,16 @@ export default function InvitationPage({
|
|
|
282
370
|
<div className="hidden md:block">
|
|
283
371
|
<Tabs
|
|
284
372
|
aria-label="Wedding Events"
|
|
285
|
-
color="danger"
|
|
286
|
-
variant="underlined"
|
|
287
373
|
classNames={{
|
|
288
|
-
tabList:
|
|
374
|
+
tabList:
|
|
375
|
+
"gap-6 w-full relative rounded-none p-0 border-b border-divider",
|
|
289
376
|
cursor: "w-full bg-wedding-pink-500",
|
|
290
377
|
tab: "max-w-fit px-0 h-12",
|
|
291
|
-
tabContent:
|
|
378
|
+
tabContent:
|
|
379
|
+
"group-data-[selected=true]:text-wedding-pink-500 font-medium text-lg",
|
|
292
380
|
}}
|
|
381
|
+
color="danger"
|
|
382
|
+
variant="underlined"
|
|
293
383
|
>
|
|
294
384
|
{guest.invitedFor.map((eventKey: string) => (
|
|
295
385
|
<Tab key={eventKey} title={events[eventKey].title}>
|
|
@@ -301,22 +391,22 @@ export default function InvitationPage({
|
|
|
301
391
|
|
|
302
392
|
{/* Mobile Accordion */}
|
|
303
393
|
<div className="md:hidden text-left">
|
|
304
|
-
<Accordion
|
|
305
|
-
variant="splitted"
|
|
306
|
-
selectionMode="multiple"
|
|
307
|
-
defaultExpandedKeys={guest.invitedFor}
|
|
394
|
+
<Accordion
|
|
308
395
|
className="px-0"
|
|
396
|
+
defaultExpandedKeys={guest.invitedFor}
|
|
397
|
+
selectionMode="multiple"
|
|
398
|
+
variant="splitted"
|
|
309
399
|
>
|
|
310
400
|
{guest.invitedFor.map((eventKey: string) => (
|
|
311
401
|
<AccordionItem
|
|
312
402
|
key={eventKey}
|
|
313
403
|
aria-label={events[eventKey].title}
|
|
404
|
+
className="bg-default-50 dark:bg-default-100/5 border border-default-100 dark:border-default-800 rounded-xl shadow-sm mb-4"
|
|
314
405
|
title={
|
|
315
406
|
<span className="font-bold text-wedding-pink-600 dark:text-wedding-pink-400">
|
|
316
407
|
{events[eventKey].title}
|
|
317
408
|
</span>
|
|
318
409
|
}
|
|
319
|
-
className="bg-default-50 dark:bg-default-100/5 border border-default-100 dark:border-default-800 rounded-xl shadow-sm mb-4"
|
|
320
410
|
>
|
|
321
411
|
{renderEventDetails(events[eventKey])}
|
|
322
412
|
</AccordionItem>
|
|
@@ -332,92 +422,157 @@ export default function InvitationPage({
|
|
|
332
422
|
<AnimatePresence mode="wait">
|
|
333
423
|
{hasRSVPed ? (
|
|
334
424
|
<motion.div
|
|
335
|
-
initial={{ opacity: 0, scale: 0.9 }}
|
|
336
425
|
animate={{ opacity: 1, scale: 1 }}
|
|
337
426
|
className="bg-green-50 dark:bg-green-900/20 p-6 md:p-8 rounded-3xl border border-green-100 dark:border-green-800/30 text-center"
|
|
427
|
+
initial={{ opacity: 0, scale: 0.9 }}
|
|
338
428
|
>
|
|
339
|
-
<p
|
|
340
|
-
|
|
429
|
+
<p
|
|
430
|
+
className={`${fontCursive.className} text-2xl md:text-3xl text-green-600 dark:text-green-400 mb-2`}
|
|
431
|
+
>
|
|
432
|
+
Thank You!
|
|
433
|
+
</p>
|
|
434
|
+
<p className="text-sm md:text-green-700 dark:text-green-300 font-medium">
|
|
435
|
+
We have received your RSVP and can't wait to see you.
|
|
436
|
+
</p>
|
|
341
437
|
</motion.div>
|
|
342
438
|
) : !showRSVP ? (
|
|
343
|
-
<Button
|
|
344
|
-
onPress={() => setShowRSVP(true)}
|
|
439
|
+
<Button
|
|
345
440
|
className="bg-wedding-pink-500 text-white font-bold h-12 md:h-14 px-8 md:px-10 text-base md:text-lg rounded-full shadow-lg shadow-wedding-pink-500/30 w-full md:w-auto"
|
|
441
|
+
onPress={() => setShowRSVP(true)}
|
|
346
442
|
>
|
|
347
443
|
RSVP / Confirm Attendance
|
|
348
444
|
</Button>
|
|
349
445
|
) : (
|
|
350
446
|
<motion.div
|
|
351
|
-
initial={{ opacity: 0, y: 20 }}
|
|
352
447
|
animate={{ opacity: 1, y: 0 }}
|
|
353
448
|
className="space-y-6 md:space-y-8 text-left bg-default-50 dark:bg-zinc-900/50 p-5 md:p-10 rounded-[24px] md:rounded-[32px] border border-default-200 dark:border-default-800"
|
|
449
|
+
initial={{ opacity: 0, y: 20 }}
|
|
354
450
|
>
|
|
355
451
|
<div className="flex justify-between items-center">
|
|
356
|
-
|
|
357
|
-
|
|
452
|
+
<h3
|
|
453
|
+
className={`${fontSans.className} text-xl md:text-2xl font-bold text-default-800 dark:text-white`}
|
|
454
|
+
>
|
|
455
|
+
RSVP Details
|
|
456
|
+
</h3>
|
|
457
|
+
<Button
|
|
458
|
+
isIconOnly
|
|
459
|
+
size="sm"
|
|
460
|
+
variant="light"
|
|
461
|
+
onPress={() => setShowRSVP(false)}
|
|
462
|
+
>
|
|
463
|
+
✕
|
|
464
|
+
</Button>
|
|
358
465
|
</div>
|
|
359
466
|
|
|
360
467
|
<div className="grid md:grid-cols-2 gap-6 md:gap-8">
|
|
361
468
|
<RadioGroup
|
|
469
|
+
classNames={{
|
|
470
|
+
label:
|
|
471
|
+
"text-wedding-pink-600 font-bold mb-1 text-sm md:text-base",
|
|
472
|
+
}}
|
|
362
473
|
label="Will you attend?"
|
|
363
474
|
value={rsvpData.attending}
|
|
364
|
-
onValueChange={(val) =>
|
|
365
|
-
|
|
475
|
+
onValueChange={(val) =>
|
|
476
|
+
setRsvpData({ ...rsvpData, attending: val })
|
|
477
|
+
}
|
|
366
478
|
>
|
|
367
|
-
<Radio
|
|
368
|
-
|
|
479
|
+
<Radio classNames={{ label: "text-sm" }} value="yes">
|
|
480
|
+
Yes, I'll be there!
|
|
481
|
+
</Radio>
|
|
482
|
+
<Radio classNames={{ label: "text-sm" }} value="no">
|
|
483
|
+
Sorry, I can't make it
|
|
484
|
+
</Radio>
|
|
369
485
|
</RadioGroup>
|
|
370
486
|
|
|
371
487
|
{rsvpData.attending === "yes" && (
|
|
372
488
|
<RadioGroup
|
|
489
|
+
classNames={{
|
|
490
|
+
label:
|
|
491
|
+
"text-wedding-pink-600 font-bold mb-1 text-sm md:text-base",
|
|
492
|
+
}}
|
|
373
493
|
label="Food Preference"
|
|
374
494
|
value={rsvpData.food}
|
|
375
|
-
onValueChange={(val) =>
|
|
376
|
-
|
|
495
|
+
onValueChange={(val) =>
|
|
496
|
+
setRsvpData({ ...rsvpData, food: val })
|
|
497
|
+
}
|
|
377
498
|
>
|
|
378
|
-
<Radio
|
|
379
|
-
|
|
499
|
+
<Radio
|
|
500
|
+
classNames={{ label: "text-sm" }}
|
|
501
|
+
value="non-veg"
|
|
502
|
+
>
|
|
503
|
+
Non-Vegetarian
|
|
504
|
+
</Radio>
|
|
505
|
+
<Radio classNames={{ label: "text-sm" }} value="veg">
|
|
506
|
+
Vegetarian
|
|
507
|
+
</Radio>
|
|
380
508
|
</RadioGroup>
|
|
381
509
|
)}
|
|
382
510
|
</div>
|
|
383
511
|
|
|
384
512
|
<div className="space-y-5 md:space-y-6">
|
|
385
|
-
<Input
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
513
|
+
<Input
|
|
514
|
+
classNames={{
|
|
515
|
+
label:
|
|
516
|
+
"text-wedding-pink-600 font-bold text-xs md:text-sm",
|
|
517
|
+
inputWrapper: "border-wedding-pink-100",
|
|
518
|
+
input: "outline-none text-sm",
|
|
519
|
+
}}
|
|
520
|
+
label="Number of Guests"
|
|
389
521
|
labelPlacement="outside-top"
|
|
522
|
+
type="number"
|
|
390
523
|
value={String(rsvpData.guests)}
|
|
391
|
-
onChange={(e) => setRsvpData({ ...rsvpData, guests: Number(e.target.value) })}
|
|
392
|
-
classNames={{ label: "text-wedding-pink-600 font-bold text-xs md:text-sm", inputWrapper: "border-wedding-pink-100", input: "outline-none text-sm" }}
|
|
393
|
-
/>
|
|
394
|
-
<Input
|
|
395
|
-
label="Special Note / Allergies"
|
|
396
524
|
variant="bordered"
|
|
525
|
+
onChange={(e) =>
|
|
526
|
+
setRsvpData({
|
|
527
|
+
...rsvpData,
|
|
528
|
+
guests: Number(e.target.value),
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
/>
|
|
532
|
+
<Input
|
|
533
|
+
classNames={{
|
|
534
|
+
label:
|
|
535
|
+
"text-wedding-pink-600 font-bold text-xs md:text-sm",
|
|
536
|
+
inputWrapper: "border-wedding-pink-100",
|
|
537
|
+
input: "outline-none text-sm",
|
|
538
|
+
}}
|
|
539
|
+
label="Special Note / Allergies"
|
|
397
540
|
labelPlacement="outside-top"
|
|
398
541
|
value={rsvpData.note}
|
|
399
|
-
|
|
400
|
-
|
|
542
|
+
variant="bordered"
|
|
543
|
+
onChange={(e) =>
|
|
544
|
+
setRsvpData({ ...rsvpData, note: e.target.value })
|
|
545
|
+
}
|
|
401
546
|
/>
|
|
402
|
-
|
|
547
|
+
|
|
403
548
|
<div className="p-4 bg-wedding-pink-50 dark:bg-wedding-pink-900/10 rounded-xl border border-wedding-pink-100 dark:border-wedding-pink-800/30">
|
|
404
|
-
<p className="text-[9px] font-black text-wedding-pink-600 uppercase tracking-widest mb-1">
|
|
405
|
-
|
|
549
|
+
<p className="text-[9px] font-black text-wedding-pink-600 uppercase tracking-widest mb-1">
|
|
550
|
+
Bot Protection
|
|
551
|
+
</p>
|
|
552
|
+
<Input
|
|
406
553
|
isRequired
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
554
|
+
classNames={{
|
|
555
|
+
label: "text-[10px] md:text-xs",
|
|
556
|
+
input: "font-bold outline-none",
|
|
557
|
+
}}
|
|
558
|
+
label="How many years have we been together?"
|
|
410
559
|
labelPlacement="outside-top"
|
|
560
|
+
placeholder="Answer in numbers"
|
|
411
561
|
value={rsvpData.captcha}
|
|
412
|
-
|
|
413
|
-
|
|
562
|
+
variant="underlined"
|
|
563
|
+
onChange={(e) =>
|
|
564
|
+
setRsvpData({
|
|
565
|
+
...rsvpData,
|
|
566
|
+
captcha: e.target.value,
|
|
567
|
+
})
|
|
568
|
+
}
|
|
414
569
|
/>
|
|
415
570
|
</div>
|
|
416
571
|
|
|
417
|
-
<Button
|
|
572
|
+
<Button
|
|
573
|
+
className="w-full bg-gradient-to-r from-wedding-pink-500 to-wedding-gold-500 text-white font-black h-12 md:h-14 text-base md:text-lg rounded-full"
|
|
418
574
|
isLoading={rsvpLoading}
|
|
419
575
|
onPress={handleRSVP}
|
|
420
|
-
className="w-full bg-gradient-to-r from-wedding-pink-500 to-wedding-gold-500 text-white font-black h-12 md:h-14 text-base md:text-lg rounded-full"
|
|
421
576
|
>
|
|
422
577
|
Confirm RSVP
|
|
423
578
|
</Button>
|
|
@@ -427,6 +582,62 @@ export default function InvitationPage({
|
|
|
427
582
|
</AnimatePresence>
|
|
428
583
|
</div>
|
|
429
584
|
|
|
585
|
+
<Divider className="my-8 md:my-10 opacity-50" />
|
|
586
|
+
|
|
587
|
+
{/* Email Reminder Section */}
|
|
588
|
+
<div className="mb-10 px-0 md:px-10">
|
|
589
|
+
<div className="bg-default-50 dark:bg-zinc-900/50 p-6 rounded-2xl border border-default-200 dark:border-default-800">
|
|
590
|
+
<h3 className={`${fontSans.className} text-xl font-bold text-default-800 dark:text-white mb-4`}>
|
|
591
|
+
Get Event Reminders
|
|
592
|
+
</h3>
|
|
593
|
+
{hasEmailReminder && (
|
|
594
|
+
<div className="mb-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl border border-blue-100 dark:border-blue-800/30 flex items-center justify-center gap-2">
|
|
595
|
+
<CheckIcon className="w-4 h-4 text-blue-500" />
|
|
596
|
+
<p className="text-xs font-medium text-blue-700 dark:text-blue-300">
|
|
597
|
+
Email reminder already set for your invitation.
|
|
598
|
+
</p>
|
|
599
|
+
</div>
|
|
600
|
+
)}
|
|
601
|
+
<p className="text-default-600 dark:text-gray-400 text-sm mb-6">
|
|
602
|
+
Leave your email to receive a reminder before the event starts.
|
|
603
|
+
</p>
|
|
604
|
+
<form
|
|
605
|
+
action={async (formData) => {
|
|
606
|
+
const { sendEmailReminder } = await import("../actions");
|
|
607
|
+
|
|
608
|
+
formData.append("slug", slug);
|
|
609
|
+
await sendEmailReminder(formData);
|
|
610
|
+
setHasEmailReminder(true);
|
|
611
|
+
addToast({
|
|
612
|
+
title: "Reminder Set",
|
|
613
|
+
description: "We will notify you before the event!",
|
|
614
|
+
color: "success",
|
|
615
|
+
});
|
|
616
|
+
}}
|
|
617
|
+
className="flex flex-col sm:flex-row gap-4 items-end"
|
|
618
|
+
>
|
|
619
|
+
<Input
|
|
620
|
+
isRequired
|
|
621
|
+
className="flex-1"
|
|
622
|
+
classNames={{
|
|
623
|
+
input: "outline-none",
|
|
624
|
+
inputWrapper: "bg-white dark:bg-zinc-900",
|
|
625
|
+
}}
|
|
626
|
+
name="email"
|
|
627
|
+
placeholder="john@example.com"
|
|
628
|
+
type="email"
|
|
629
|
+
aria-label="Email Address"
|
|
630
|
+
/>
|
|
631
|
+
<Button
|
|
632
|
+
className="w-full sm:w-auto bg-wedding-pink-500 text-white font-semibold shadow-md"
|
|
633
|
+
type="submit"
|
|
634
|
+
>
|
|
635
|
+
Notify Me
|
|
636
|
+
</Button>
|
|
637
|
+
</form>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
|
|
430
641
|
<Divider className="my-8 md:my-10 opacity-50" />
|
|
431
642
|
<div className="space-y-6">
|
|
432
643
|
<p
|
|
@@ -443,6 +654,14 @@ export default function InvitationPage({
|
|
|
443
654
|
>
|
|
444
655
|
Meet the Couple
|
|
445
656
|
</Button>
|
|
657
|
+
<Button
|
|
658
|
+
as={Link}
|
|
659
|
+
className="bg-wedding-pink-500 text-white shadow-lg hover:bg-wedding-pink-600 font-semibold w-full sm:w-auto"
|
|
660
|
+
href="/game"
|
|
661
|
+
size="lg"
|
|
662
|
+
>
|
|
663
|
+
Play Couple Quiz
|
|
664
|
+
</Button>
|
|
446
665
|
<Button
|
|
447
666
|
as={Link}
|
|
448
667
|
className="border-wedding-pink-500 text-wedding-pink-500 hover:bg-wedding-pink-50 dark:hover:bg-wedding-pink-900/20 font-semibold w-full sm:w-auto"
|