@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.
Files changed (123) hide show
  1. package/README.md +104 -170
  2. package/app/api/email-reminders/route.ts +240 -0
  3. package/app/couple/page.tsx +4 -4
  4. package/app/game/page.tsx +298 -0
  5. package/app/guestbook/page.tsx +270 -152
  6. package/app/invitation/[slug]/layout.tsx +4 -2
  7. package/app/invitation/[slug]/page.tsx +303 -84
  8. package/app/invitation/actions.ts +49 -0
  9. package/app/invitation/maker/auth.js +1 -1
  10. package/app/invitation/maker/guestAdder.js +4 -0
  11. package/app/invitation/maker/guestShower.js +39 -8
  12. package/app/invitation/maker/layout.tsx +1 -1
  13. package/app/invitation/maker/page.js +9 -7
  14. package/app/invitation/maker/rsvpViewer.js +90 -8
  15. package/app/layout.tsx +40 -14
  16. package/app/mark-the-dates/page.tsx +8 -2
  17. package/app/page.tsx +7 -1
  18. package/app/providers.tsx +1 -1
  19. package/app/sagun/page.tsx +224 -76
  20. package/app/song-requests/page.tsx +242 -105
  21. package/app/sukanya/page.tsx +9 -13
  22. package/app/titas/page.tsx +8 -24
  23. package/app/travel-guide/page.tsx +361 -120
  24. package/app/updates/maker/page.js +2 -2
  25. package/app/updates/overlay/page.tsx +65 -30
  26. package/app/updates/page.js +3 -3
  27. package/cli.mjs +26 -15
  28. package/components/AdminAuth.tsx +145 -0
  29. package/components/AdminLinks.tsx +120 -0
  30. package/components/ConciergeBot.tsx +104 -44
  31. package/components/CountdownTimer.tsx +37 -15
  32. package/components/Gallery.tsx +1 -1
  33. package/components/LiveVideos.tsx +27 -15
  34. package/components/OurStory.tsx +1 -1
  35. package/components/SchemaMarkup.tsx +74 -0
  36. package/components/certificate.jsx +287 -300
  37. package/components/footer.tsx +2 -0
  38. package/components/hero.tsx +47 -4
  39. package/components/icons.tsx +45 -0
  40. package/components/importantNews.js +168 -168
  41. package/components/navbar.tsx +113 -18
  42. package/components/updates.tsx +36 -26
  43. package/config/firebase-admin.js +14 -17
  44. package/config/firebase.ts +4 -2
  45. package/config/site.ts +10 -2
  46. package/firestore.rules +6 -1
  47. package/next-sitemap.config.js +21 -0
  48. package/package.json +4 -3
  49. package/public/corner1-01.svg +0 -0
  50. package/public/love-birds.png +0 -0
  51. package/public/next.svg +0 -0
  52. package/public/pubqr.png +0 -0
  53. package/public/pw/sample.jpg +0 -0
  54. package/public/qr.png +0 -0
  55. package/public/sample.jpg +0 -0
  56. package/public/vercel.svg +0 -0
  57. package/vercel.json +1 -0
  58. package/.recover +0 -9
  59. package/next-env.d.ts +0 -6
  60. package/public/DCV.gif +0 -0
  61. package/public/DCV2.gif +0 -0
  62. package/public/DCV3.gif +0 -0
  63. package/public/Images/1.jpg +0 -0
  64. package/public/Images/11.jpg +0 -0
  65. package/public/Images/12.jpg +0 -0
  66. package/public/Images/13.jpg +0 -0
  67. package/public/Images/14.jpg +0 -0
  68. package/public/Images/15.jpg +0 -0
  69. package/public/Images/16.jpg +0 -0
  70. package/public/Images/17.jpg +0 -0
  71. package/public/Images/18.jpg +0 -0
  72. package/public/Images/19.jpg +0 -0
  73. package/public/Images/2.jpg +0 -0
  74. package/public/Images/21.jpg +0 -0
  75. package/public/Images/22.jpg +0 -0
  76. package/public/Images/3.jpg +0 -0
  77. package/public/Images/4.jpg +0 -0
  78. package/public/Images/5.jpg +0 -0
  79. package/public/Images/6.jpg +0 -0
  80. package/public/Images/7.jpg +0 -0
  81. package/public/Images/8.jpg +0 -0
  82. package/public/Images/9.jpg +0 -0
  83. package/public/Images/9b.jpg +0 -0
  84. package/public/Images/Patipatra.jpeg +0 -0
  85. package/public/audio (1).mp3 +0 -0
  86. package/public/audio (2).mp3 +0 -0
  87. package/public/bride.jpg +0 -0
  88. package/public/groom.jpg +0 -0
  89. package/public/invite.png +0 -0
  90. package/public/pw/001.jpg +0 -0
  91. package/public/pw/002.jpg +0 -0
  92. package/public/pw/003.jpg +0 -0
  93. package/public/pw/004.jpg +0 -0
  94. package/public/pw/005.jpg +0 -0
  95. package/public/pw/006.jpg +0 -0
  96. package/public/pw/007.jpg +0 -0
  97. package/public/pw/008.jpg +0 -0
  98. package/public/pw/009.jpg +0 -0
  99. package/public/pw/010.jpg +0 -0
  100. package/public/pw/011.jpg +0 -0
  101. package/public/pw/012.jpg +0 -0
  102. package/public/pw/013.jpg +0 -0
  103. package/public/pw/014.jpg +0 -0
  104. package/public/pw/015.jpg +0 -0
  105. package/public/pw/016.jpg +0 -0
  106. package/public/pw/017.jpg +0 -0
  107. package/public/pw/018.jpg +0 -0
  108. package/public/pw/019.jpg +0 -0
  109. package/public/pw/020.jpg +0 -0
  110. package/public/pw/021.jpg +0 -0
  111. package/public/pw/022.jpg +0 -0
  112. package/public/pw/023.jpg +0 -0
  113. package/public/pw/024.jpg +0 -0
  114. package/public/pw/025.jpg +0 -0
  115. package/public/pw/026.jpg +0 -0
  116. package/public/pw/027.jpg +0 -0
  117. package/public/pw/028.jpg +0 -0
  118. package/public/pw/029.jpg +0 -0
  119. package/public/pw/030.jpg +0 -0
  120. package/public/pw/031.jpg +0 -0
  121. package/public/pw/032.jpg +0 -0
  122. package/tsconfig.tsbuildinfo +0 -1
  123. /package/public/Images/{20.jpg → sample.jpg} +0 -0
@@ -0,0 +1,49 @@
1
+ "use server";
2
+
3
+ import * as admin from "firebase-admin";
4
+ import adminCred from "@/config/firebase-admin";
5
+
6
+ if (!admin.apps.length) {
7
+ admin.initializeApp({
8
+ credential: admin.credential.cert(adminCred as admin.ServiceAccount),
9
+ });
10
+ }
11
+
12
+ const db = admin.firestore();
13
+
14
+ export async function sendEmailReminder(formData: FormData) {
15
+ const email = formData.get("email") as string;
16
+ const slug = formData.get("slug") as string;
17
+
18
+ console.log("Received email reminder request:", { email, slug });
19
+
20
+ try {
21
+ const docRef = db.collection("invitation").doc(slug);
22
+ const docSnap = await docRef.get();
23
+
24
+ if (docSnap.exists) {
25
+ const guestData = docSnap.data();
26
+
27
+ await db.collection("email-reminders").add({
28
+ email,
29
+ guestId: slug,
30
+ guestName: guestData?.name || "Unknown",
31
+ familySide: guestData?.familySide || "Unknown",
32
+ invitedFor: guestData?.invitedFor || [],
33
+ createdAt: admin.firestore.FieldValue.serverTimestamp(),
34
+ });
35
+
36
+ console.log("Email reminder saved for:", email);
37
+ } else {
38
+ console.log("Invitation not found for slug:", slug);
39
+ }
40
+ } catch (error) {
41
+ console.error("Error fetching invitation:", error);
42
+ }
43
+
44
+ // In the future, we will use 'resend' here to send the email.
45
+ // const { Resend } = require("resend");
46
+ // const resend = new Resend(process.env.RESEND_API_KEY);
47
+
48
+ return { success: true };
49
+ }
@@ -95,7 +95,7 @@ const Auth = ({ userSet }) => {
95
95
 
96
96
  return (
97
97
  <>
98
- <section className="flex items-center justify-between p-4 rounded-lg">
98
+ <section className="flex flex-col sm:flex-row items-center justify-between p-4 gap-4 rounded-lg">
99
99
  {!user ? (
100
100
  <Button color="primary" onPress={() => setModalOpen(true)}>
101
101
  Sign In
@@ -115,6 +115,7 @@ export default function AddGuestModal({
115
115
  isRequired
116
116
  label="Family Side"
117
117
  orientation="horizontal"
118
+ classNames={{ wrapper: "flex-wrap gap-4" }}
118
119
  value={formData.familySide}
119
120
  onValueChange={(val) => handleRadioChange("familySide", val)}
120
121
  >
@@ -126,6 +127,7 @@ export default function AddGuestModal({
126
127
  isRequired
127
128
  label="Relation to Couple"
128
129
  orientation="horizontal"
130
+ classNames={{ wrapper: "flex-wrap gap-4" }}
129
131
  value={formData.relation}
130
132
  onValueChange={(val) => handleRadioChange("relation", val)}
131
133
  >
@@ -138,6 +140,7 @@ export default function AddGuestModal({
138
140
  <CheckboxGroup
139
141
  label="Invited For"
140
142
  orientation="horizontal"
143
+ classNames={{ wrapper: "flex-wrap gap-4" }}
141
144
  value={formData.invitedFor}
142
145
  onValueChange={(val) => handleCheckboxChange("invitedFor", val)}
143
146
  isRequired
@@ -166,6 +169,7 @@ export default function AddGuestModal({
166
169
  <RadioGroup
167
170
  label="RSVP Status"
168
171
  orientation="horizontal"
172
+ classNames={{ wrapper: "flex-wrap gap-4" }}
169
173
  value={formData.rsvpStatus}
170
174
  onValueChange={(val) => handleRadioChange("rsvpStatus", val)}
171
175
  >
@@ -10,6 +10,7 @@ import {
10
10
  CardHeader,
11
11
  CardFooter,
12
12
  Link,
13
+ Input,
13
14
  } from "@heroui/react";
14
15
  import { fontSans, fontMono } from "@/config/fonts";
15
16
  import { useState, useMemo } from "react";
@@ -46,6 +47,7 @@ export default function GuestTable({ guests, onEdit, onDelete, onAdd }) {
46
47
  relation: "all",
47
48
  rsvpStatus: "all",
48
49
  });
50
+ const [searchTerm, setSearchTerm] = useState("");
49
51
 
50
52
  const handleFilterChange = (key, value) => {
51
53
  setFilters((prev) => ({
@@ -56,6 +58,14 @@ export default function GuestTable({ guests, onEdit, onDelete, onAdd }) {
56
58
 
57
59
  const filteredGuests = useMemo(() => {
58
60
  return guests.filter((guest) => {
61
+ if (searchTerm) {
62
+ const lowerTerm = searchTerm.toLowerCase();
63
+ const nameMatch = guest.name?.toLowerCase().includes(lowerTerm);
64
+ const contactMatch = guest.contact?.toLowerCase().includes(lowerTerm);
65
+
66
+ if (!nameMatch && !contactMatch) return false;
67
+ }
68
+
59
69
  if (
60
70
  filters.familySide !== "all" &&
61
71
  guest.familySide !== filters.familySide
@@ -75,18 +85,39 @@ export default function GuestTable({ guests, onEdit, onDelete, onAdd }) {
75
85
  return false;
76
86
  return true;
77
87
  });
78
- }, [guests, filters]);
88
+ }, [guests, filters, searchTerm]);
79
89
 
80
90
  return (
81
91
  <div className={`${fontSans.className}`}>
82
92
  {/* FILTER SECTION */}
83
93
  <Card className="mb-4 w-full shadow-sm bg-transparent">
84
- <CardBody className="p-2 flex flex-col gap-4 sm:flex-row sm:justify-between sm:items-center">
85
- <Button onPress={onAdd} color="primary" className="w-full sm:w-auto">
86
- Add Guest
87
- </Button>
94
+ <CardBody className="p-4 flex flex-col gap-6">
95
+ <div className="flex flex-col sm:flex-row justify-between items-end gap-4">
96
+ <div className="w-full sm:max-w-md">
97
+ <Input
98
+ label="Search Guests"
99
+ placeholder="Search by name or contact..."
100
+ labelPlacement="outside-top"
101
+ value={searchTerm}
102
+ onValueChange={setSearchTerm}
103
+ variant="bordered"
104
+ size="md"
105
+ classNames={{
106
+ input: "outline-none",
107
+ }}
108
+ />
109
+ </div>
110
+ <Button
111
+ onPress={onAdd}
112
+ color="primary"
113
+ className="w-full sm:w-auto"
114
+ size="lg"
115
+ >
116
+ Add Guest
117
+ </Button>
118
+ </div>
88
119
 
89
- <div className="flex flex-wrap gap-3 w-full sm:w-auto justify-start sm:justify-end">
120
+ <div className="flex flex-wrap gap-3 w-full justify-start border-t dark:border-white/10 pt-4">
90
121
  <Select
91
122
  label="Family Side"
92
123
  variant="bordered"
@@ -166,7 +197,7 @@ export default function GuestTable({ guests, onEdit, onDelete, onAdd }) {
166
197
  key={index}
167
198
  className={`flex text-left flex-col justify-between mb-2 border-t-4 ${rsvpBorderColor[guest.rsvpStatus]} shadow-md h-full`}
168
199
  >
169
- <CardHeader className="flex justify-between items-center px-4 pt-4 pb-2">
200
+ <CardHeader className="flex justify-between items-center px-4 pt-4 pb-2 gap-2">
170
201
  <div>
171
202
  <p className="font-bold text-base">{guest.name}</p>
172
203
  <p className="text-sm capitalize">
@@ -249,7 +280,7 @@ export default function GuestTable({ guests, onEdit, onDelete, onAdd }) {
249
280
  {guest.contact && (
250
281
  <a
251
282
  href={`https://wa.me/${guest.contact.replace(/[^0-9]/g, "")}?text=${encodeURIComponent(
252
- `Hi! You are invited to Titas & Sukanya's wedding. Here's your invitation link: ${window.location.origin}/${guest.id}`
283
+ `Hi! You are invited to Titas & Sukanya's wedding. Here's your invitation link: ${window.location.origin}/invitation/${guest.id}`
253
284
  )}`}
254
285
  target="_blank"
255
286
  rel="noopener noreferrer"
@@ -5,7 +5,7 @@ export default function PricingLayout({
5
5
  }) {
6
6
  return (
7
7
  <section className="flex flex-col items-center justify-center gap-4 md:px-20 py-8 md:py-10">
8
- <div className="inline-block text-center justify-center">{children}</div>
8
+ <div className="w-full text-center justify-center">{children}</div>
9
9
  </section>
10
10
  );
11
11
  }
@@ -133,7 +133,7 @@ const InvitationMaker = () => {
133
133
  return (
134
134
  <>
135
135
  <h1
136
- className={`${fontCursive.className} text-center text-5xl leading-snug bg-cover bg-center bg-no-repeat pt-2 pb-10`}
136
+ className={`${fontCursive.className} text-center text-3xl md:text-5xl leading-snug bg-cover bg-center bg-no-repeat pt-2 pb-10`}
137
137
  >
138
138
  Guest Management System
139
139
  </h1>
@@ -154,12 +154,14 @@ const InvitationMaker = () => {
154
154
  )}
155
155
  <br />
156
156
  {guests && user && (
157
- <GuestTable
158
- guests={guests}
159
- onEdit={handleEditClick}
160
- onDelete={handleDeleteGuest}
161
- onAdd={handleAddClick}
162
- />
157
+ <div className="px-4">
158
+ <GuestTable
159
+ guests={guests}
160
+ onEdit={handleEditClick}
161
+ onDelete={handleDeleteGuest}
162
+ onAdd={handleAddClick}
163
+ />
164
+ </div>
163
165
  )}
164
166
  </>
165
167
  );
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect } from "react";
3
+ import { useState, useEffect, useMemo } from "react";
4
4
  import {
5
5
  Table,
6
6
  TableHeader,
@@ -11,6 +11,8 @@ import {
11
11
  Chip,
12
12
  User,
13
13
  Tooltip,
14
+ Select,
15
+ SelectItem,
14
16
  } from "@heroui/react";
15
17
  import {
16
18
  getFirestore,
@@ -27,6 +29,11 @@ const db = getFirestore(firebaseApp());
27
29
  export default function RSVPViewer({ guests }) {
28
30
  const [rsvps, setRsvps] = useState([]);
29
31
  const [loading, setLoading] = useState(true);
32
+
33
+ // Filter & Sort States
34
+ const [foodFilter, setFoodFilter] = useState("all");
35
+ const [attendanceFilter, setAttendanceFilter] = useState("all");
36
+ const [sortOption, setSortOption] = useState("newest");
30
37
 
31
38
  useEffect(() => {
32
39
  const q = query(collection(db, "rsvps"), orderBy("timestamp", "desc"));
@@ -40,6 +47,7 @@ export default function RSVPViewer({ guests }) {
40
47
  ...data,
41
48
  familySide: masterGuest?.familySide || "Unknown",
42
49
  relation: masterGuest?.relation || "Guest",
50
+ timestamp: data.timestamp?.toDate ? data.timestamp.toDate() : new Date(),
43
51
  };
44
52
  });
45
53
  setRsvps(rsvpData);
@@ -49,6 +57,31 @@ export default function RSVPViewer({ guests }) {
49
57
  return () => unsubscribe();
50
58
  }, [guests]);
51
59
 
60
+ const processedRsvps = useMemo(() => {
61
+ let result = [...rsvps];
62
+
63
+ // Filter by Food
64
+ if (foodFilter !== "all") {
65
+ result = result.filter((item) => item.food === foodFilter);
66
+ }
67
+
68
+ // Filter by Attendance
69
+ if (attendanceFilter !== "all") {
70
+ result = result.filter((item) => item.attending === attendanceFilter);
71
+ }
72
+
73
+ // Sort
74
+ result.sort((a, b) => {
75
+ if (sortOption === "newest") return b.timestamp - a.timestamp;
76
+ if (sortOption === "oldest") return a.timestamp - b.timestamp;
77
+ if (sortOption === "guests_high") return b.guests - a.guests;
78
+ if (sortOption === "guests_low") return a.guests - b.guests;
79
+ return 0;
80
+ });
81
+
82
+ return result;
83
+ }, [rsvps, foodFilter, attendanceFilter, sortOption]);
84
+
52
85
  const columns = [
53
86
  { name: "GUEST", uid: "guest" },
54
87
  { name: "ATTENDING", uid: "attending" },
@@ -59,14 +92,62 @@ export default function RSVPViewer({ guests }) {
59
92
 
60
93
  return (
61
94
  <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>
95
+ <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 px-2">
96
+ <div className="flex items-center gap-4">
97
+ <h2 className="text-2xl font-bold text-default-800 dark:text-white">Live RSVP Responses</h2>
98
+ <Chip variant="flat" color="secondary" size="sm" className={fontMono.className}>
99
+ Total: {processedRsvps.length}
100
+ </Chip>
101
+ </div>
102
+
103
+ <div className="flex flex-wrap gap-3 w-full md:w-auto justify-start md:justify-end">
104
+ <Select
105
+ label="Attendance"
106
+ variant="bordered"
107
+ size="sm"
108
+ labelPlacement="inside"
109
+ className="min-w-[140px] max-w-[160px] flex-1 [&_label]:!pb-1.5 [&_[data-slot=value]]:!pt-1.5"
110
+ selectedKeys={[attendanceFilter]}
111
+ onSelectionChange={(e) => setAttendanceFilter(Array.from(e)[0])}
112
+ >
113
+ <SelectItem key="all">All</SelectItem>
114
+ <SelectItem key="yes">Attending</SelectItem>
115
+ <SelectItem key="no">Declined</SelectItem>
116
+ </Select>
117
+
118
+ <Select
119
+ label="Food"
120
+ variant="bordered"
121
+ size="sm"
122
+ labelPlacement="inside"
123
+ className="min-w-[140px] max-w-[160px] flex-1 [&_label]:!pb-1.5 [&_[data-slot=value]]:!pt-1.5"
124
+ selectedKeys={[foodFilter]}
125
+ onSelectionChange={(e) => setFoodFilter(Array.from(e)[0])}
126
+ >
127
+ <SelectItem key="all">All</SelectItem>
128
+ <SelectItem key="veg">Veg</SelectItem>
129
+ <SelectItem key="non-veg">Non-Veg</SelectItem>
130
+ </Select>
131
+
132
+ <Select
133
+ label="Sort By"
134
+ variant="bordered"
135
+ size="sm"
136
+ labelPlacement="inside"
137
+ className="min-w-[140px] max-w-[160px] flex-1 [&_label]:!pb-1.5 [&_[data-slot=value]]:!pt-1.5"
138
+ selectedKeys={[sortOption]}
139
+ onSelectionChange={(e) => setSortOption(Array.from(e)[0])}
140
+ >
141
+ <SelectItem key="newest">Newest First</SelectItem>
142
+ <SelectItem key="oldest">Oldest First</SelectItem>
143
+ <SelectItem key="guests_high">Most Guests</SelectItem>
144
+ <SelectItem key="guests_low">Fewest Guests</SelectItem>
145
+ </Select>
146
+ </div>
67
147
  </div>
68
148
 
69
- <Table aria-label="RSVP Response Table" shadow="sm" className="bg-white dark:bg-zinc-900/50">
149
+ <div className="overflow-x-auto">
150
+ <Table aria-label="RSVP Response Table" shadow="sm" className="bg-white dark:bg-zinc-900/50 min-w-[600px]">
70
151
  <TableHeader columns={columns}>
71
152
  {(column) => (
72
153
  <TableColumn key={column.uid} className="bg-default-50 dark:bg-zinc-800 font-bold">
@@ -74,7 +155,7 @@ export default function RSVPViewer({ guests }) {
74
155
  </TableColumn>
75
156
  )}
76
157
  </TableHeader>
77
- <TableBody items={rsvps} emptyContent={"No RSVPs received yet."} isLoading={loading}>
158
+ <TableBody items={processedRsvps} emptyContent={"No RSVPs match your filters."} isLoading={loading}>
78
159
  {(item) => (
79
160
  <TableRow key={item.id} className="border-b border-default-100 last:border-none">
80
161
  <TableCell>
@@ -117,6 +198,7 @@ export default function RSVPViewer({ guests }) {
117
198
  )}
118
199
  </TableBody>
119
200
  </Table>
201
+ </div>
120
202
  </div>
121
203
  );
122
204
  }
package/app/layout.tsx CHANGED
@@ -1,25 +1,35 @@
1
1
  import "@/styles/globals.css";
2
2
  import { Metadata, Viewport } from "next";
3
3
  import clsx from "clsx";
4
+ import { Analytics } from "@vercel/analytics/next";
4
5
 
5
- import { Providers } from "./providers";
6
+ import Providers from "./providers";
6
7
 
7
8
  import { siteConfig } from "@/config/site";
8
9
  import { fontSans, fontMono, fontCursive } from "@/config/fonts"; // include all fonts
9
10
  import { Navbar } from "@/components/navbar";
10
11
  import { Footer } from "@/components/footer";
11
- import { Analytics } from "@vercel/analytics/next";
12
12
  import ConciergeBot from "@/components/ConciergeBot";
13
+ import { SchemaMarkup } from "@/components/SchemaMarkup";
13
14
 
14
15
  export const metadata: Metadata = {
15
- metadataBase: new URL("https://www.titas-sukanya-for.life/"),
16
+ metadataBase: new URL("https://www.titas-sukanya-for.life"),
16
17
 
17
18
  title: {
18
19
  default: siteConfig.name,
19
- template: `%s - ${siteConfig.name}`,
20
+ template: `%s | ${siteConfig.name}`,
20
21
  },
21
22
  description: siteConfig.description,
22
- keywords: ["Wedding", "Titas", "Sukanya", "Marriage", "Celebration", "Event"],
23
+ keywords: [
24
+ "Wedding",
25
+ "Titas",
26
+ "Sukanya",
27
+ "Marriage",
28
+ "Celebration",
29
+ "Event",
30
+ "Serampore Wedding",
31
+ "Titas and Sukanya Wedding",
32
+ ],
23
33
  authors: [
24
34
  {
25
35
  name: "Titas Mallick",
@@ -29,6 +39,11 @@ export const metadata: Metadata = {
29
39
  creator: "Titas Mallick",
30
40
  icons: {
31
41
  icon: "/love-birds.png",
42
+ shortcut: "/love-birds.png",
43
+ apple: "/love-birds.png",
44
+ },
45
+ alternates: {
46
+ canonical: "/",
32
47
  },
33
48
  openGraph: {
34
49
  type: "website",
@@ -39,10 +54,10 @@ export const metadata: Metadata = {
39
54
  siteName: siteConfig.name,
40
55
  images: [
41
56
  {
42
- url: "/pw/020.jpg",
57
+ url: "https://www.titas-sukanya-for.life/invite.jpeg",
43
58
  width: 1200,
44
59
  height: 630,
45
- alt: "Titas & Sukanya Wedding",
60
+ alt: "Titas & Sukanya Wedding Invitation",
46
61
  },
47
62
  ],
48
63
  },
@@ -50,9 +65,20 @@ export const metadata: Metadata = {
50
65
  card: "summary_large_image",
51
66
  title: siteConfig.name,
52
67
  description: siteConfig.description,
53
- images: ["/pw/020.jpg"],
68
+ images: ["https://www.titas-sukanya-for.life/invite.jpeg"],
54
69
  creator: "@titas",
55
70
  },
71
+ robots: {
72
+ index: true,
73
+ follow: true,
74
+ googleBot: {
75
+ index: true,
76
+ follow: true,
77
+ "max-video-preview": -1,
78
+ "max-image-preview": "large",
79
+ "max-snippet": -1,
80
+ },
81
+ },
56
82
  };
57
83
 
58
84
  export const viewport: Viewport = {
@@ -62,11 +88,7 @@ export const viewport: Viewport = {
62
88
  ],
63
89
  };
64
90
 
65
- export default function RootLayout({
66
- children,
67
- }: {
68
- children: React.ReactNode;
69
- }) {
91
+ function RootLayout({ children }: { children: React.ReactNode }) {
70
92
  return (
71
93
  <html
72
94
  suppressHydrationWarning
@@ -77,7 +99,9 @@ export default function RootLayout({
77
99
  )}
78
100
  lang="en"
79
101
  >
80
- <head />
102
+ <head>
103
+ <SchemaMarkup />
104
+ </head>
81
105
  <body
82
106
  className={clsx("min-h-screen bg-background font-sans antialiased")}
83
107
  >
@@ -96,3 +120,5 @@ export default function RootLayout({
96
120
  </html>
97
121
  );
98
122
  }
123
+
124
+ export default RootLayout;
@@ -11,7 +11,7 @@ const importantDates = [
11
11
  {
12
12
  title: "The Proposal",
13
13
  date: "15th March 2025",
14
- note: "The day Titas finally asked Sukanya out with a ring.",
14
+ note: "The day the proposal happened with a ring.",
15
15
  image: "/Images/19.jpg",
16
16
  },
17
17
  {
@@ -85,7 +85,13 @@ export default function DatesPage() {
85
85
  <div className="flex flex-col gap-12 md:gap-24">
86
86
  {importantDates.map((item, index) => {
87
87
  const eventDate = parseDate(item.date);
88
- const isPast = now ? now > eventDate : false;
88
+ // Mark as completed only after one full day has passed (24 hours after end of day)
89
+ const completionThreshold = new Date(eventDate);
90
+
91
+ completionThreshold.setDate(completionThreshold.getDate() + 1);
92
+ completionThreshold.setHours(23, 59, 59, 999);
93
+
94
+ const isPast = now ? now > completionThreshold : false;
89
95
  const isLeft = index % 2 === 0;
90
96
 
91
97
  return (
package/app/page.tsx CHANGED
@@ -1,5 +1,11 @@
1
1
  import Herohome from "@/components/hero";
2
+ import { AdminLinks } from "@/components/AdminLinks";
2
3
 
3
4
  export default function Home() {
4
- return <Herohome />;
5
+ return (
6
+ <>
7
+ <AdminLinks />
8
+ <Herohome />
9
+ </>
10
+ );
5
11
  }
package/app/providers.tsx CHANGED
@@ -21,7 +21,7 @@ declare module "@react-types/shared" {
21
21
  }
22
22
  }
23
23
 
24
- export function Providers({ children, themeProps }: ProvidersProps) {
24
+ export default function Providers({ children, themeProps }: ProvidersProps) {
25
25
  const router = useRouter();
26
26
 
27
27
  return (