@titas_mallick/wedding-site-gen 1.0.0
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/.eslintignore +20 -0
- package/.eslintrc.json +93 -0
- package/.recover +9 -0
- package/.vscode/settings.json +3 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/app/Neo-Lucentism/layout.tsx +7 -0
- package/app/Neo-Lucentism/page.tsx +259 -0
- package/app/couple/layout.tsx +7 -0
- package/app/couple/page.tsx +164 -0
- package/app/error.tsx +31 -0
- package/app/guestbook/page.tsx +470 -0
- package/app/invitation/[slug]/layout.tsx +36 -0
- package/app/invitation/[slug]/page.tsx +462 -0
- package/app/invitation/maker/auth.js +165 -0
- package/app/invitation/maker/dashboard.js +81 -0
- package/app/invitation/maker/guestAdder.js +204 -0
- package/app/invitation/maker/guestShower.js +287 -0
- package/app/invitation/maker/layout.tsx +11 -0
- package/app/invitation/maker/page.js +168 -0
- package/app/invitation/maker/rsvpViewer.js +122 -0
- package/app/layout.tsx +98 -0
- package/app/mark-the-dates/layout.tsx +7 -0
- package/app/mark-the-dates/page.tsx +196 -0
- package/app/memories/layout.tsx +7 -0
- package/app/memories/page.tsx +29 -0
- package/app/page.tsx +5 -0
- package/app/providers.tsx +33 -0
- package/app/sagun/layout.tsx +7 -0
- package/app/sagun/page.tsx +348 -0
- package/app/song-requests/page.tsx +354 -0
- package/app/sukanya/layout.tsx +7 -0
- package/app/sukanya/page.tsx +167 -0
- package/app/titas/layout.tsx +7 -0
- package/app/titas/page.tsx +175 -0
- package/app/travel-guide/page.tsx +400 -0
- package/app/updates/maker/page.js +323 -0
- package/app/updates/overlay/page.tsx +144 -0
- package/app/updates/page.js +207 -0
- package/cli.mjs +196 -0
- package/components/ConciergeBot.tsx +203 -0
- package/components/CountdownTimer.tsx +137 -0
- package/components/Gallery.tsx +372 -0
- package/components/LiveVideos.tsx +173 -0
- package/components/OurStory.tsx +160 -0
- package/components/certificate.jsx +300 -0
- package/components/counter.tsx +14 -0
- package/components/footer.tsx +89 -0
- package/components/hero.tsx +136 -0
- package/components/icons.tsx +283 -0
- package/components/importantNews.js +168 -0
- package/components/navbar.tsx +106 -0
- package/components/primitives.ts +53 -0
- package/components/sagun.js +22 -0
- package/components/theme-switch.tsx +81 -0
- package/components/updates.tsx +118 -0
- package/components/weddingcard.js +68 -0
- package/components/weddingcard2.js +58 -0
- package/config/firebase-admin.js +17 -0
- package/config/firebase.ts +36 -0
- package/config/fonts.ts +21 -0
- package/config/site.ts +74 -0
- package/next-env.d.ts +6 -0
- package/next.config.js +4 -0
- package/package.json +64 -0
- package/postcss.config.js +6 -0
- 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/20.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/corner1-01.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/groom.jpg +0 -0
- package/public/invite.png +0 -0
- package/public/love-birds.png +0 -0
- package/public/next.svg +1 -0
- package/public/pubqr.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/public/qr.png +0 -0
- package/public/vercel.svg +1 -0
- package/styles/globals.css +3 -0
- package/tailwind.config.js +51 -0
- package/tsconfig.json +45 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/types/index.ts +5 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardBody,
|
|
8
|
+
Input,
|
|
9
|
+
Button,
|
|
10
|
+
addToast,
|
|
11
|
+
Divider,
|
|
12
|
+
Spinner,
|
|
13
|
+
Table,
|
|
14
|
+
TableHeader,
|
|
15
|
+
TableColumn,
|
|
16
|
+
TableBody,
|
|
17
|
+
TableRow,
|
|
18
|
+
TableCell,
|
|
19
|
+
Chip,
|
|
20
|
+
Tooltip,
|
|
21
|
+
Modal,
|
|
22
|
+
ModalContent,
|
|
23
|
+
ModalHeader,
|
|
24
|
+
ModalBody,
|
|
25
|
+
ModalFooter
|
|
26
|
+
} from "@heroui/react";
|
|
27
|
+
import {
|
|
28
|
+
getFirestore,
|
|
29
|
+
collection,
|
|
30
|
+
addDoc,
|
|
31
|
+
serverTimestamp,
|
|
32
|
+
onSnapshot,
|
|
33
|
+
query,
|
|
34
|
+
orderBy,
|
|
35
|
+
deleteDoc,
|
|
36
|
+
doc,
|
|
37
|
+
updateDoc
|
|
38
|
+
} from "firebase/firestore";
|
|
39
|
+
import { getAuth, onAuthStateChanged } from "firebase/auth";
|
|
40
|
+
import firebaseApp from "@/config/firebase";
|
|
41
|
+
import { fontCursive, fontSans, fontMono } from "@/config/fonts";
|
|
42
|
+
import { HeartFilledIcon } from "@/components/icons";
|
|
43
|
+
|
|
44
|
+
const db = getFirestore(firebaseApp());
|
|
45
|
+
const auth = getAuth(firebaseApp());
|
|
46
|
+
|
|
47
|
+
export default function SongRequestsPage() {
|
|
48
|
+
const [formData, setFormData] = useState({ name: "", song: "", artist: "", captcha: "" });
|
|
49
|
+
const [loading, setLoading] = useState(false);
|
|
50
|
+
const [user, setUser] = useState<any>(null);
|
|
51
|
+
const [requests, setRequests] = useState<any[]>([]);
|
|
52
|
+
const [listLoading, setListLoading] = useState(true);
|
|
53
|
+
|
|
54
|
+
// Delete Confirmation States
|
|
55
|
+
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
56
|
+
const [requestToDelete, setRequestToDelete] = useState<string | null>(null);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const unsubscribeAuth = onAuthStateChanged(auth, (currentUser) => {
|
|
60
|
+
setUser(currentUser);
|
|
61
|
+
});
|
|
62
|
+
return () => unsubscribeAuth();
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const q = query(collection(db, "song_requests"), orderBy("createdAt", "desc"));
|
|
67
|
+
const unsubscribeData = onSnapshot(q, (snapshot) => {
|
|
68
|
+
const data = snapshot.docs.map((doc) => ({
|
|
69
|
+
id: doc.id,
|
|
70
|
+
...doc.data(),
|
|
71
|
+
}));
|
|
72
|
+
setRequests(data);
|
|
73
|
+
setListLoading(false);
|
|
74
|
+
});
|
|
75
|
+
return () => unsubscribeData();
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
|
|
81
|
+
if (formData.captcha.trim() !== "10") {
|
|
82
|
+
addToast({
|
|
83
|
+
title: "Security Check",
|
|
84
|
+
description: "Please answer the question correctly (Hint: 10 years!).",
|
|
85
|
+
color: "warning",
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!formData.name || !formData.song) return;
|
|
91
|
+
|
|
92
|
+
setLoading(true);
|
|
93
|
+
try {
|
|
94
|
+
await addDoc(collection(db, "song_requests"), {
|
|
95
|
+
...formData,
|
|
96
|
+
status: "pending",
|
|
97
|
+
createdAt: serverTimestamp(),
|
|
98
|
+
});
|
|
99
|
+
setFormData({ name: "", song: "", artist: "", captcha: "" });
|
|
100
|
+
addToast({
|
|
101
|
+
title: "Song Requested!",
|
|
102
|
+
description: "We'll try our best to play your favorite track at the Reception.",
|
|
103
|
+
color: "success",
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error("Song request error", err);
|
|
107
|
+
addToast({
|
|
108
|
+
title: "Error",
|
|
109
|
+
description: "Could not save your request. Please try again.",
|
|
110
|
+
color: "danger",
|
|
111
|
+
});
|
|
112
|
+
} finally {
|
|
113
|
+
setLoading(false);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleDeleteClick = (id: string) => {
|
|
118
|
+
setRequestToDelete(id);
|
|
119
|
+
setIsDeleteModalOpen(true);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleConfirmDelete = async () => {
|
|
123
|
+
if (!requestToDelete) return;
|
|
124
|
+
try {
|
|
125
|
+
await deleteDoc(doc(db, "song_requests", requestToDelete));
|
|
126
|
+
addToast({ title: "Deleted", description: "Request removed.", color: "success" });
|
|
127
|
+
} catch (err) {
|
|
128
|
+
addToast({ title: "Error", color: "danger" });
|
|
129
|
+
} finally {
|
|
130
|
+
setIsDeleteModalOpen(false);
|
|
131
|
+
setRequestToDelete(null);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const toggleStatus = async (id: string, currentStatus: string) => {
|
|
136
|
+
try {
|
|
137
|
+
await updateDoc(doc(db, "song_requests", id), {
|
|
138
|
+
status: currentStatus === "played" ? "pending" : "played"
|
|
139
|
+
});
|
|
140
|
+
addToast({ title: "Status Updated", color: "success" });
|
|
141
|
+
} catch (err) {
|
|
142
|
+
addToast({ title: "Error", color: "danger" });
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const isAdmin = user?.email === "titas@titas.titas";
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className="min-h-screen pb-20 pt-10 px-4 max-w-6xl mx-auto flex flex-col items-center">
|
|
150
|
+
{/* Header */}
|
|
151
|
+
<motion.div
|
|
152
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
153
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
154
|
+
className="text-center space-y-6 mb-12"
|
|
155
|
+
>
|
|
156
|
+
<div className="bg-wedding-pink-100 dark:bg-wedding-pink-900/30 p-4 rounded-full w-fit mx-auto">
|
|
157
|
+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-wedding-pink-500">
|
|
158
|
+
<path d="M9 18V5l12-2v13"></path>
|
|
159
|
+
<circle cx="6" cy="18" r="3"></circle>
|
|
160
|
+
<circle cx="18" cy="16" r="3"></circle>
|
|
161
|
+
</svg>
|
|
162
|
+
</div>
|
|
163
|
+
<h1 className={`${fontCursive.className} text-5xl md:text-7xl text-wedding-pink-600 dark:text-wedding-pink-400 py-2`}>
|
|
164
|
+
Reception Playlist
|
|
165
|
+
</h1>
|
|
166
|
+
<p className="text-default-500 max-w-xl mx-auto italic">
|
|
167
|
+
"Music is the soul of our celebration. Tell us which song makes you want to hit the dance floor!"
|
|
168
|
+
</p>
|
|
169
|
+
</motion.div>
|
|
170
|
+
|
|
171
|
+
<div className="grid lg:grid-cols-2 gap-12 w-full items-start">
|
|
172
|
+
{/* Left: Request Form */}
|
|
173
|
+
<Card className="bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl border border-wedding-gold-200 dark:border-wedding-gold-800 shadow-2xl p-6 md:p-8 rounded-[40px]">
|
|
174
|
+
<h2 className={`${fontSans.className} text-2xl font-bold mb-6 text-default-800 dark:text-white`}>Request a Song</h2>
|
|
175
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
176
|
+
<Input
|
|
177
|
+
isRequired
|
|
178
|
+
label="Your Name"
|
|
179
|
+
labelPlacement="outside-top"
|
|
180
|
+
placeholder="Who are we dancing with?"
|
|
181
|
+
variant="bordered"
|
|
182
|
+
value={formData.name}
|
|
183
|
+
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
184
|
+
classNames={{
|
|
185
|
+
label: "text-wedding-pink-600 font-bold",
|
|
186
|
+
input: "outline-none",
|
|
187
|
+
inputWrapper: "border-wedding-pink-100 focus-within:!border-wedding-pink-500 h-14"
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
191
|
+
<Input
|
|
192
|
+
isRequired
|
|
193
|
+
label="Song Title"
|
|
194
|
+
labelPlacement="outside-top"
|
|
195
|
+
placeholder="E.g. Kesariya"
|
|
196
|
+
variant="bordered"
|
|
197
|
+
value={formData.song}
|
|
198
|
+
onChange={(e) => setFormData({ ...formData, song: e.target.value })}
|
|
199
|
+
classNames={{
|
|
200
|
+
label: "text-wedding-pink-600 font-bold",
|
|
201
|
+
input: "outline-none",
|
|
202
|
+
inputWrapper: "border-wedding-pink-100 focus-within:!border-wedding-pink-500 h-14"
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
<Input
|
|
206
|
+
label="Artist (Optional)"
|
|
207
|
+
labelPlacement="outside-top"
|
|
208
|
+
placeholder="E.g. Arijit Singh"
|
|
209
|
+
variant="bordered"
|
|
210
|
+
value={formData.artist}
|
|
211
|
+
onChange={(e) => setFormData({ ...formData, artist: e.target.value })}
|
|
212
|
+
classNames={{
|
|
213
|
+
label: "text-wedding-pink-600 font-bold",
|
|
214
|
+
input: "outline-none",
|
|
215
|
+
inputWrapper: "border-wedding-pink-100 focus-within:!border-wedding-pink-500 h-14"
|
|
216
|
+
}}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<Divider className="opacity-50" />
|
|
221
|
+
|
|
222
|
+
<div className="p-4 bg-wedding-pink-50 dark:bg-wedding-pink-900/10 rounded-2xl border border-wedding-pink-100 dark:border-wedding-pink-800/30">
|
|
223
|
+
<p className="text-[10px] font-bold text-wedding-pink-600 dark:text-wedding-pink-400 uppercase tracking-widest mb-2">Bot Protection</p>
|
|
224
|
+
<Input
|
|
225
|
+
isRequired
|
|
226
|
+
label="How many years have we been together?"
|
|
227
|
+
labelPlacement="outside-top"
|
|
228
|
+
placeholder="Answer in numbers"
|
|
229
|
+
variant="underlined"
|
|
230
|
+
value={formData.captcha}
|
|
231
|
+
onChange={(e) => setFormData({ ...formData, captcha: e.target.value })}
|
|
232
|
+
classNames={{
|
|
233
|
+
label: "text-default-600 text-xs",
|
|
234
|
+
input: "text-center font-bold text-xl outline-none",
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<Button
|
|
240
|
+
type="submit"
|
|
241
|
+
isLoading={loading}
|
|
242
|
+
className="w-full bg-gradient-to-r from-wedding-pink-500 to-wedding-gold-500 text-white font-black h-14 text-lg shadow-xl shadow-wedding-pink-500/20"
|
|
243
|
+
>
|
|
244
|
+
Submit Song Request
|
|
245
|
+
</Button>
|
|
246
|
+
</form>
|
|
247
|
+
</Card>
|
|
248
|
+
|
|
249
|
+
{/* Right: Requests Feed */}
|
|
250
|
+
<div className="space-y-6">
|
|
251
|
+
<div className="flex items-center justify-between px-2">
|
|
252
|
+
<h2 className={`${fontSans.className} text-2xl font-bold text-default-800 dark:text-white`}>The Queue</h2>
|
|
253
|
+
<Chip color="secondary" variant="flat" size="sm" className={fontMono.className}>
|
|
254
|
+
{requests.length} Total
|
|
255
|
+
</Chip>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div className="space-y-4 max-h-[600px] overflow-y-auto pr-2 custom-scrollbar">
|
|
259
|
+
{listLoading ? (
|
|
260
|
+
<div className="flex justify-center py-10"><Spinner color="danger" /></div>
|
|
261
|
+
) : requests.length === 0 ? (
|
|
262
|
+
<p className="text-center py-10 text-default-400 italic font-medium bg-default-50 dark:bg-zinc-900/30 rounded-3xl border border-dashed border-default-200">The playlist is waiting for your touch!</p>
|
|
263
|
+
) : (
|
|
264
|
+
<AnimatePresence>
|
|
265
|
+
{requests.map((item, i) => (
|
|
266
|
+
<motion.div
|
|
267
|
+
key={item.id}
|
|
268
|
+
initial={{ opacity: 0, x: 20 }}
|
|
269
|
+
animate={{ opacity: 1, x: 0 }}
|
|
270
|
+
transition={{ delay: i * 0.05 }}
|
|
271
|
+
>
|
|
272
|
+
<Card className={`border ${item.status === 'played' ? 'bg-default-50/50 dark:bg-zinc-900/20 opacity-60' : 'bg-white dark:bg-zinc-900 shadow-lg'} transition-all`}>
|
|
273
|
+
<CardBody className="p-4 flex flex-row items-center justify-between gap-4">
|
|
274
|
+
<div className="flex items-center gap-4 flex-1 overflow-hidden">
|
|
275
|
+
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${item.status === 'played' ? 'bg-green-100 text-green-600' : 'bg-wedding-pink-100 text-wedding-pink-600'}`}>
|
|
276
|
+
{item.status === 'played' ? (
|
|
277
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
|
278
|
+
) : (
|
|
279
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle></svg>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
<div className="flex flex-col overflow-hidden">
|
|
283
|
+
<p className={`font-bold truncate ${item.status === 'played' ? 'line-through text-default-400' : 'text-default-800 dark:text-white'}`}>{item.song}</p>
|
|
284
|
+
<p className="text-xs text-default-500 truncate italic">{item.artist || 'Unknown Artist'} • Requested by {item.name}</p>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
{isAdmin && (
|
|
289
|
+
<div className="flex gap-1">
|
|
290
|
+
<Tooltip content={item.status === 'played' ? 'Mark Pending' : 'Mark Played'}>
|
|
291
|
+
<Button isIconOnly size="sm" variant="flat" color={item.status === 'played' ? 'warning' : 'success'} onPress={() => toggleStatus(item.id, item.status)}>
|
|
292
|
+
{item.status === 'played' ? '↺' : '✓'}
|
|
293
|
+
</Button>
|
|
294
|
+
</Tooltip>
|
|
295
|
+
<Tooltip content="Delete">
|
|
296
|
+
<Button isIconOnly size="sm" variant="flat" color="danger" onPress={() => handleDeleteClick(item.id)}>
|
|
297
|
+
âś•
|
|
298
|
+
</Button>
|
|
299
|
+
</Tooltip>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</CardBody>
|
|
303
|
+
</Card>
|
|
304
|
+
</motion.div>
|
|
305
|
+
))}
|
|
306
|
+
</AnimatePresence>
|
|
307
|
+
)}
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<div className="mt-16 text-center text-default-400 space-y-2">
|
|
313
|
+
<HeartFilledIcon className="w-6 h-6 mx-auto text-wedding-pink-200" />
|
|
314
|
+
<p className={`${fontMono.className} text-[10px] uppercase tracking-widest`}>Let the music play</p>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
{/* Delete Confirmation Modal */}
|
|
318
|
+
<Modal
|
|
319
|
+
isOpen={isDeleteModalOpen}
|
|
320
|
+
onOpenChange={setIsDeleteModalOpen}
|
|
321
|
+
backdrop="blur"
|
|
322
|
+
size="xs"
|
|
323
|
+
className="dark:bg-zinc-900 border border-default-100 dark:border-zinc-800"
|
|
324
|
+
>
|
|
325
|
+
<ModalContent>
|
|
326
|
+
{(onClose) => (
|
|
327
|
+
<>
|
|
328
|
+
<ModalBody className="pt-8 pb-4 text-center space-y-4">
|
|
329
|
+
<div className="w-16 h-16 bg-danger-50 dark:bg-danger-900/20 rounded-full flex items-center justify-center mx-auto text-danger animate-bounce">
|
|
330
|
+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
331
|
+
<polyline points="3 6 5 6 21 6"></polyline>
|
|
332
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
333
|
+
</svg>
|
|
334
|
+
</div>
|
|
335
|
+
<div>
|
|
336
|
+
<h3 className="text-xl font-bold text-default-900 dark:text-white">Delete Request?</h3>
|
|
337
|
+
<p className="text-sm text-default-500 mt-2">Are you sure you want to remove this song from the queue?</p>
|
|
338
|
+
</div>
|
|
339
|
+
</ModalBody>
|
|
340
|
+
<ModalFooter className="flex-col gap-2 pb-8">
|
|
341
|
+
<Button color="danger" className="w-full font-bold h-12" onPress={handleConfirmDelete}>
|
|
342
|
+
Yes, Remove Request
|
|
343
|
+
</Button>
|
|
344
|
+
<Button variant="flat" className="w-full font-semibold h-12" onPress={onClose}>
|
|
345
|
+
Cancel
|
|
346
|
+
</Button>
|
|
347
|
+
</ModalFooter>
|
|
348
|
+
</>
|
|
349
|
+
)}
|
|
350
|
+
</ModalContent>
|
|
351
|
+
</Modal>
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Image, Button, Card, CardBody } from "@heroui/react";
|
|
4
|
+
import { Link } from "@heroui/link";
|
|
5
|
+
|
|
6
|
+
import { fontCursive, fontSans, fontMono } from "@/config/fonts";
|
|
7
|
+
import { HeartFilledIcon } from "@/components/icons";
|
|
8
|
+
|
|
9
|
+
export default function SukanyaPage() {
|
|
10
|
+
return (
|
|
11
|
+
<div className="w-full min-h-screen pb-20">
|
|
12
|
+
{/* Hero / Intro */}
|
|
13
|
+
<section className="relative py-16 md:py-24 px-4 bg-wedding-pink-50/50 dark:bg-wedding-pink-900/10 rounded-md">
|
|
14
|
+
<div className="container mx-auto max-w-6xl flex flex-col md:flex-row-reverse items-center gap-12">
|
|
15
|
+
{/* Image */}
|
|
16
|
+
<div className="w-full md:w-1/2 flex justify-center md:justify-start">
|
|
17
|
+
<div className="relative w-72 h-72 md:w-96 md:h-96">
|
|
18
|
+
<div className="absolute inset-0 bg-wedding-pink-200 dark:bg-wedding-pink-800 rounded-full blur-2xl opacity-50 transform translate-x-4 translate-y-4" />
|
|
19
|
+
<Image
|
|
20
|
+
alt="Sukanya Saha"
|
|
21
|
+
className="rounded-full object-cover w-full h-full border-4 border-white dark:border-default-100 shadow-2xl z-10"
|
|
22
|
+
src="/bride.jpg"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{/* Text */}
|
|
28
|
+
<div className="w-full md:w-1/2 text-center md:text-right space-y-6">
|
|
29
|
+
<h1
|
|
30
|
+
className={`${fontCursive.className} text-6xl md:text-7xl text-wedding-pink-600 dark:text-wedding-pink-400`}
|
|
31
|
+
>
|
|
32
|
+
Sukanya Saha
|
|
33
|
+
</h1>
|
|
34
|
+
<p
|
|
35
|
+
className={`${fontMono.className} text-xl md:text-2xl text-default-600 dark:text-white font-medium`}
|
|
36
|
+
>
|
|
37
|
+
Dedicated Educator & Compassionate Mentor
|
|
38
|
+
</p>
|
|
39
|
+
<p
|
|
40
|
+
className={`${fontSans.className} text-lg text-default-700 dark:text-white leading-relaxed max-w-lg mx-auto md:ml-auto md:mr-0`}
|
|
41
|
+
>
|
|
42
|
+
A dedicated teacher with a strong academic background in Botany
|
|
43
|
+
and an unwavering passion for nurturing young learners.
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
|
|
49
|
+
{/* Content Grid */}
|
|
50
|
+
<section className="container mx-auto max-w-5xl px-4 py-16 space-y-16">
|
|
51
|
+
{/* About */}
|
|
52
|
+
<div className="grid md:grid-cols-12 gap-8 items-start">
|
|
53
|
+
<div className="md:col-span-4">
|
|
54
|
+
<h2
|
|
55
|
+
className={`${fontCursive.className} text-4xl text-wedding-pink-500 dark:text-wedding-pink-400 md:text-right`}
|
|
56
|
+
>
|
|
57
|
+
The Journey
|
|
58
|
+
</h2>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="md:col-span-8 space-y-4 text-default-700 dark:text-white text-lg leading-relaxed">
|
|
61
|
+
<p>
|
|
62
|
+
I strive to foster curiosity, empathy, and excellence in the
|
|
63
|
+
classroom. My academic journey began at{" "}
|
|
64
|
+
<strong>Serampore Girls High School</strong> and continued with
|
|
65
|
+
Botany Honours at <strong>Raja Peary Mohan College</strong>. I
|
|
66
|
+
earned my Master's from{" "}
|
|
67
|
+
<strong>Lady Brabourne College</strong>, building a deep research
|
|
68
|
+
orientation.
|
|
69
|
+
</p>
|
|
70
|
+
<p>
|
|
71
|
+
Currently, I am a school teacher at{" "}
|
|
72
|
+
<strong>Rishra Narayana School</strong> and a teaching partner at{" "}
|
|
73
|
+
<strong>EugenicsErudite</strong>, where I emphasize conceptual
|
|
74
|
+
clarity and moral growth.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Philosophy */}
|
|
80
|
+
<div className="grid md:grid-cols-12 gap-8 items-start">
|
|
81
|
+
<div className="md:col-span-4">
|
|
82
|
+
<h2
|
|
83
|
+
className={`${fontCursive.className} text-4xl text-wedding-pink-500 dark:text-wedding-pink-400 md:text-right`}
|
|
84
|
+
>
|
|
85
|
+
My Philosophy
|
|
86
|
+
</h2>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="md:col-span-8 space-y-4 text-default-700 dark:text-white text-lg leading-relaxed">
|
|
89
|
+
<p>
|
|
90
|
+
Education is the most powerful tool for personal and social
|
|
91
|
+
transformation. I design my lessons to balance structure with
|
|
92
|
+
exploration, encouraging students to think independently and act
|
|
93
|
+
ethically in an ever-changing world.
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Interests */}
|
|
99
|
+
<div className="grid md:grid-cols-12 gap-8 items-start">
|
|
100
|
+
<div className="md:col-span-4">
|
|
101
|
+
<h2
|
|
102
|
+
className={`${fontCursive.className} text-4xl text-wedding-pink-500 dark:text-wedding-pink-400 md:text-right`}
|
|
103
|
+
>
|
|
104
|
+
Beyond Work
|
|
105
|
+
</h2>
|
|
106
|
+
</div>
|
|
107
|
+
<div className="md:col-span-8 space-y-4 text-default-700 dark:text-white text-lg leading-relaxed">
|
|
108
|
+
<p>
|
|
109
|
+
I have a keen interest in Indian classical literature, nature
|
|
110
|
+
walks, and visual arts. I often find inspiration in the harmony
|
|
111
|
+
between traditional wisdom and modern scientific thought.
|
|
112
|
+
</p>
|
|
113
|
+
<p>
|
|
114
|
+
Outside the classroom, I enjoy exploring plant biology and
|
|
115
|
+
creating learner-friendly resources while staying connected to my
|
|
116
|
+
cultural roots.
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* Partner / Values */}
|
|
122
|
+
<div className="grid md:grid-cols-12 gap-8 items-start">
|
|
123
|
+
<div className="md:col-span-4">
|
|
124
|
+
<h2
|
|
125
|
+
className={`${fontCursive.className} text-4xl text-wedding-pink-500 dark:text-wedding-pink-400 md:text-right`}
|
|
126
|
+
>
|
|
127
|
+
Love & Life
|
|
128
|
+
</h2>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="md:col-span-8 space-y-6 text-default-700 dark:text-white text-lg leading-relaxed">
|
|
131
|
+
<p>
|
|
132
|
+
I believe that marriage is a space for mutual upliftment, calm
|
|
133
|
+
companionship, and respect for each other's individuality.
|
|
134
|
+
Simplicity and emotional well-being are the values that shape my
|
|
135
|
+
daily life.
|
|
136
|
+
</p>
|
|
137
|
+
|
|
138
|
+
<Card className="bg-wedding-pink-50 dark:bg-wedding-pink-900/20 border-none shadow-sm mt-6">
|
|
139
|
+
<CardBody className="flex flex-col md:flex-row items-center gap-6 p-6">
|
|
140
|
+
<HeartFilledIcon className="text-wedding-pink-500 w-12 h-12 flex-shrink-0" />
|
|
141
|
+
<div>
|
|
142
|
+
<h3
|
|
143
|
+
className={`${fontCursive.className} text-2xl text-wedding-pink-600 dark:text-wedding-pink-300 mb-2`}
|
|
144
|
+
>
|
|
145
|
+
My Partner
|
|
146
|
+
</h3>
|
|
147
|
+
<p className="text-default-600 dark:text-white text-sm mb-4">
|
|
148
|
+
I found a partner in Titas—someone who is sincere,
|
|
149
|
+
intellectually inclined, and my steady companion.
|
|
150
|
+
</p>
|
|
151
|
+
<Button
|
|
152
|
+
as={Link}
|
|
153
|
+
className="bg-wedding-gold-100 text-wedding-gold-700 font-semibold hover:bg-wedding-gold-200 dark:bg-wedding-gold-500 dark:text-black dark:hover:bg-wedding-gold-400"
|
|
154
|
+
href="/titas"
|
|
155
|
+
size="sm"
|
|
156
|
+
>
|
|
157
|
+
Meet Titas
|
|
158
|
+
</Button>
|
|
159
|
+
</div>
|
|
160
|
+
</CardBody>
|
|
161
|
+
</Card>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</section>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|