@titas_mallick/wedding-site-gen 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -170
- package/app/api/email-reminders/route.ts +240 -0
- package/app/couple/page.tsx +4 -4
- package/app/game/page.tsx +298 -0
- package/app/guestbook/page.tsx +270 -152
- package/app/invitation/[slug]/layout.tsx +4 -2
- package/app/invitation/[slug]/page.tsx +303 -84
- package/app/invitation/actions.ts +49 -0
- package/app/invitation/maker/auth.js +1 -1
- package/app/invitation/maker/guestAdder.js +4 -0
- package/app/invitation/maker/guestShower.js +39 -8
- package/app/invitation/maker/layout.tsx +1 -1
- package/app/invitation/maker/page.js +9 -7
- package/app/invitation/maker/rsvpViewer.js +90 -8
- package/app/layout.tsx +40 -14
- package/app/mark-the-dates/page.tsx +8 -2
- package/app/page.tsx +7 -1
- package/app/providers.tsx +1 -1
- package/app/sagun/page.tsx +224 -76
- package/app/song-requests/page.tsx +242 -105
- package/app/sukanya/page.tsx +9 -13
- package/app/titas/page.tsx +8 -24
- package/app/travel-guide/page.tsx +361 -120
- package/app/updates/maker/page.js +2 -2
- package/app/updates/overlay/page.tsx +65 -30
- package/app/updates/page.js +3 -3
- package/cli.mjs +26 -15
- package/components/AdminAuth.tsx +145 -0
- package/components/AdminLinks.tsx +120 -0
- package/components/ConciergeBot.tsx +104 -44
- package/components/CountdownTimer.tsx +37 -15
- package/components/Gallery.tsx +1 -1
- package/components/LiveVideos.tsx +27 -15
- package/components/OurStory.tsx +1 -1
- package/components/SchemaMarkup.tsx +74 -0
- package/components/certificate.jsx +287 -300
- package/components/footer.tsx +2 -0
- package/components/hero.tsx +47 -4
- package/components/icons.tsx +45 -0
- package/components/importantNews.js +168 -168
- package/components/navbar.tsx +113 -18
- package/components/updates.tsx +36 -26
- package/config/firebase-admin.js +14 -17
- package/config/firebase.ts +4 -2
- package/config/site.ts +10 -2
- package/firestore.rules +6 -1
- package/next-sitemap.config.js +21 -0
- package/package.json +4 -3
- package/public/corner1-01.svg +0 -0
- package/public/love-birds.png +0 -0
- package/public/next.svg +0 -0
- package/public/pubqr.png +0 -0
- package/public/pw/sample.jpg +0 -0
- package/public/qr.png +0 -0
- package/public/sample.jpg +0 -0
- package/public/vercel.svg +0 -0
- package/vercel.json +1 -0
- package/.recover +0 -9
- package/next-env.d.ts +0 -6
- package/public/DCV.gif +0 -0
- package/public/DCV2.gif +0 -0
- package/public/DCV3.gif +0 -0
- package/public/Images/1.jpg +0 -0
- package/public/Images/11.jpg +0 -0
- package/public/Images/12.jpg +0 -0
- package/public/Images/13.jpg +0 -0
- package/public/Images/14.jpg +0 -0
- package/public/Images/15.jpg +0 -0
- package/public/Images/16.jpg +0 -0
- package/public/Images/17.jpg +0 -0
- package/public/Images/18.jpg +0 -0
- package/public/Images/19.jpg +0 -0
- package/public/Images/2.jpg +0 -0
- package/public/Images/21.jpg +0 -0
- package/public/Images/22.jpg +0 -0
- package/public/Images/3.jpg +0 -0
- package/public/Images/4.jpg +0 -0
- package/public/Images/5.jpg +0 -0
- package/public/Images/6.jpg +0 -0
- package/public/Images/7.jpg +0 -0
- package/public/Images/8.jpg +0 -0
- package/public/Images/9.jpg +0 -0
- package/public/Images/9b.jpg +0 -0
- package/public/Images/Patipatra.jpeg +0 -0
- package/public/audio (1).mp3 +0 -0
- package/public/audio (2).mp3 +0 -0
- package/public/bride.jpg +0 -0
- package/public/groom.jpg +0 -0
- package/public/invite.png +0 -0
- package/public/pw/001.jpg +0 -0
- package/public/pw/002.jpg +0 -0
- package/public/pw/003.jpg +0 -0
- package/public/pw/004.jpg +0 -0
- package/public/pw/005.jpg +0 -0
- package/public/pw/006.jpg +0 -0
- package/public/pw/007.jpg +0 -0
- package/public/pw/008.jpg +0 -0
- package/public/pw/009.jpg +0 -0
- package/public/pw/010.jpg +0 -0
- package/public/pw/011.jpg +0 -0
- package/public/pw/012.jpg +0 -0
- package/public/pw/013.jpg +0 -0
- package/public/pw/014.jpg +0 -0
- package/public/pw/015.jpg +0 -0
- package/public/pw/016.jpg +0 -0
- package/public/pw/017.jpg +0 -0
- package/public/pw/018.jpg +0 -0
- package/public/pw/019.jpg +0 -0
- package/public/pw/020.jpg +0 -0
- package/public/pw/021.jpg +0 -0
- package/public/pw/022.jpg +0 -0
- package/public/pw/023.jpg +0 -0
- package/public/pw/024.jpg +0 -0
- package/public/pw/025.jpg +0 -0
- package/public/pw/026.jpg +0 -0
- package/public/pw/027.jpg +0 -0
- package/public/pw/028.jpg +0 -0
- package/public/pw/029.jpg +0 -0
- package/public/pw/030.jpg +0 -0
- package/public/pw/031.jpg +0 -0
- package/public/pw/032.jpg +0 -0
- package/tsconfig.tsbuildinfo +0 -1
- /package/public/Images/{20.jpg → sample.jpg} +0 -0
package/README.md
CHANGED
|
@@ -1,138 +1,51 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 💍 Wedding Website Generator v2.0 (AI-Powered)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Transform your wedding journey with a sophisticated, AI-driven digital experience. This generator scaffolds a production-ready Next.js application featuring an AI concierge, digital guestbook, real-time song requests, and automated guest reminders.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 🚀 Phase 1: Generation (The A-B-C Guide)
|
|
8
8
|
|
|
9
|
+
### Step A: Initialize Your Project
|
|
10
|
+
Run the following command in your terminal to start the interactive setup:
|
|
9
11
|
```bash
|
|
10
12
|
npx @titas_mallick/wedding-site-gen
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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."
|
|
15
|
+
### Step B: CLI Prompt Reference
|
|
16
|
+
The generator will ask you several questions. Here is how to answer them:
|
|
17
|
+
|
|
18
|
+
| Prompt | Example Input | Purpose |
|
|
19
|
+
| :--- | :--- | :--- |
|
|
20
|
+
| **Groom's First Name** | `Titas` | Used for URLs and short mentions. |
|
|
21
|
+
| **Groom's Full Name** | `Titas Mallick` | Used in official bio and certificate pages. |
|
|
22
|
+
| **Bride's First Name** | `Sukanya` | Used for URLs and short mentions. |
|
|
23
|
+
| **Bride's Full Name** | `Sukanya Saha` | Used in official bio and certificate pages. |
|
|
24
|
+
| **Wedding Date** | `January 23, 2026` | Displayed text on the countdown and hero. |
|
|
25
|
+
| **Wedding Date ISO** | `2026-01-23` | Powers the technical countdown logic. |
|
|
26
|
+
| **Admin Email** | `admin@wedding.com` | **CRITICAL**: Log in with this email to manage content. |
|
|
27
|
+
| **UPI ID** | `wedding@okaxis` | Used for the "Sagun" (Gift) QR code generator. |
|
|
28
|
+
| **Website URL** | `https://our-wedding.com` | Used for email links and SEO. |
|
|
29
|
+
| **Wedding Hashtag** | `#TitasWedsSukanya` | Displayed across the site. |
|
|
30
|
+
| **Visual Theme** | `1` (Pink & Gold) | Sets the primary color palette (Pink, Blue, Green, or Red). |
|
|
31
|
+
| **Target Directory** | `my-wedding` | The name of the folder where code will be saved. |
|
|
99
32
|
|
|
100
33
|
---
|
|
101
34
|
|
|
102
|
-
##
|
|
103
|
-
|
|
104
|
-
To make the site functional, create these collections in Firestore:
|
|
35
|
+
## 🛠️ Phase 2: Infrastructure Setup
|
|
105
36
|
|
|
106
|
-
###
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"invitedFor": ["wedding", "reception"]
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
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
|
-
```
|
|
37
|
+
### 1. Firebase (Database & Auth)
|
|
38
|
+
1. Go to the [Firebase Console](https://console.firebase.google.com/).
|
|
39
|
+
2. **Create a Project**: Give it a name (e.g., `wedding-project`).
|
|
40
|
+
3. **Authentication**: Enable "Email/Password" provider in the Auth tab.
|
|
41
|
+
4. **Firestore**: Create a Database in "Production Mode".
|
|
42
|
+
5. **Project Settings**:
|
|
43
|
+
- Click the **Web Icon (</>)** to register a web app. Copy the `firebaseConfig` keys for your `.env.local`.
|
|
44
|
+
- Go to **Service Accounts** > **Generate New Private Key**. This downloads a JSON file. Use these values for the `FIREBASE_ADMIN` variables.
|
|
130
45
|
|
|
131
|
-
###
|
|
132
|
-
|
|
46
|
+
### 2. Firestore Security Rules
|
|
47
|
+
Copy the following into the **Rules** tab of your Firestore console to allow guests to post wishes/songs while keeping admin functions secure:
|
|
133
48
|
|
|
134
|
-
### 🔒 Recommended Firestore Rules
|
|
135
|
-
Paste these into your Firebase Console to secure your data (also available in `firestore.rules`):
|
|
136
49
|
```javascript
|
|
137
50
|
rules_version = '2';
|
|
138
51
|
service cloud.firestore {
|
|
@@ -141,6 +54,10 @@ service cloud.firestore {
|
|
|
141
54
|
match /song_requests/{requestId} { allow read, write: if true; }
|
|
142
55
|
match /guestbook/{entryId} { allow read, write: if true; }
|
|
143
56
|
match /rsvps/{rsvpId} { allow read, write: if true; }
|
|
57
|
+
match /email-reminders/{reminderId} {
|
|
58
|
+
allow read: if true;
|
|
59
|
+
allow write: if request.auth != null;
|
|
60
|
+
}
|
|
144
61
|
match /{document=**} {
|
|
145
62
|
allow read: if true;
|
|
146
63
|
allow write: if request.auth != null;
|
|
@@ -149,64 +66,81 @@ service cloud.firestore {
|
|
|
149
66
|
}
|
|
150
67
|
```
|
|
151
68
|
|
|
152
|
-
|
|
69
|
+
### 3. Cloudinary (Guestbook Uploads)
|
|
70
|
+
1. Sign up at [Cloudinary](https://cloudinary.com/).
|
|
71
|
+
2. In the **Dashboard**, copy your `Cloud Name`.
|
|
72
|
+
3. Go to **Settings** > **Upload** > **Upload Presets** and create a new preset named `wedding` with "Unsigned" signing mode.
|
|
73
|
+
|
|
74
|
+
### 4. Resend (Email Automation)
|
|
75
|
+
1. Sign up at [Resend.com](https://resend.com/).
|
|
76
|
+
2. Generate an **API Key** and add it to `.env.local`.
|
|
153
77
|
|
|
154
|
-
|
|
78
|
+
---
|
|
155
79
|
|
|
156
|
-
|
|
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.
|
|
80
|
+
## 🔐 Phase 3: Environment Configuration
|
|
160
81
|
|
|
161
|
-
|
|
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`.
|
|
82
|
+
Create a `.env.local` file in your generated project root and populate it:
|
|
167
83
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
84
|
+
```bash
|
|
85
|
+
# --- Firebase Client ---
|
|
86
|
+
NEXT_PUBLIC_APIKEY=AIza...
|
|
87
|
+
NEXT_PUBLIC_AUTHDOMAIN=your-project.firebaseapp.com
|
|
88
|
+
NEXT_PUBLIC_PROJECTID=your-project
|
|
89
|
+
NEXT_PUBLIC_STORAGEBUCKET=your-project.firebasestorage.app
|
|
90
|
+
NEXT_PUBLIC_SENDERID=...
|
|
91
|
+
NEXT_PUBLIC_APPID=...
|
|
92
|
+
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=...
|
|
93
|
+
|
|
94
|
+
# --- Firebase Admin (Secrets) ---
|
|
95
|
+
FIREBASE_ADMIN_PROJECT_ID=your-project
|
|
96
|
+
FIREBASE_ADMIN_PRIVATE_KEY_ID=...
|
|
97
|
+
FIREBASE_ADMIN_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
|
98
|
+
FIREBASE_ADMIN_CLIENT_EMAIL=firebase-adminsdk-...
|
|
99
|
+
FIREBASE_ADMIN_CLIENT_ID=...
|
|
100
|
+
|
|
101
|
+
# --- AI & Content ---
|
|
102
|
+
NEXT_PUBLIC_GEMINI_API_KEY=AIza... # Get from Google AI Studio
|
|
103
|
+
NEXT_PUBLIC_ADMIN_EMAIL=your-email@gmail.com # Must match login for admin powers
|
|
104
|
+
|
|
105
|
+
# --- Media & Email ---
|
|
106
|
+
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=...
|
|
107
|
+
NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=wedding
|
|
108
|
+
RESEND_API_KEY=re_...
|
|
109
|
+
CRON_SECRET=a-secure-random-string
|
|
110
|
+
```
|
|
173
111
|
|
|
174
112
|
---
|
|
175
113
|
|
|
176
|
-
##
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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!
|
|
114
|
+
## 🎨 Phase 4: Asset Personalization
|
|
115
|
+
|
|
116
|
+
Replace the files in `/public/` keeping the exact filenames:
|
|
117
|
+
|
|
118
|
+
| Location | Filename | Purpose |
|
|
119
|
+
| :--- | :--- | :--- |
|
|
120
|
+
| `/public/` | `bride.jpg` | Main portrait of the Bride (3:4 ratio). |
|
|
121
|
+
| `/public/` | `groom.jpg` | Main portrait of the Groom (3:4 ratio). |
|
|
122
|
+
| `/public/` | `qr.png` | Your UPI QR code for the Sagun page. |
|
|
123
|
+
| `/public/Images/` | `19.jpg` - `22.jpg` | Milestone images for the "Mark the Dates" timeline. |
|
|
124
|
+
| `/public/Images/` | `Patipatra.jpeg` | Image for the traditional date-fixing milestone. |
|
|
125
|
+
| `/public/Images/` | `* (any name)` | All other images in this folder automatically populate the **Memories** gallery. |
|
|
201
126
|
|
|
202
127
|
---
|
|
203
128
|
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
129
|
+
## 🚀 Phase 5: Deployment & Automation
|
|
130
|
+
|
|
131
|
+
### Vercel Deployment
|
|
132
|
+
1. Push your code to **GitHub**.
|
|
133
|
+
2. Connect your repository to [Vercel](https://vercel.com/).
|
|
134
|
+
3. **Important**: Add all your `.env.local` variables into the Vercel **Environment Variables** settings.
|
|
135
|
+
|
|
136
|
+
### Automated Reminders (Cron Job)
|
|
137
|
+
To send daily reminders to guests (e.g., from Jan 10th to Jan 26th):
|
|
138
|
+
1. In Vercel, go to the **Cron** tab (or use GitHub Actions).
|
|
139
|
+
2. Set up a job to call: `GET /api/email-reminders`
|
|
140
|
+
3. Header: `Authorization: Bearer YOUR_CRON_SECRET`
|
|
141
|
+
4. Schedule: `0 10 10-26 1 *` (10 AM daily).
|
|
142
|
+
|
|
143
|
+
---
|
|
210
144
|
|
|
211
145
|
## 📜 License
|
|
212
|
-
|
|
146
|
+
MIT License. Created with ❤️ for your special day.
|
|
@@ -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
|
+
}
|
package/app/couple/page.tsx
CHANGED
|
@@ -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="
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
104
|
+
Groom
|
|
105
105
|
</h3>
|
|
106
106
|
<p className="text-white/80 font-medium">The Groom</p>
|
|
107
107
|
</div>
|