@titas_mallick/wedding-site-gen 1.0.9 → 2.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.
Files changed (123) hide show
  1. package/README.md +70 -184
  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 +49 -21
  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
package/README.md CHANGED
@@ -1,212 +1,98 @@
1
- # AI-Powered Wedding Website Generator 💍
1
+ # 💍 Wedding Website Generator v2.0
2
2
 
3
- Create a stunning, interactive, and intelligent wedding website in seconds. This tool scaffolds a complete Next.js application tailored to your wedding.
3
+ > **Empower your wedding with AI.** Transform your special day into a digital masterpiece with an interactive invitation, AI-powered concierge, and real-time guest engagement.
4
4
 
5
- ## 🚀 Quick Start (No Installation Required)
5
+ ---
6
6
 
7
- Simply run the following command in your terminal:
7
+ ## 🚀 The A-B-C Quick Start
8
8
 
9
+ ### Step A: Generate Your Codebase
10
+ Open your terminal and run the following command to scaffold your project:
9
11
  ```bash
10
12
  npx @titas_mallick/wedding-site-gen
11
13
  ```
12
-
13
- > [!WARNING]
14
- > **⚠️ CRITICAL: Asset Replacement Required**
15
- > This generator includes **sample images** (in `/public`) and **sample text** meant for demonstration. You **MUST** replace `groom.jpg`, `bride.jpg`, and all photos in `/public/pw` and `/public/Images` with your own assets before publishing your website. Using the sample images may lead to copyright issues or a confusing guest experience.
16
-
17
- ### What happens next?
18
- 1. **Interactive Setup**: The CLI will ask for:
19
- - Groom & Bride Names
20
- - Wedding Date & Location Details
21
- - Admin Email (for managing guestbook & song requests)
22
- - UPI ID (for digital gifts)
23
- 2. **Scaffolding**: It will generate a new folder with your personalized wedding website.
24
- 3. **Deployment Ready**: Follow the onscreen instructions to install dependencies and start your site!
14
+ *The CLI will guide you through couple names, wedding dates, and theme selection.*
15
+
16
+ ### Step B: Infrastructure & Environment
17
+ 1. **Navigate & Install**:
18
+ ```bash
19
+ cd your-wedding-folder
20
+ npm install
21
+ ```
22
+ 2. **Firebase Setup**:
23
+ - Create a project at [Firebase Console](https://console.firebase.google.com/).
24
+ - Enable **Firestore** and **Authentication** (Email/Password).
25
+ - Copy the `firestore.rules` file content from your project into the Firebase console.
26
+ 3. **Configuration**:
27
+ - Rename `.env.example` to `.env.local` (or create a new one).
28
+ - Fill in your Firebase keys, Gemini API key, and Cloudinary credentials.
29
+
30
+ ### Step C: Asset Personalization
31
+ This is where the magic happens. Replace the placeholders in the `public/` directory with your own files.
25
32
 
26
33
  ---
27
34
 
28
- ## Core Features
29
-
30
- ### 🤖 AI-Powered Intelligence
31
- - **Wedding Concierge (Gemini 2.5-flash)**: A sitewide floating chatbot that answers guest queries about venues, schedules, and the couple's 10-year journey.
32
- - **Smart Bengali Translation**: Instantly translates invitation details into elegant, formal Bengali script for guests.
33
- - **AI Content Refinement**: An admin tool to polish rough updates into poetic, wedding-appropriate announcements.
34
- - **Sentiment Wall**: Automatically summarizes guest wishes into a beautiful "Collective Blessing" using AI analysis.
35
-
36
- ### 📸 Interactive Guest Experience
37
- - **Digital Guestbook**: Allows guests to upload photos directly from their devices. Features client-side compression/resizing and an immersive lightbox gallery.
38
- - **Reception Playlist**: A real-time queue where guests can request songs for the reception party.
39
- - **Live RSVP System**: Secure confirm-attendance form with food preferences and guest counts.
40
- - **Travel & Stay Guide**: Dynamic, venue-aware guide with heritage highlights, hotel recommendations, and "Bengali 101."
41
-
42
- ### 🎥 Broadcast & Display
43
- - **OBS Broadcast Overlay**: A dedicated `/updates/overlay` route designed for venue video walls. Features a real-time clock, QR codes, and a chroma-key ready green background.
44
- - **Live Countdown**: High-impact, animated timer counting down to the wedding on January 23, 2026.
45
-
46
- ## 🚀 Tech Stack
47
-
48
- - **Framework**: Next.js (App Router)
49
- - **UI Components**: HeroUI (formerly NextUI)
50
- - **Animations**: Framer Motion
51
- - **Database & Auth**: Firebase (Firestore & Authentication)
52
- - **AI Model**: Google Gemini 2.5-flash
53
- - **Image Hosting**: Cloudinary (Unsigned Uploads)
54
- - **Analytics**: Vercel Analytics
55
-
56
- ## 📁 Project Architecture & Codebase Map
57
-
58
- ### 1. Route Structure (`/app`)
59
- - **`/invitation/[slug]`**: The core engine for personalized digital invites. It reads the `slug` (guest name) and displays a customized experience.
60
- - **`/[groom]` & `/[bride]`**: (Dynamically renamed by CLI) Individual bio pages for the couple.
61
- - *Modification Point*: Edit these files to change personal stories or education/work history.
62
- - **`/guestbook`**: Handles real-time photo uploads and gallery display.
63
- - *Tech Detail*: Uses Cloudinary for storage and Firestore to track image metadata.
64
- - **`/song-requests`**: A dual-purpose route. Guests see a request form; Admins see a management queue.
65
- - **`/updates/overlay`**: A specialized green-screen route for OBS.
66
- - *Usage*: Add this as a Browser Source in OBS with a Chroma Key filter.
67
-
68
- ### 2. Configuration (`/config`)
69
- - **`site.ts`**: The central source of truth for navigation links, site name, and SEO metadata.
70
- - **Modify here** if you want to add/remove menu items or change the site description.
71
- - **`firebase.ts`**: Client-side Firebase initialization.
72
- - **`firebase-admin.js`**: Server-side Admin SDK configuration for secure operations (RSVP management, etc.).
73
-
74
- ### 3. Key Components (`/components`)
75
- - **`ConciergeBot.tsx`**: The Gemini-powered AI assistant.
76
- - *Customization*: Edit the `systemInstruction` in this file to change the bot's personality or the facts it knows about your wedding.
77
- - **`CountdownTimer.tsx`**: The main landing page countdown.
78
- - *Logic*: Uses a `setInterval` to calculate time until the `weddingStart` date.
79
- - **`OurStory.tsx`**: The timeline component showing the couple's history.
80
-
81
- ## 🧠 Deep-Dive: How it Works
82
-
83
- ### 1. AI Wedding Concierge (`ConciergeBot.tsx`)
84
- The floating assistant uses **Google Gemini 2.5-flash**.
85
- - **Personality**: Defined in the `systemInstruction` constant. It's programmed to be a warm, respectful Indian wedding host.
86
- - **Customization**: To update facts (like venue changes), simply edit the `Key Info` section inside the `systemInstruction` in `components/ConciergeBot.tsx`.
87
-
88
- ### 2. Personalized Invitations (`/app/invitation/[slug]`)
89
- This route handles dynamic guest experiences.
90
- - **Dynamic Content**: Based on the `slug` (guest ID), it fetches personalized data from Firestore.
91
- - **Smart Bengali Translation**: Uses Gemini to translate invitation details into formal Bengali when the user clicks "বাংলায় দেখুন".
92
-
93
- ### 3. Digital Guestbook (`/app/guestbook`)
94
- - **Image Processing**: Before uploading, photos are client-side resized and compressed (JPEG 70% quality) to ensure fast uploads even on mobile networks.
95
- - **Storage**: Images go to Cloudinary, and the resulting URL is saved to Firestore.
96
-
97
- ### 4. Sentiment Wall (`/app/sagun`)
98
- - **Collective Blessing**: When guests leave wishes, Gemini periodically summarizes the latest messages into a single "poetic paragraph" that updates live on the "Sentiment Wall."
35
+ ## 🎨 Asset Mapping (Photo & Media Guide)
99
36
 
100
- ---
37
+ To ensure the site looks perfect, replace these files but **keep the filenames exactly the same**.
101
38
 
102
- ## 📊 Database Structure (Firestore)
39
+ ### 📸 Core Portraits (`/public/`)
40
+ | Filename | Purpose | Recommended Aspect Ratio |
41
+ | :--- | :--- | :--- |
42
+ | `bride.jpg` | Main profile photo of the Bride. | 3:4 (Portrait) |
43
+ | `groom.jpg` | Main profile photo of the Groom. | 3:4 (Portrait) |
44
+ | `love-birds.png` | Avatar for the AI Chatbot. | 1:1 (Square) |
45
+ | `qr.png` | UPI Payment QR code for gifts. | 1:1 (Square) |
103
46
 
104
- To make the site functional, create these collections in Firestore:
47
+ ### 🗓️ Milestone Timeline (`/public/Images/`)
48
+ These images appear on the **"Mark the Dates"** timeline.
49
+ - `19.jpg`: The Proposal Milestone.
50
+ - `Patipatra.jpeg`: The Date-Fixing/Traditional Ceremony.
51
+ - `22.jpg`: The Engagement Milestone.
52
+ - `21.jpg`: The Wedding Day Milestone.
53
+ - `20.jpg`: The Reception Milestone.
105
54
 
106
- ### `invitation` (Collection)
107
- Each document ID should be a unique `slug` (e.g., `rahul-wedding`).
108
- ```json
109
- {
110
- "name": "Rahul & Priya",
111
- "invitedGuests": 2,
112
- "relation": "Close Friend",
113
- "familySide": "groom",
114
- "invitedFor": ["wedding", "reception"]
115
- }
116
- ```
55
+ ### 🏔️ Pre-Wedding & Memories Gallery
56
+ The **"Memories"** page is designed to showcase your journey.
57
+ - **Bulk Uploads**: Place all your pre-wedding shoot photos in `/public/Images/`.
58
+ - **Naming**: Use descriptive names or simply number them. The gallery will automatically generate a beautiful masonry layout for all images found in this directory.
117
59
 
118
- ### `rsvps` (Collection)
119
- Automatically populated when guests confirm attendance.
120
- ```json
121
- {
122
- "guestId": "rahul-wedding",
123
- "attending": "yes",
124
- "guests": 2,
125
- "food": "non-veg",
126
- "note": "Looking forward to it!",
127
- "timestamp": "serverTime"
128
- }
129
- ```
60
+ ---
130
61
 
131
- ### `guestbook` & `wishes` (Collections)
132
- Store guest memories and text blessings respectively.
133
-
134
- ### 🔒 Recommended Firestore Rules
135
- Paste these into your Firebase Console to secure your data (also available in `firestore.rules`):
136
- ```javascript
137
- rules_version = '2';
138
- service cloud.firestore {
139
- match /databases/{database}/documents {
140
- match /wishes/{wishId} { allow read, write: if true; }
141
- match /song_requests/{requestId} { allow read, write: if true; }
142
- match /guestbook/{entryId} { allow read, write: if true; }
143
- match /rsvps/{rsvpId} { allow read, write: if true; }
144
- match /{document=**} {
145
- allow read: if true;
146
- allow write: if request.auth != null;
147
- }
148
- }
149
- }
150
- ```
62
+ ## 🤖 Advanced AI Features
151
63
 
152
- ---
64
+ ### 1. The Wedding Concierge (Gemini 2.5)
65
+ Guests can chat with an AI assistant trained on your wedding data.
66
+ - **Setup**: Provide your `NEXT_PUBLIC_GEMINI_API_KEY` in `.env.local`.
67
+ - **Knowledge**: The bot automatically knows your venues, dates, and story based on the info you provided during Step A.
153
68
 
154
- ## 🎨 Customization & Development
69
+ ### 2. Automated Email Reminders
70
+ Send beautiful, automated reminders to your guest list.
71
+ - **Endpoint**: `YOUR_SITE_URL/api/email-reminders`
72
+ - **Auth**: Requires `Authorization: Bearer [YOUR_CRON_SECRET]`.
73
+ - **Cron Suggestion**: `0 10 10-26 1 *` (Runs daily at 10 AM during the wedding month).
155
74
 
156
- ### How to change the Theme/Colors
157
- This project uses **Tailwind CSS** and **HeroUI**.
158
- - To change the primary "Wedding Gold" or "Wedding Pink" colors, edit `tailwind.config.js`.
159
- - Search for the `extend.colors` section to update the hex codes.
75
+ ---
160
76
 
161
- ### What to Keep vs. What to Replace
162
- - **KEEP**: The `/app/invitation` logic and `/components/providers.tsx` as they handle the core framework setup.
163
- - **REPLACE**:
164
- - All images in `/public` (groom.jpg, bride.jpg, and gallery photos).
165
- - The text content in `app/[groom]/page.tsx` and `app/[bride]/page.tsx`.
166
- - The venue details in `app/mark-the-dates/page.tsx`.
77
+ ## ⚙️ Environment Variables Checklist
167
78
 
168
- ### Running Locally for Development
169
- 1. Clone the generated repository.
170
- 2. Install dependencies: `npm install`.
171
- 3. Start the dev server: `npm run dev`.
172
- 4. Open [http://localhost:3000](http://localhost:3000).
79
+ | Key | Description |
80
+ | :--- | :--- |
81
+ | `NEXT_PUBLIC_ADMIN_EMAIL` | The email that has "Super User" powers (delete photos/songs). |
82
+ | `NEXT_PUBLIC_GEMINI_API_KEY` | Powers the Concierge and the AI Wish Summarizer. |
83
+ | `RESEND_API_KEY` | Used to send the actual emails for reminders. |
84
+ | `NEXT_PUBLIC_CLOUDINARY_URL` | Enables guests to upload photos to your live guestbook. |
173
85
 
174
86
  ---
175
87
 
176
- ## 🛠️ Setup & Configuration (Services)
177
-
178
- ### 1. Firebase (Database, Auth, and RSVP)
179
- 1. Go to the [Firebase Console](https://console.firebase.google.com/).
180
- 2. Create a new project.
181
- 3. Enable **Firestore Database** and **Authentication** (enable Email/Password provider).
182
- 4. Go to **Project Settings > General** and scroll down to "Your apps" to create a Web App.
183
- 5. Copy the `firebaseConfig` values into your `.env.local`:
184
- - `NEXT_PUBLIC_APIKEY`, `NEXT_PUBLIC_AUTHDOMAIN`, `NEXT_PUBLIC_PROJECTID`, etc.
185
- 6. **Admin SDK**: Go to **Project Settings > Service accounts**, click "Generate new private key", and use those values for the `FIREBASE_ADMIN_*` variables in `.env.local`.
186
- 7. **Security Rules**: Go to the **Rules** tab in Firestore and paste the contents of `firestore.rules` found in this project.
187
-
188
- ### 2. Google Gemini (AI Concierge)
189
- 1. Go to the [Google AI Studio](https://aistudio.google.com/).
190
- 2. Click **"Get API key"**.
191
- 3. Copy the key and paste it as `NEXT_PUBLIC_GEMINI_API_KEY` in your `.env.local`.
192
-
193
- ### 3. Cloudinary (Guestbook Photo Uploads)
194
- 1. Create a free account at [Cloudinary](https://cloudinary.com/).
195
- 2. Go to **Dashboard** and copy your **Cloud Name** into `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME`.
196
- 3. Go to **Settings > Upload** and scroll down to **Upload presets**.
197
- 4. Click "Add upload preset".
198
- 5. Set **Signing Mode** to **Unsigned**.
199
- 6. Set the name to `wedding` (or match whatever you put in `NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET`).
200
- 7. Save and you're ready!
88
+ ## 📁 Key Project Routes
89
+ - `/` - The Home Hero & Countdown.
90
+ - `/couple` - Your love story & individual bios.
91
+ - `/invitation/[guest-id]` - Personalized landing pages for guests.
92
+ - `/guestbook` - The real-time masonry photo wall.
93
+ - `/song-requests` - Interactive guest playlist.
201
94
 
202
95
  ---
203
96
 
204
- ## 📁 Project Structure Highlights
205
- - `/app/invitation/[slug]`: The core personalized guest experience.
206
- - `/app/updates/maker`: Admin dashboard for news and announcements.
207
- - `/app/song-requests`: Consolidated guest form and admin queue.
208
- - `/app/guestbook`: Real-time masonry gallery with Cloudinary integration.
209
- - `/components/ConciergeBot.tsx`: The sitewide AI assistant component.
210
-
211
97
  ## 📜 License
212
- This project is open-source and intended for wedding celebrations.
98
+ MIT License. Built with ❤️ for the open-source wedding community.
@@ -0,0 +1,240 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import * as admin from "firebase-admin";
3
+ import { Resend } from "resend";
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
+ const resend = new Resend(process.env.RESEND_API_KEY);
14
+
15
+ const EVENTS: Record<string, { title: string; date: string; venue: string; isoDate: string; details: string; startTime: string; endTime: string; location: string }> = {
16
+ registration: {
17
+ title: "Engagement Ceremony",
18
+ date: "23rd November 2025",
19
+ venue: "Venue City",
20
+ isoDate: "2025-11-23",
21
+ details: "Join us for the Engagement Ceremony of the couple",
22
+ startTime: "20251123T043000Z",
23
+ endTime: "20251123T083000Z",
24
+ location: "Venue Name, City"
25
+ },
26
+ wedding: {
27
+ title: "Wedding Ceremony",
28
+ date: "23rd January 2026",
29
+ venue: "Venue City",
30
+ isoDate: "2026-01-23",
31
+ details: "The Wedding Ceremony",
32
+ startTime: "20260123T123000Z",
33
+ endTime: "20260123T163000Z",
34
+ location: "Venue Name, City"
35
+ },
36
+ reception: {
37
+ title: "Reception Celebration",
38
+ date: "25th January 2026",
39
+ venue: "Venue City",
40
+ isoDate: "2026-01-25",
41
+ details: "Reception Party for the couple",
42
+ startTime: "20260125T123000Z",
43
+ endTime: "20260125T163000Z",
44
+ location: "Venue Name, City"
45
+ },
46
+ };
47
+
48
+ const getCalendarLink = (event: any) => {
49
+ return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(event.title)}&dates=${event.startTime}/${event.endTime}&details=${encodeURIComponent(event.details)}&location=${encodeURIComponent(event.location)}`;
50
+ };
51
+
52
+ const getDaysToGo = (isoDate: string) => {
53
+ const eventDate = new Date(isoDate);
54
+ // Get current time in IST
55
+ const nowIST = new Date(new Date().toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
56
+
57
+ eventDate.setHours(0, 0, 0, 0);
58
+ nowIST.setHours(0, 0, 0, 0);
59
+
60
+ const diffTime = eventDate.getTime() - nowIST.getTime();
61
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
62
+
63
+ if (diffDays === 0) return "TODAY! 🎉";
64
+ if (diffDays === 1) return "TOMORROW! ⏳";
65
+ if (diffDays < 0) return "Completed ✔️";
66
+ return `${diffDays} days to go`;
67
+ };
68
+
69
+ const emailTemplate = (guestName: string, invitedFor: string[] = []) => {
70
+ const eventDetailsHtml = invitedFor
71
+ .map((eventKey) => {
72
+ const event = EVENTS[eventKey.toLowerCase()];
73
+ if (!event) return "";
74
+
75
+ const daysMessage = getDaysToGo(event.isoDate);
76
+ const calendarLink = getCalendarLink(event);
77
+
78
+ return `
79
+ <div class="detail-item">
80
+ <strong>${event.title}</strong>
81
+ <span class="days-badge" style="background-color: #fce7f3; color: #db2777; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: bold; margin-left: 8px;">${daysMessage}</span>
82
+ </div>
83
+ <div class="detail-item">📅 ${event.date}</div>
84
+ <div class="detail-item">📍 ${event.venue}</div>
85
+ <div class="detail-item" style="margin-top: 8px;">
86
+ <a href="${calendarLink}" style="color: #ec4899; text-decoration: underline; font-size: 14px;">📅 Add to Calendar</a>
87
+ </div>
88
+ <br>
89
+ `;
90
+ })
91
+ .join("");
92
+
93
+ return `
94
+ <!DOCTYPE html>
95
+ <html>
96
+ <head>
97
+ <style>
98
+ body { font-family: 'Helvetica', 'Arial', sans-serif; background-color: #fdf2f8; color: #4a4a4a; margin: 0; padding: 0; }
99
+ .container { max-width: 600px; margin: 20px auto; background-color: #ffffff; padding: 30px; border-radius: 16px; border: 1px solid #fbcfe8; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
100
+ .header { text-align: center; color: #ec4899; font-family: 'Brush Script MT', cursive; font-size: 36px; margin-bottom: 24px; border-bottom: 2px solid #fce7f3; padding-bottom: 16px; }
101
+ .content { font-size: 16px; line-height: 1.6; color: #374151; }
102
+ .details { background-color: #fffbeb; padding: 20px; border-radius: 12px; margin: 24px 0; border: 1px solid #edd5a3; }
103
+ .detail-item { margin-bottom: 8px; }
104
+ .footer { text-align: center; font-size: 12px; color: #9ca3af; margin-top: 32px; border-top: 1px solid #f3f4f6; padding-top: 16px; }
105
+ .btn { display: inline-block; background-color: #ec4899; color: #ffffff !important; padding: 12px 32px; text-decoration: none; border-radius: 50px; font-weight: bold; margin-top: 16px; }
106
+ </style>
107
+ </head>
108
+ <body>
109
+ <div class="container">
110
+ <div class="header">Groom & Bride</div>
111
+ <div class="content">
112
+ <p>Dear <strong>${guestName}</strong>,</p>
113
+ <p>We are counting down the days and are so excited to celebrate our union with you! This is a gentle reminder that our big day is just around the corner.</p>
114
+ ${eventDetailsHtml ? `<div class="details">${eventDetailsHtml}</div>` : ''}
115
+ <p>Please visit our wedding website for the full schedule, maps, and travel guide.</p>
116
+ <div style="text-align: center;">
117
+ <a href="https://www.your-wedding-site.com" class="btn">View Wedding Details</a>
118
+ </div>
119
+ <p style="text-align: center; margin-top: 30px;">With Love,<br>Groom & Bride</p>
120
+ </div>
121
+ <div class="footer">
122
+ <p>You received this email because you signed up for reminders on our wedding website.</p>
123
+ </div>
124
+ </div>
125
+ </body>
126
+ </html>
127
+ `;
128
+ };
129
+
130
+ export async function GET(request: NextRequest) {
131
+ const authHeader = request.headers.get('Authorization');
132
+ if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
133
+ return new Response('Unauthorized', { status: 401 });
134
+ }
135
+
136
+ try {
137
+ const snapshot = await db.collection("email-reminders").get();
138
+
139
+ if (snapshot.empty) {
140
+ return NextResponse.json({ message: "No email reminders found." });
141
+ }
142
+
143
+ // Deduplicate by email
144
+ const uniqueRecipients = new Map<string, any>();
145
+ snapshot.docs.forEach(doc => {
146
+ const data = doc.data();
147
+ if (data.email) {
148
+ uniqueRecipients.set(data.email, data);
149
+ }
150
+ });
151
+
152
+ const results = [];
153
+ const recipients = Array.from(uniqueRecipients.values());
154
+
155
+ // Get current time in IST
156
+ const nowIST = new Date(new Date().toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
157
+
158
+ const isFutureOrToday = (isoDateStr: string) => {
159
+ const eventDate = new Date(isoDateStr);
160
+ // Set event date to end of day in IST
161
+ eventDate.setHours(23, 59, 59, 999);
162
+ return eventDate >= nowIST;
163
+ };
164
+
165
+ for (const recipient of recipients) {
166
+ const { email, guestName, invitedFor } = recipient;
167
+
168
+ const upcomingEvents = (invitedFor || []).filter((eventKey: string) => {
169
+ const evt = EVENTS[eventKey.toLowerCase()];
170
+ return evt && isFutureOrToday(evt.isoDate);
171
+ });
172
+
173
+ if (upcomingEvents.length === 0) {
174
+ results.push({ email, status: 'skipped', reason: 'No upcoming events' });
175
+ continue;
176
+ }
177
+
178
+ try {
179
+ const { data: emailData, error } = await resend.emails.send({
180
+ from: 'Groom & Bride <wedding@your-wedding-site.com>',
181
+ to: [email],
182
+ subject: "Reminder: Wedding Celebration! 🎉",
183
+ html: emailTemplate(guestName || "Guest", upcomingEvents),
184
+ });
185
+
186
+ if (error) {
187
+ console.error(`Failed to send to ${email}:`, error);
188
+ results.push({ email, status: 'failed', error });
189
+ } else {
190
+ results.push({ email, status: 'sent', id: emailData?.id });
191
+ }
192
+ } catch (err) {
193
+ console.error(`Exception sending to ${email}:`, err);
194
+ results.push({ email, status: 'failed', error: err });
195
+ }
196
+ }
197
+
198
+ const sentCount = results.filter(r => r?.status === 'sent').length;
199
+ const failedCount = results.length - sentCount;
200
+
201
+ // Send Report to Admin
202
+ const reportHtml = `
203
+ <h1>Email Reminder Report</h1>
204
+ <p><strong>Total Unique Recipients:</strong> ${results.length}</p>
205
+ <p><strong>Successfully Sent:</strong> ${sentCount}</p>
206
+ <p><strong>Failed:</strong> ${failedCount}</p>
207
+ <hr/>
208
+ <h3>Details:</h3>
209
+ <ul>
210
+ ${results.map(r => `<li>${r.email}: <strong>${r.status}</strong> ${r.error ? `(${JSON.stringify(r.error)})` : ''}</li>`).join('')}
211
+ </ul>
212
+ `;
213
+
214
+ try {
215
+ await resend.emails.send({
216
+ from: 'Wedding Bot <bot@your-wedding-site.com>',
217
+ to: [process.env.ADMIN_EMAIL || 'admin@example.com'],
218
+ subject: `Wedding Reminder Report: ${sentCount}/${results.length} Sent`,
219
+ html: reportHtml,
220
+ });
221
+ } catch (reportError) {
222
+ console.error("Failed to send admin report:", reportError);
223
+ }
224
+
225
+ return NextResponse.json({
226
+ success: true,
227
+ total: results.length,
228
+ sent: sentCount,
229
+ failed: failedCount,
230
+ results
231
+ });
232
+
233
+ } catch (error) {
234
+ console.error("Error processing email reminders:", error);
235
+ return NextResponse.json(
236
+ { error: "Internal Server Error" },
237
+ { status: 500 }
238
+ );
239
+ }
240
+ }
@@ -48,13 +48,13 @@ export default function CouplePage() {
48
48
  <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/60 z-10 opacity-80 group-hover:opacity-100 transition-opacity" />
49
49
  <Image
50
50
  removeWrapper
51
- alt="Sukanya"
51
+ alt="Bride"
52
52
  className="z-0 w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-700"
53
53
  src="/bride.jpg"
54
54
  />
55
55
  <div className="absolute bottom-0 left-0 right-0 p-6 z-20 text-white">
56
56
  <h3 className={`${fontCursive.className} text-4xl mb-1`}>
57
- Sukanya
57
+ Bride
58
58
  </h3>
59
59
  <p className="text-white/80 font-medium">The Bride</p>
60
60
  </div>
@@ -95,13 +95,13 @@ export default function CouplePage() {
95
95
  <div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/60 z-10 opacity-80 group-hover:opacity-100 transition-opacity" />
96
96
  <Image
97
97
  removeWrapper
98
- alt="Titas"
98
+ alt="Groom"
99
99
  className="z-0 w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-700"
100
100
  src="/groom.jpg"
101
101
  />
102
102
  <div className="absolute bottom-0 left-0 right-0 p-6 z-20 text-white">
103
103
  <h3 className={`${fontCursive.className} text-4xl mb-1`}>
104
- Titas
104
+ Groom
105
105
  </h3>
106
106
  <p className="text-white/80 font-medium">The Groom</p>
107
107
  </div>