@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,168 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import AddGuestModal from "./guestAdder";
|
|
5
|
+
import GuestTable from "./guestShower";
|
|
6
|
+
import Dash from "./dashboard";
|
|
7
|
+
import RSVPViewer from "./rsvpViewer";
|
|
8
|
+
import Auth from "./auth";
|
|
9
|
+
import firebaseApp from "@/config/firebase";
|
|
10
|
+
import {
|
|
11
|
+
getFirestore,
|
|
12
|
+
doc,
|
|
13
|
+
addDoc,
|
|
14
|
+
collection,
|
|
15
|
+
onSnapshot,
|
|
16
|
+
setDoc,
|
|
17
|
+
deleteDoc,
|
|
18
|
+
serverTimestamp,
|
|
19
|
+
query,
|
|
20
|
+
orderBy,
|
|
21
|
+
} from "firebase/firestore";
|
|
22
|
+
import { addToast } from "@heroui/react";
|
|
23
|
+
import { fontCursive } from "@/config/fonts";
|
|
24
|
+
|
|
25
|
+
const InvitationMaker = () => {
|
|
26
|
+
const [user, setUser] = useState(null);
|
|
27
|
+
const [guests, setGuests] = useState([]);
|
|
28
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
29
|
+
const [editingGuest, setEditingGuest] = useState(null);
|
|
30
|
+
|
|
31
|
+
const db = getFirestore(firebaseApp());
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!user) return;
|
|
35
|
+
|
|
36
|
+
// const invitationRef = collection(db, "invitation");
|
|
37
|
+
const q = query(collection(db, "invitation"), orderBy("createdAt", "desc"));
|
|
38
|
+
|
|
39
|
+
const unsubscribe = onSnapshot(q, (snapshot) => {
|
|
40
|
+
const guests = snapshot.docs.map((doc) => ({
|
|
41
|
+
id: doc.id,
|
|
42
|
+
...doc.data(),
|
|
43
|
+
}));
|
|
44
|
+
setGuests(guests);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return () => unsubscribe(); // cleanup on unmount
|
|
48
|
+
}, [db, user]);
|
|
49
|
+
|
|
50
|
+
const handleAddGuest = async (guestData) => {
|
|
51
|
+
console.log("Saving Guest:", guestData);
|
|
52
|
+
const cleanedData = {
|
|
53
|
+
...guestData,
|
|
54
|
+
invitedGuests: Number(guestData.invitedGuests), // ensure number type
|
|
55
|
+
createdAt: serverTimestamp(),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
if (guestData.id) {
|
|
60
|
+
// Update existing guest by doc ID with merge
|
|
61
|
+
const guestRef = doc(db, "invitation", guestData.id);
|
|
62
|
+
await setDoc(guestRef, cleanedData, { merge: true });
|
|
63
|
+
console.log("🔁 Guest updated in Firestore");
|
|
64
|
+
addToast({
|
|
65
|
+
title: "Guest Updated",
|
|
66
|
+
description: "Guest details have been updated successfully.",
|
|
67
|
+
color: "success",
|
|
68
|
+
variant: "solid",
|
|
69
|
+
radius: "lg",
|
|
70
|
+
closeIcon: true,
|
|
71
|
+
timeout: 2000,
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
// Add new guest
|
|
75
|
+
await addDoc(collection(db, "invitation"), cleanedData);
|
|
76
|
+
console.log("✅ Guest added to Firestore");
|
|
77
|
+
addToast({
|
|
78
|
+
title: "Guest Added",
|
|
79
|
+
description: "Guest has been added successfully.",
|
|
80
|
+
color: "success",
|
|
81
|
+
variant: "solid",
|
|
82
|
+
radius: "lg",
|
|
83
|
+
closeIcon: true,
|
|
84
|
+
timeout: 2000,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("❌ Error saving guest:", error);
|
|
89
|
+
addToast({
|
|
90
|
+
title: "Error",
|
|
91
|
+
description: "An error occurred while saving the guest.",
|
|
92
|
+
color: "danger",
|
|
93
|
+
variant: "solid",
|
|
94
|
+
radius: "lg",
|
|
95
|
+
closeIcon: true,
|
|
96
|
+
timeout: 2000,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setIsModalOpen(false);
|
|
101
|
+
setEditingGuest(null);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleDeleteGuest = async (guestToDelete) => {
|
|
105
|
+
try {
|
|
106
|
+
const guestRef = doc(db, "invitation", guestToDelete.id);
|
|
107
|
+
await deleteDoc(guestRef);
|
|
108
|
+
console.log("🗑️ Guest deleted from Firestore");
|
|
109
|
+
addToast({
|
|
110
|
+
title: "Guest deleted",
|
|
111
|
+
description: "Guest has been deleted successfully.",
|
|
112
|
+
color: "danger",
|
|
113
|
+
variant: "solid",
|
|
114
|
+
radius: "lg",
|
|
115
|
+
closeIcon: true,
|
|
116
|
+
timeout: 2000,
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error("❌ Error deleting guest:", error);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleEditClick = (guest) => {
|
|
124
|
+
setEditingGuest(guest);
|
|
125
|
+
setIsModalOpen(true);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleAddClick = () => {
|
|
129
|
+
setEditingGuest(null);
|
|
130
|
+
setIsModalOpen(true);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
<h1
|
|
136
|
+
className={`${fontCursive.className} text-center text-5xl leading-snug bg-cover bg-center bg-no-repeat pt-2 pb-10`}
|
|
137
|
+
>
|
|
138
|
+
Guest Management System
|
|
139
|
+
</h1>
|
|
140
|
+
<Auth userSet={setUser} />
|
|
141
|
+
{guests && <Dash guests={guests} />}
|
|
142
|
+
{user && guests && (
|
|
143
|
+
<div className="mt-10 px-4">
|
|
144
|
+
<RSVPViewer guests={guests} />
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
{user && (
|
|
148
|
+
<AddGuestModal
|
|
149
|
+
isOpen={isModalOpen}
|
|
150
|
+
onClose={() => setIsModalOpen(false)}
|
|
151
|
+
onAdd={handleAddGuest}
|
|
152
|
+
existingGuest={editingGuest}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
<br />
|
|
156
|
+
{guests && user && (
|
|
157
|
+
<GuestTable
|
|
158
|
+
guests={guests}
|
|
159
|
+
onEdit={handleEditClick}
|
|
160
|
+
onDelete={handleDeleteGuest}
|
|
161
|
+
onAdd={handleAddClick}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export default InvitationMaker;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Table,
|
|
6
|
+
TableHeader,
|
|
7
|
+
TableColumn,
|
|
8
|
+
TableBody,
|
|
9
|
+
TableRow,
|
|
10
|
+
TableCell,
|
|
11
|
+
Chip,
|
|
12
|
+
User,
|
|
13
|
+
Tooltip,
|
|
14
|
+
} from "@heroui/react";
|
|
15
|
+
import {
|
|
16
|
+
getFirestore,
|
|
17
|
+
collection,
|
|
18
|
+
onSnapshot,
|
|
19
|
+
query,
|
|
20
|
+
orderBy
|
|
21
|
+
} from "firebase/firestore";
|
|
22
|
+
import firebaseApp from "@/config/firebase";
|
|
23
|
+
import { fontMono } from "@/config/fonts";
|
|
24
|
+
|
|
25
|
+
const db = getFirestore(firebaseApp());
|
|
26
|
+
|
|
27
|
+
export default function RSVPViewer({ guests }) {
|
|
28
|
+
const [rsvps, setRsvps] = useState([]);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const q = query(collection(db, "rsvps"), orderBy("timestamp", "desc"));
|
|
33
|
+
const unsubscribe = onSnapshot(q, (snapshot) => {
|
|
34
|
+
const rsvpData = snapshot.docs.map((doc) => {
|
|
35
|
+
const data = doc.data();
|
|
36
|
+
// Match with the master guest list to get more details (side, relation)
|
|
37
|
+
const masterGuest = guests.find(g => g.id === data.guestId);
|
|
38
|
+
return {
|
|
39
|
+
id: doc.id,
|
|
40
|
+
...data,
|
|
41
|
+
familySide: masterGuest?.familySide || "Unknown",
|
|
42
|
+
relation: masterGuest?.relation || "Guest",
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
setRsvps(rsvpData);
|
|
46
|
+
setLoading(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return () => unsubscribe();
|
|
50
|
+
}, [guests]);
|
|
51
|
+
|
|
52
|
+
const columns = [
|
|
53
|
+
{ name: "GUEST", uid: "guest" },
|
|
54
|
+
{ name: "ATTENDING", uid: "attending" },
|
|
55
|
+
{ name: "FOOD", uid: "food" },
|
|
56
|
+
{ name: "GUESTS", uid: "count" },
|
|
57
|
+
{ name: "NOTE", uid: "note" },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="space-y-4">
|
|
62
|
+
<div className="flex items-center justify-between px-2">
|
|
63
|
+
<h2 className="text-2xl font-bold text-default-800 dark:text-white">Live RSVP Responses</h2>
|
|
64
|
+
<Chip variant="flat" color="secondary" size="sm" className={fontMono.className}>
|
|
65
|
+
Total: {rsvps.length}
|
|
66
|
+
</Chip>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<Table aria-label="RSVP Response Table" shadow="sm" className="bg-white dark:bg-zinc-900/50">
|
|
70
|
+
<TableHeader columns={columns}>
|
|
71
|
+
{(column) => (
|
|
72
|
+
<TableColumn key={column.uid} className="bg-default-50 dark:bg-zinc-800 font-bold">
|
|
73
|
+
{column.name}
|
|
74
|
+
</TableColumn>
|
|
75
|
+
)}
|
|
76
|
+
</TableHeader>
|
|
77
|
+
<TableBody items={rsvps} emptyContent={"No RSVPs received yet."} isLoading={loading}>
|
|
78
|
+
{(item) => (
|
|
79
|
+
<TableRow key={item.id} className="border-b border-default-100 last:border-none">
|
|
80
|
+
<TableCell>
|
|
81
|
+
<User
|
|
82
|
+
name={item.guestName}
|
|
83
|
+
description={`${item.familySide === "bride" ? "Bride's" : "Groom's"} ${item.relation}`}
|
|
84
|
+
avatarProps={{
|
|
85
|
+
size: "sm",
|
|
86
|
+
className: item.familySide === "bride" ? "bg-wedding-pink-100 text-wedding-pink-600" : "bg-wedding-gold-100 text-wedding-gold-600",
|
|
87
|
+
name: item.guestName.charAt(0)
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
</TableCell>
|
|
91
|
+
<TableCell>
|
|
92
|
+
<Chip
|
|
93
|
+
color={item.attending === "yes" ? "success" : "danger"}
|
|
94
|
+
variant="flat"
|
|
95
|
+
size="sm"
|
|
96
|
+
className="font-bold uppercase"
|
|
97
|
+
>
|
|
98
|
+
{item.attending === "yes" ? "Attending" : "Declined"}
|
|
99
|
+
</Chip>
|
|
100
|
+
</TableCell>
|
|
101
|
+
<TableCell>
|
|
102
|
+
<Chip color={item.food === "veg" ? "success" : "warning"} variant="dot" size="sm">
|
|
103
|
+
{item.food === "veg" ? "Vegetarian" : "Non-Veg"}
|
|
104
|
+
</Chip>
|
|
105
|
+
</TableCell>
|
|
106
|
+
<TableCell>
|
|
107
|
+
<span className="font-mono font-bold text-lg">{item.guests}</span>
|
|
108
|
+
</TableCell>
|
|
109
|
+
<TableCell>
|
|
110
|
+
<Tooltip content={item.note || "No special note"}>
|
|
111
|
+
<p className="text-xs text-default-500 truncate max-w-[150px] italic">
|
|
112
|
+
{item.note || "—"}
|
|
113
|
+
</p>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
</TableCell>
|
|
116
|
+
</TableRow>
|
|
117
|
+
)}
|
|
118
|
+
</TableBody>
|
|
119
|
+
</Table>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import "@/styles/globals.css";
|
|
2
|
+
import { Metadata, Viewport } from "next";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
|
|
5
|
+
import { Providers } from "./providers";
|
|
6
|
+
|
|
7
|
+
import { siteConfig } from "@/config/site";
|
|
8
|
+
import { fontSans, fontMono, fontCursive } from "@/config/fonts"; // include all fonts
|
|
9
|
+
import { Navbar } from "@/components/navbar";
|
|
10
|
+
import { Footer } from "@/components/footer";
|
|
11
|
+
import { Analytics } from "@vercel/analytics/next";
|
|
12
|
+
import ConciergeBot from "@/components/ConciergeBot";
|
|
13
|
+
|
|
14
|
+
export const metadata: Metadata = {
|
|
15
|
+
metadataBase: new URL("https://www.titas-sukanya-for.life/"),
|
|
16
|
+
|
|
17
|
+
title: {
|
|
18
|
+
default: siteConfig.name,
|
|
19
|
+
template: `%s - ${siteConfig.name}`,
|
|
20
|
+
},
|
|
21
|
+
description: siteConfig.description,
|
|
22
|
+
keywords: ["Wedding", "Titas", "Sukanya", "Marriage", "Celebration", "Event"],
|
|
23
|
+
authors: [
|
|
24
|
+
{
|
|
25
|
+
name: "Titas Mallick",
|
|
26
|
+
url: "https://www.titas-sukanya-for.life",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
creator: "Titas Mallick",
|
|
30
|
+
icons: {
|
|
31
|
+
icon: "/love-birds.png",
|
|
32
|
+
},
|
|
33
|
+
openGraph: {
|
|
34
|
+
type: "website",
|
|
35
|
+
locale: "en_US",
|
|
36
|
+
url: "https://www.titas-sukanya-for.life/",
|
|
37
|
+
title: siteConfig.name,
|
|
38
|
+
description: siteConfig.description,
|
|
39
|
+
siteName: siteConfig.name,
|
|
40
|
+
images: [
|
|
41
|
+
{
|
|
42
|
+
url: "/pw/020.jpg",
|
|
43
|
+
width: 1200,
|
|
44
|
+
height: 630,
|
|
45
|
+
alt: "Titas & Sukanya Wedding",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
twitter: {
|
|
50
|
+
card: "summary_large_image",
|
|
51
|
+
title: siteConfig.name,
|
|
52
|
+
description: siteConfig.description,
|
|
53
|
+
images: ["/pw/020.jpg"],
|
|
54
|
+
creator: "@titas",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const viewport: Viewport = {
|
|
59
|
+
themeColor: [
|
|
60
|
+
{ media: "(prefers-color-scheme: light)", color: "white" },
|
|
61
|
+
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default function RootLayout({
|
|
66
|
+
children,
|
|
67
|
+
}: {
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<html
|
|
72
|
+
suppressHydrationWarning
|
|
73
|
+
className={clsx(
|
|
74
|
+
fontSans.variable,
|
|
75
|
+
fontMono.variable,
|
|
76
|
+
fontCursive.variable,
|
|
77
|
+
)}
|
|
78
|
+
lang="en"
|
|
79
|
+
>
|
|
80
|
+
<head />
|
|
81
|
+
<body
|
|
82
|
+
className={clsx("min-h-screen bg-background font-sans antialiased")}
|
|
83
|
+
>
|
|
84
|
+
<Providers themeProps={{ attribute: "class", defaultTheme: "light" }}>
|
|
85
|
+
<div className="relative flex flex-col h-screen">
|
|
86
|
+
<Navbar />
|
|
87
|
+
<main className="container mx-auto max-w-7xl pt-16 px-6 flex-grow">
|
|
88
|
+
{children}
|
|
89
|
+
</main>
|
|
90
|
+
<Footer />
|
|
91
|
+
<Analytics />
|
|
92
|
+
<ConciergeBot />
|
|
93
|
+
</div>
|
|
94
|
+
</Providers>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
import { Card, CardBody, Image, Chip } from "@heroui/react";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
|
|
7
|
+
import { fontCursive, fontSans, fontMono } from "@/config/fonts";
|
|
8
|
+
import { HeartFilledIcon, CalendarIcon } from "@/components/icons";
|
|
9
|
+
|
|
10
|
+
const importantDates = [
|
|
11
|
+
{
|
|
12
|
+
title: "The Proposal",
|
|
13
|
+
date: "15th March 2025",
|
|
14
|
+
note: "The day Titas finally asked Sukanya out with a ring.",
|
|
15
|
+
image: "/Images/19.jpg",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: "The Patipatra",
|
|
19
|
+
date: "18th April 2025",
|
|
20
|
+
note: "Our families came up with a holy date.",
|
|
21
|
+
image: "/Images/Patipatra.jpeg",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: "Our Engagement",
|
|
25
|
+
date: "23rd November 2025",
|
|
26
|
+
note: "A promise sealed with love and laughter.",
|
|
27
|
+
image: "/Images/22.jpg",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: "Wedding Bells",
|
|
31
|
+
date: "23rd January 2026",
|
|
32
|
+
note: "Two souls, one journey begins.",
|
|
33
|
+
image: "/Images/21.jpg",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: "Reception",
|
|
37
|
+
date: "25th January 2026",
|
|
38
|
+
note: "Celebrating our union with friends and family.",
|
|
39
|
+
image: "/Images/20.jpg",
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export default function DatesPage() {
|
|
44
|
+
const [now, setNow] = useState<Date | null>(null);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setNow(new Date());
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const parseDate = (dateStr: string) => {
|
|
51
|
+
const cleaned = dateStr.replace(/(\d+)(st|nd|rd|th)/, "$1");
|
|
52
|
+
|
|
53
|
+
return new Date(cleaned);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="min-h-screen pb-20 pt-10 px-4">
|
|
58
|
+
{/* Header */}
|
|
59
|
+
<section className="max-w-4xl mx-auto text-center mb-20">
|
|
60
|
+
<motion.div
|
|
61
|
+
animate={{ opacity: 1, y: 0 }}
|
|
62
|
+
initial={{ opacity: 0, y: -20 }}
|
|
63
|
+
transition={{ duration: 0.8 }}
|
|
64
|
+
>
|
|
65
|
+
<span className="inline-block px-3 py-1 rounded-full bg-wedding-pink-50 dark:bg-wedding-pink-900/30 text-wedding-pink-600 dark:text-wedding-pink-300 text-xs font-bold tracking-widest uppercase mb-6 border border-wedding-pink-100 dark:border-wedding-pink-800">
|
|
66
|
+
Our Journey
|
|
67
|
+
</span>
|
|
68
|
+
<h1
|
|
69
|
+
className={`${fontCursive.className} py-2 text-5xl md:text-7xl bg-gradient-to-r from-wedding-pink-600 to-wedding-gold-600 dark:from-wedding-pink-400 dark:to-wedding-gold-400 bg-clip-text text-transparent mb-6 leading-normal`}
|
|
70
|
+
>
|
|
71
|
+
Mark the Dates
|
|
72
|
+
</h1>
|
|
73
|
+
<p className="text-default-500 dark:text-default-400 max-w-xl mx-auto italic">
|
|
74
|
+
"Every moment led us here. From the first proposal to the final
|
|
75
|
+
vows, follow the milestones of our love story."
|
|
76
|
+
</p>
|
|
77
|
+
</motion.div>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
{/* Timeline Grid */}
|
|
81
|
+
<div className="max-w-5xl mx-auto relative">
|
|
82
|
+
{/* Timeline Line (Center - Desktop only) */}
|
|
83
|
+
<div className="absolute left-1/2 top-0 bottom-0 w-0.5 bg-gradient-to-b from-wedding-pink-200 via-wedding-gold-200 to-transparent -translate-x-1/2 hidden md:block opacity-30" />
|
|
84
|
+
|
|
85
|
+
<div className="flex flex-col gap-12 md:gap-24">
|
|
86
|
+
{importantDates.map((item, index) => {
|
|
87
|
+
const eventDate = parseDate(item.date);
|
|
88
|
+
const isPast = now ? now > eventDate : false;
|
|
89
|
+
const isLeft = index % 2 === 0;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<motion.div
|
|
93
|
+
key={index}
|
|
94
|
+
className={`flex flex-col ${isLeft ? "md:flex-row" : "md:flex-row-reverse"} items-center gap-8 md:gap-0`}
|
|
95
|
+
initial={{ opacity: 0, x: isLeft ? -50 : 50 }}
|
|
96
|
+
transition={{ duration: 0.7, ease: "easeOut" }}
|
|
97
|
+
viewport={{ once: true, margin: "-100px" }}
|
|
98
|
+
whileInView={{ opacity: 1, x: 0 }}
|
|
99
|
+
>
|
|
100
|
+
{/* Content Side */}
|
|
101
|
+
<div
|
|
102
|
+
className={`w-full md:w-1/2 ${isLeft ? "md:pr-16 md:text-right" : "md:pl-16 md:text-left"}`}
|
|
103
|
+
>
|
|
104
|
+
<div
|
|
105
|
+
className={`flex flex-col ${isLeft ? "md:items-end" : "md:items-start"} items-center space-y-3`}
|
|
106
|
+
>
|
|
107
|
+
<div className="flex items-center gap-2">
|
|
108
|
+
{isPast && (
|
|
109
|
+
<Chip
|
|
110
|
+
className="bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border-green-100 dark:border-green-800/30"
|
|
111
|
+
color="success"
|
|
112
|
+
size="sm"
|
|
113
|
+
variant="flat"
|
|
114
|
+
>
|
|
115
|
+
Completed
|
|
116
|
+
</Chip>
|
|
117
|
+
)}
|
|
118
|
+
<p
|
|
119
|
+
className={`${fontMono.className} text-wedding-pink-500 font-bold tracking-tighter text-sm`}
|
|
120
|
+
>
|
|
121
|
+
{item.date}
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<h3
|
|
126
|
+
className={`${fontSans.className} text-2xl md:text-3xl font-bold text-default-900`}
|
|
127
|
+
>
|
|
128
|
+
{item.title}
|
|
129
|
+
</h3>
|
|
130
|
+
|
|
131
|
+
<p className="text-default-500 dark:text-default-400 text-sm md:text-base leading-relaxed max-w-sm">
|
|
132
|
+
{item.note}
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Center Icon (Desktop only) */}
|
|
138
|
+
<div className="absolute left-1/2 -translate-x-1/2 w-10 h-10 rounded-full bg-white dark:bg-zinc-900 border-4 border-wedding-pink-100 dark:border-wedding-pink-900 hidden md:flex items-center justify-center z-10 shadow-lg">
|
|
139
|
+
{isPast ? (
|
|
140
|
+
<HeartFilledIcon className="w-4 h-4 text-wedding-pink-500" />
|
|
141
|
+
) : (
|
|
142
|
+
<CalendarIcon className="w-4 h-4 text-wedding-gold-500" />
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Image Side */}
|
|
147
|
+
<div
|
|
148
|
+
className={`w-full md:w-1/2 ${isLeft ? "md:pl-16" : "md:pr-16"}`}
|
|
149
|
+
>
|
|
150
|
+
<motion.div
|
|
151
|
+
className="w-full"
|
|
152
|
+
transition={{ type: "spring", stiffness: 300 }}
|
|
153
|
+
whileHover={{ scale: 1.02 }}
|
|
154
|
+
>
|
|
155
|
+
<Card
|
|
156
|
+
className={`overflow-hidden border-none shadow-xl aspect-[4/5] w-full ${isPast ? "opacity-90" : ""}`}
|
|
157
|
+
>
|
|
158
|
+
<CardBody className="p-0 h-full w-full overflow-hidden relative">
|
|
159
|
+
<Image
|
|
160
|
+
removeWrapper
|
|
161
|
+
alt={item.title}
|
|
162
|
+
className="z-0 w-full h-full object-cover hover:scale-105 transition-transform duration-700 block"
|
|
163
|
+
height="100%"
|
|
164
|
+
radius="none"
|
|
165
|
+
src={item.image}
|
|
166
|
+
width="100%"
|
|
167
|
+
/>
|
|
168
|
+
</CardBody>
|
|
169
|
+
</Card>
|
|
170
|
+
</motion.div>
|
|
171
|
+
</div>
|
|
172
|
+
</motion.div>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Bottom Call to Action */}
|
|
179
|
+
<motion.div
|
|
180
|
+
className="mt-32 text-center"
|
|
181
|
+
initial={{ opacity: 0 }}
|
|
182
|
+
whileInView={{ opacity: 1 }}
|
|
183
|
+
>
|
|
184
|
+
<div className="inline-block p-[2px] rounded-full bg-gradient-to-r from-wedding-pink-500 to-wedding-gold-500 mb-6">
|
|
185
|
+
<div className="bg-white dark:bg-zinc-900 px-8 py-3 rounded-full">
|
|
186
|
+
<p
|
|
187
|
+
className={`${fontCursive.className} text-3xl bg-gradient-to-r from-wedding-pink-600 to-wedding-gold-600 dark:from-wedding-pink-400 dark:to-wedding-gold-400 bg-clip-text text-transparent`}
|
|
188
|
+
>
|
|
189
|
+
Counting down the days...
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</motion.div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { fontCursive } from "@/config/fonts";
|
|
4
|
+
import Gallery from "@/components/Gallery";
|
|
5
|
+
|
|
6
|
+
export default function MemoriesPage() {
|
|
7
|
+
return (
|
|
8
|
+
<div className="w-full min-h-screen pb-20">
|
|
9
|
+
<section className="relative py-16 md:py-24 text-center px-4 overflow-hidden">
|
|
10
|
+
{/* Decorative Background */}
|
|
11
|
+
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[400px] bg-wedding-pink-100/40 dark:bg-wedding-pink-900/10 rounded-full blur-[100px] pointer-events-none" />
|
|
12
|
+
|
|
13
|
+
<div className="relative z-10">
|
|
14
|
+
<h1
|
|
15
|
+
className={`${fontCursive.className} text-5xl md:text-7xl lg:text-8xl bg-gradient-to-r from-wedding-pink-600 to-wedding-gold-600 bg-clip-text text-transparent mb-6 py-4 leading-normal`}
|
|
16
|
+
>
|
|
17
|
+
Days of Future Past
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="text-default-500 dark:text-gray-300 text-lg md:text-xl max-w-2xl mx-auto italic">
|
|
20
|
+
"Frames that echo where we began, rather than where we
|
|
21
|
+
stand."
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
</section>
|
|
25
|
+
|
|
26
|
+
<Gallery />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
package/app/page.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ThemeProviderProps } from "next-themes";
|
|
4
|
+
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { HeroUIProvider } from "@heroui/system";
|
|
7
|
+
import { useRouter } from "next/navigation";
|
|
8
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
9
|
+
import { ToastProvider } from "@heroui/react";
|
|
10
|
+
|
|
11
|
+
export interface ProvidersProps {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
themeProps?: ThemeProviderProps;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module "@react-types/shared" {
|
|
17
|
+
interface RouterConfig {
|
|
18
|
+
routerOptions: NonNullable<
|
|
19
|
+
Parameters<ReturnType<typeof useRouter>["push"]>[1]
|
|
20
|
+
>;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Providers({ children, themeProps }: ProvidersProps) {
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<HeroUIProvider navigate={router.push}>
|
|
29
|
+
<ToastProvider />
|
|
30
|
+
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
|
31
|
+
</HeroUIProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|