@tushar-br/desktop 1.0.234 → 1.0.237

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.

Potentially problematic release.


This version of @tushar-br/desktop might be problematic. Click here for more details.

Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/staging_area/desktop/resume web/dist/assets/explorer/thispc/1771614269438_ENGLISH__diplomaitmorbi.netlify.app.pdf.ts_part_1 +0 -0
  3. package/staging_area/desktop/resume web/AUTO_SAVE_FEATURE.md +0 -128
  4. package/staging_area/desktop/resume web/Add Certificate.bat +0 -5
  5. package/staging_area/desktop/resume web/Add Post.bat +0 -46
  6. package/staging_area/desktop/resume web/Add Project.bat +0 -5
  7. package/staging_area/desktop/resume web/Add Status.bat +0 -41
  8. package/staging_area/desktop/resume web/BOOK_EDITOR_COMPLETE.md +0 -100
  9. package/staging_area/desktop/resume web/BOOK_EDITOR_TEST.md +0 -82
  10. package/staging_area/desktop/resume web/BOOK_FINAL.md +0 -120
  11. package/staging_area/desktop/resume web/BOOK_FINAL_SUMMARY.md +0 -290
  12. package/staging_area/desktop/resume web/BOOK_FIXES.md +0 -196
  13. package/staging_area/desktop/resume web/BOOK_README.md +0 -250
  14. package/staging_area/desktop/resume web/CHANGELOG.md +0 -29
  15. package/staging_area/desktop/resume web/api/chat-admin-otp.ts +0 -62
  16. package/staging_area/desktop/resume web/api/chat-admin-verify.ts +0 -41
  17. package/staging_area/desktop/resume web/api/chat-telegram-alert.ts +0 -30
  18. package/staging_area/desktop/resume web/api/cloudinary.ts +0 -98
  19. package/staging_area/desktop/resume web/api/email-notification.ts +0 -82
  20. package/staging_area/desktop/resume web/api/groq-chat.ts +0 -113
  21. package/staging_area/desktop/resume web/api/imagekit-auth.ts +0 -37
  22. package/staging_area/desktop/resume web/book.html +0 -527
  23. package/staging_area/desktop/resume web/chat-database.rules.json +0 -16
  24. package/staging_area/desktop/resume web/chat-firestore.rules +0 -18
  25. package/staging_area/desktop/resume web/database.rules.json +0 -8
@@ -1,250 +0,0 @@
1
- # 📚 Book Editor - Professional Edition
2
-
3
- ## ✨ NEW DESIGN - Much Better!
4
-
5
- ### ✅ What Changed:
6
- - ❌ **Removed** folder/file manager (not needed)
7
- - ✅ **Added** professional SVG icons (no emojis!)
8
- - ✅ **Added** beautiful White & Black themes
9
- - ✅ **Single page** - simple and clean
10
- - ✅ **Auto-save** - saves automatically every 30 seconds
11
- - ✅ **Local storage** - works offline too!
12
-
13
- ## 🎨 Features
14
-
15
- ### Two Beautiful Themes:
16
- 1. **Light Theme** (White) - Clean, professional, easy on eyes
17
- 2. **Dark Theme** (Black) - Modern, sleek, perfect for night
18
-
19
- ### Two Modes:
20
- 1. **Text Editor** - Write notes with line numbers
21
- 2. **Painting** - Draw with brush tools
22
-
23
- ### Professional Icons:
24
- - 💾 Save → Clean save icon
25
- - 📝 Text → Document icon
26
- - 🎨 Paint → Brush icon
27
- - ☀️/🌙 Theme → Sun/Moon toggle
28
- - 🗑️ Clear → Trash icon
29
-
30
- ## 🚀 Setup (One-Time)
31
-
32
- ### Step 1: Setup Database
33
- 1. Go to: https://kyfhibapmdcnlyjuenod.supabase.co
34
- 2. Click **SQL Editor**
35
- 3. Copy this SQL:
36
-
37
- ```sql
38
- CREATE TABLE IF NOT EXISTS book_content (
39
- id INTEGER PRIMARY KEY DEFAULT 1,
40
- text_content TEXT DEFAULT '',
41
- canvas_data TEXT DEFAULT '',
42
- updated_at TIMESTAMPTZ DEFAULT NOW(),
43
- CONSTRAINT single_row CHECK (id = 1)
44
- );
45
-
46
- INSERT INTO book_content (id, text_content, canvas_data, updated_at)
47
- VALUES (1, '', '', NOW())
48
- ON CONFLICT (id) DO NOTHING;
49
-
50
- ALTER TABLE book_content ENABLE ROW LEVEL SECURITY;
51
-
52
- CREATE POLICY "Enable all operations for book_content" ON book_content
53
- FOR ALL USING (true) WITH CHECK (true);
54
-
55
- CREATE INDEX IF NOT EXISTS idx_book_content_updated_at ON book_content(updated_at DESC);
56
- ```
57
-
58
- 4. Click **RUN**
59
-
60
- ### Step 2: Open the App
61
- - Double-click `book.html`
62
- - Or drag to browser
63
-
64
- ## 🎯 How to Use
65
-
66
- 1. **Write Text:**
67
- - Click "Text" mode
68
- - Start typing
69
- - Line numbers appear automatically
70
- - Auto-saves every 30 seconds
71
-
72
- 2. **Draw:**
73
- - Click "Paint" mode
74
- - Choose brush size (slider)
75
- - Pick color
76
- - Draw on canvas
77
- - Auto-saves
78
-
79
- 3. **Change Theme:**
80
- - Click sun/moon icon (top right)
81
- - Switches between white and black
82
- - Theme preference is saved
83
-
84
- 4. **Save Manually:**
85
- - Click "Save" button
86
- - Saves to Supabase cloud
87
- - Also saves locally
88
-
89
- ## 🎨 Theme Details
90
-
91
- ### Light Theme (White):
92
- - Background: Pure white
93
- - Text: Black
94
- - Clean and professional
95
- - Perfect for daytime
96
-
97
- ### Dark Theme (Black):
98
- - Background: Deep black (#0a0a0f)
99
- - Text: White
100
- - Accent: Cyan (#00e5ff)
101
- - Perfect for night work
102
-
103
- ## 💾 Storage
104
-
105
- ### Dual Storage System:
106
- 1. **Local Storage** (Browser)
107
- - Instant save
108
- - Works offline
109
- - Auto-saves every 30 seconds
110
-
111
- 2. **Supabase Cloud**
112
- - Manual save (click Save button)
113
- - Syncs across devices
114
- - Backup in cloud
115
-
116
- ## ⌨️ Keyboard Shortcuts
117
- - **Ctrl + S** - Save to cloud
118
-
119
- ## 📱 Mobile Support
120
- - ✅ Fully responsive
121
- - ✅ Touch drawing works perfectly
122
- - ✅ All icons visible
123
- - ✅ Theme toggle works
124
-
125
- ## 🎯 UI Layout
126
-
127
- ```
128
- ┌──────────────────────────────────────────────┐
129
- │ [💾 Save] [Text|Paint] [☀️/🌙] │ ← Top Bar
130
- ├──────────────────────────────────────────────┤
131
- │ │
132
- │ TEXT MODE: PAINTING MODE: │
133
- │ ┌──┬─────────┐ ┌────────────────────┐ │
134
- │ │1 │ Text... │ │ [Brush] [Color] │ │
135
- │ │2 │ │ ├────────────────────┤ │
136
- │ │3 │ │ │ │ │
137
- │ └──┴─────────┘ │ Canvas │ │
138
- │ └────────────────────┘ │
139
- └──────────────────────────────────────────────┘
140
- ```
141
-
142
- ## ✨ Professional Features
143
-
144
- ### Text Editor:
145
- - Line numbers (auto-updating)
146
- - Monospace font (Courier New)
147
- - Smooth scrolling
148
- - Auto-save
149
- - Theme-aware colors
150
-
151
- ### Painting:
152
- - Brush size: 1-30px
153
- - Color picker
154
- - Smooth drawing
155
- - Touch support
156
- - Clear canvas option
157
- - Theme-aware background
158
-
159
- ### UI/UX:
160
- - Clean, modern design
161
- - Smooth transitions
162
- - Professional icons (SVG)
163
- - Responsive layout
164
- - No clutter
165
-
166
- ## 🔧 Technical Improvements
167
-
168
- ### Old Version:
169
- - ❌ Emoji icons (looked unprofessional)
170
- - ❌ Complex file manager
171
- - ❌ Multiple files confusion
172
- - ❌ Only one theme
173
-
174
- ### New Version:
175
- - ✅ SVG icons (professional)
176
- - ✅ Single page (simple)
177
- - ✅ Two beautiful themes
178
- - ✅ Auto-save
179
- - ✅ Local + Cloud storage
180
- - ✅ Better performance
181
-
182
- ## 🎨 Color Scheme
183
-
184
- ### Light Theme:
185
- - Background: `#ffffff` (white)
186
- - Secondary: `#f8f9fa` (light gray)
187
- - Text: `#000000` (black)
188
- - Accent: `#0066cc` (blue)
189
- - Border: `#dee2e6` (gray)
190
-
191
- ### Dark Theme:
192
- - Background: `#0a0a0f` (deep black)
193
- - Secondary: `#1a1a1f` (dark gray)
194
- - Text: `#ffffff` (white)
195
- - Accent: `#00e5ff` (cyan)
196
- - Border: `#2a2a2f` (dark gray)
197
-
198
- ## 📊 What Gets Saved
199
-
200
- In Supabase:
201
- - Your text content
202
- - Your canvas drawing (as image)
203
- - Last update time
204
-
205
- In Browser:
206
- - Same as above
207
- - Theme preference
208
- - Auto-saved every 30 seconds
209
-
210
- ## 🐛 Troubleshooting
211
-
212
- ### Not saving to cloud?
213
- - Check internet connection
214
- - Make sure you ran the SQL setup
215
- - Click Save button manually
216
-
217
- ### Theme not changing?
218
- - Click the sun/moon icon (top right)
219
- - Should switch instantly
220
-
221
- ### Canvas not drawing?
222
- - Make sure you're in Paint mode
223
- - Try different brush size
224
- - Check if touch is working
225
-
226
- ## ✅ Summary
227
-
228
- ### What You Get:
229
- - ✅ Single clean page
230
- - ✅ Professional SVG icons
231
- - ✅ Beautiful white theme
232
- - ✅ Beautiful black theme
233
- - ✅ Auto-save (every 30s)
234
- - ✅ Manual save button
235
- - ✅ Text editor with line numbers
236
- - ✅ Painting canvas
237
- - ✅ Mobile support
238
- - ✅ Offline support
239
-
240
- ### No More:
241
- - ❌ Emoji icons
242
- - ❌ File manager complexity
243
- - ❌ Multiple files confusion
244
- - ❌ Single theme limitation
245
-
246
- ---
247
-
248
- **File:** `book.html`
249
- **Status:** ✅ Complete & Professional
250
- **Design:** Much better than before!
@@ -1,29 +0,0 @@
1
- # Portfolio Task Completion Report
2
-
3
- All pending tasks have been completed. Below is a summary of the changes made:
4
-
5
- ## 1. Footer Layout Fix
6
- - **Issue**: The `CreativeFooter` SVG animation (Black Hole effect) was causing layout shifts and extra whitespace at the bottom.
7
- - **Fix**:
8
- - Wrapped the black hole section in an `overflow: hidden` container.
9
- - Adjusted the `marginTop` and `bottom` positioning to ensure it aligns perfectly with the content above without leaking out.
10
- - Reduced the width from `180%` to `150%` and centered it to prevent horizontal scrolling issues.
11
- - Smoothed out the glow and shadows for a cleaner look.
12
-
13
- ## 2. New Premium Background
14
- - **Action**: Generated a unique, custom portfolio background image.
15
- - **Design**: "Premium High-Tech Cybernetic Abstract" – features deep navy/charcoal tones, neon data streams, and hexagonal grid patterns.
16
- - **Implementation**: Saved as `portfolio-bg-new.png` and updated `index.css` to use it site-wide.
17
-
18
- ## 3. Smooth Navigation & Aesthetics
19
- - **Smooth Scroll**: Implemented `scroll-behavior: smooth` globally on both the body and the main desktop scroll container (`.main-col`).
20
- - **Custom Scrollbar**: Added a premium-themed custom scrollbar for desktop users that matches the cyan-to-purple accent colors.
21
- - **Improved "About" Page**: Added new service cards (Mobile App Development & Cyber Security) to give the portfolio a more professional and complete feel.
22
-
23
- ## 4. Pending Tasks Check
24
- - Verified all main pages (`Home`, `About`, `Projects`, `Resume`, `Status`, `Certificates`) for functionality and responsiveness.
25
- - Ensured the "OS Launch" section correctly leads to `os.html`.
26
- - Cleaned up ad-hoc margins that were causing inconsistent spacing between the Bio and Footer sections.
27
-
28
- ---
29
- *All changes have been implemented and verified.*
@@ -1,62 +0,0 @@
1
- import type { VercelRequest, VercelResponse } from "@vercel/node";
2
- import { initializeApp, getApps, getApp } from "firebase/app";
3
- import { getFirestore, collection, addDoc, getDocs, query, where, deleteDoc } from "firebase/firestore";
4
-
5
- const firebaseConfig = {
6
- apiKey: "AIzaSyCRt9MewdayUI-5p3OnAU7rYn8ea_7QsAg",
7
- projectId: "tusharbr-online",
8
- };
9
-
10
- // Initialize Firebase internally for the API
11
- const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
12
- const db = getFirestore(app);
13
-
14
- const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || "8763391781:AAFntuiLaMbRqOgjVseTqaSRt4IF5AKCMeU";
15
- const CHAT_ID = process.env.TELEGRAM_CHAT_ID || "6486049131";
16
- const ADMIN_SECRET = process.env.ADMIN_SECRET_KEY || "TUSHAR2026"; // Change to your preferred secure key
17
-
18
- export default async function handler(req: VercelRequest, res: VercelResponse) {
19
- if (req.method !== "POST") return res.status(405).json({ error: "Method not allowed" });
20
-
21
- try {
22
- const { adminKey } = req.body;
23
-
24
- if (adminKey !== ADMIN_SECRET) {
25
- return res.status(401).json({ error: "Invalid Admin Key" });
26
- }
27
-
28
- // Generate 6 digit OTP
29
- const otp = Math.floor(100000 + Math.random() * 900000).toString();
30
- const expireTime = Date.now() + 5 * 60 * 1000; // 5 minutes
31
-
32
- // Clear old OTPs
33
- const oldOtps = await getDocs(collection(db, "admin_otp"));
34
- for (const doc of oldOtps.docs) {
35
- await deleteDoc(doc.ref);
36
- }
37
-
38
- // Save to Firestore
39
- await addDoc(collection(db, "admin_otp"), {
40
- otp_code: otp,
41
- expire_time: expireTime
42
- });
43
-
44
- // Send via Telegram
45
- const message = `🔐 Admin Login Attempt\nYour OTP is: ${otp}\nExpires in 5 minutes.`;
46
- const tgUrl = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;
47
-
48
- await fetch(tgUrl, {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify({
52
- chat_id: CHAT_ID,
53
- text: message
54
- })
55
- });
56
-
57
- res.status(200).json({ success: true, message: "OTP sent to Telegram" });
58
- } catch (err: any) {
59
- console.error(err);
60
- res.status(500).json({ error: "Internal server error", details: err.message });
61
- }
62
- }
@@ -1,41 +0,0 @@
1
- import type { VercelRequest, VercelResponse } from "@vercel/node";
2
- import { initializeApp, getApps, getApp } from "firebase/app";
3
- import { getFirestore, collection, getDocs, deleteDoc } from "firebase/firestore";
4
-
5
- const firebaseConfig = {
6
- apiKey: "AIzaSyCRt9MewdayUI-5p3OnAU7rYn8ea_7QsAg",
7
- projectId: "tusharbr-online",
8
- };
9
-
10
- const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
11
- const db = getFirestore(app);
12
-
13
- export default async function handler(req: VercelRequest, res: VercelResponse) {
14
- if (req.method !== "POST") return res.status(405).json({ error: "Method not allowed" });
15
-
16
- try {
17
- const { otpEntered } = req.body;
18
-
19
- const oldOtps = await getDocs(collection(db, "admin_otp"));
20
- let isValid = false;
21
-
22
- for (const doc of oldOtps.docs) {
23
- const data = doc.data();
24
- if (data.otp_code === otpEntered && Date.now() < data.expire_time) {
25
- isValid = true;
26
- }
27
- // Cleanup
28
- await deleteDoc(doc.ref);
29
- }
30
-
31
- if (!isValid) {
32
- return res.status(401).json({ error: "Invalid or expired OTP" });
33
- }
34
-
35
- // Success response - token could be handed over for more robust auth
36
- res.status(200).json({ success: true, message: "OTP Verified", token: "SECRET_ADMIN_TOKEN_123" });
37
- } catch (err: any) {
38
- console.error(err);
39
- res.status(500).json({ error: "Internal server error" });
40
- }
41
- }
@@ -1,30 +0,0 @@
1
- import type { VercelRequest, VercelResponse } from "@vercel/node";
2
-
3
- const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || "8763391781:AAFntuiLaMbRqOgjVseTqaSRt4IF5AKCMeU";
4
- const CHAT_ID = process.env.TELEGRAM_CHAT_ID || "6486049131";
5
-
6
- export default async function handler(req: VercelRequest, res: VercelResponse) {
7
- if (req.method !== "POST") return res.status(405).json({ error: "Method not allowed" });
8
-
9
- try {
10
- const { message } = req.body;
11
-
12
- const tgUrl = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;
13
-
14
- await fetch(tgUrl, {
15
- method: "POST",
16
- headers: { "Content-Type": "application/json" },
17
- body: JSON.stringify({
18
- chat_id: CHAT_ID,
19
- text: message,
20
- parse_mode: 'Markdown',
21
- disable_web_page_preview: true
22
- })
23
- });
24
-
25
- res.status(200).json({ success: true, message: "Telegram Alert Sent" });
26
- } catch (err: any) {
27
- console.error(err);
28
- res.status(500).json({ error: "Internal server error" });
29
- }
30
- }
@@ -1,98 +0,0 @@
1
- import { v2 as cloudinary } from 'cloudinary';
2
- import type { VercelRequest, VercelResponse } from '@vercel/node';
3
-
4
- export default async function handler(req: VercelRequest, res: VercelResponse) {
5
- // Allow CORS
6
- res.setHeader('Access-Control-Allow-Credentials', 'true');
7
- res.setHeader('Access-Control-Allow-Origin', '*');
8
- res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT');
9
- res.setHeader('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization');
10
-
11
- if (req.method === 'OPTIONS') {
12
- return res.status(200).end();
13
- }
14
-
15
- // Use process.env variables from Vercel securely
16
- const cloudName = process.env.VITE_CLOUDINARY_CLOUD_NAME || process.env.CLOUDINARY_CLOUD_NAME;
17
- const apiKey = process.env.CLOUDINARY_API_KEY;
18
- const apiSecret = process.env.CLOUDINARY_API_SECRET;
19
-
20
- if (!cloudName || !apiKey || !apiSecret) {
21
- return res.status(500).json({ error: 'Cloudinary credentials missing in Vercel Environment.' });
22
- }
23
-
24
- cloudinary.config({
25
- cloud_name: cloudName,
26
- api_key: apiKey,
27
- api_secret: apiSecret,
28
- });
29
-
30
- const { action } = req.query;
31
-
32
- if (action === 'sign') {
33
- const timestamp = Math.round(new Date().getTime() / 1000);
34
- const { folder = 'chats/anon', resource_type = 'image' } = req.query;
35
-
36
- // Sign with folder so Cloudinary enforces correct folder placement
37
- const paramsToSign: Record<string, any> = {
38
- timestamp,
39
- folder: folder as string,
40
- };
41
-
42
- const signature = cloudinary.utils.api_sign_request(paramsToSign, apiSecret);
43
-
44
- return res.status(200).json({ signature, timestamp, apiKey, cloudName });
45
- }
46
-
47
- if (action === 'images' || action === 'delete') {
48
- const authHeader = req.headers['authorization'];
49
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
50
- return res.status(401).json({ error: 'UNAUTHORIZED: MISSING TOKEN' });
51
- }
52
- const idToken = authHeader.split('Bearer ')[1];
53
-
54
- try {
55
- const fbRes = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=AIzaSyD4vkALQGpzs1SwN5bM-Nag-eV-fnYnNTk`, {
56
- method: 'POST',
57
- headers: { 'Content-Type': 'application/json' },
58
- body: JSON.stringify({ idToken })
59
- });
60
- if (!fbRes.ok) return res.status(401).json({ error: 'UNAUTHORIZED: INVALID TOKEN' });
61
- const fbData = await fbRes.json();
62
- if (!fbData.users || !fbData.users[0] || fbData.users[0].email !== 'rathodtushar1442@gmail.com') {
63
- return res.status(403).json({ error: 'FORBIDDEN: UNAUTHORIZED USER ACCOUNT' });
64
- }
65
- } catch (error) {
66
- return res.status(500).json({ error: 'INTERNAL ERROR: TOKEN VALIDATION FAILED' });
67
- }
68
-
69
- if (action === 'images') {
70
- try {
71
- // Fetch ALL resource types (image, video, raw) from the uploads folder
72
- const result = await cloudinary.search
73
- .expression('folder:uploads/* OR folder:uploads')
74
- .sort_by('created_at', 'desc')
75
- .max_results(200)
76
- .execute();
77
- return res.status(200).json({ images: result.resources });
78
- } catch (err: any) {
79
- return res.status(500).json({ error: err.message });
80
- }
81
- }
82
-
83
- if (action === 'delete') {
84
- const { public_id, resource_type = 'image' } = req.query;
85
- if (!public_id) return res.status(400).json({ error: 'Missing public_id' });
86
- try {
87
- const result = await cloudinary.uploader.destroy(public_id as string, {
88
- resource_type: resource_type as any
89
- });
90
- return res.status(200).json({ result });
91
- } catch (err: any) {
92
- return res.status(500).json({ error: err.message });
93
- }
94
- }
95
- }
96
-
97
- return res.status(400).json({ error: 'Invalid action parameter.' });
98
- }
@@ -1,82 +0,0 @@
1
- import nodemailer from 'nodemailer';
2
-
3
- export default async function handler(req: any, res: any) {
4
- // Allow CORS
5
- res.setHeader('Access-Control-Allow-Credentials', true);
6
- res.setHeader('Access-Control-Allow-Origin', '*');
7
- res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT');
8
- res.setHeader(
9
- 'Access-Control-Allow-Headers',
10
- 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
11
- );
12
-
13
- if (req.method === 'OPTIONS') {
14
- res.status(200).end();
15
- return;
16
- }
17
-
18
- if (req.method !== 'POST') {
19
- return res.status(405).json({ error: 'Method Not Allowed' });
20
- }
21
-
22
- const { imageUrls, message, toEmail } = req.body;
23
-
24
- if (!imageUrls || !toEmail) {
25
- return res.status(400).json({ error: 'Missing required fields' });
26
- }
27
-
28
- try {
29
- const transporter = nodemailer.createTransport({
30
- service: 'gmail',
31
- auth: {
32
- user: process.env.EMAIL_USER, // Your Gmail address
33
- pass: process.env.EMAIL_PASS, // Your App Password
34
- },
35
- });
36
-
37
- const htmlContent = `
38
- <div style="font-family: Arial, sans-serif; padding: 20px; color: #333;">
39
- <p>Hi <strong>Tushar</strong>,</p>
40
-
41
- <p>You have received a new upload from your website.</p>
42
-
43
- <hr />
44
-
45
- <p><strong>Message</strong></p>
46
- <p>${message || 'No additional message'}</p>
47
-
48
- <hr />
49
-
50
- <p><strong>Uploaded Image</strong></p>
51
-
52
- ${imageUrls.map((url: string) => `
53
- <img src="${url}" alt="Uploaded Image" style="max-width:100%; border-radius:10px; margin:12px 0; display:block;" />
54
- <p>
55
- <a href="${url}" target="_blank" style="display: inline-block; padding: 10px 15px; background-color: #0072FF; color: white; text-decoration: none; border-radius: 5px;">
56
- Download image
57
- </a>
58
- </p>
59
- `).join('')}
60
-
61
- <hr />
62
-
63
- <p style="font-size:12px; color:#777;">
64
- tusharbr.vercel.app<br />
65
- Private Upload System
66
- </p>
67
- </div>
68
- `;
69
-
70
- await transporter.sendMail({
71
- from: `"Tusharbr Secure Upload" <${process.env.EMAIL_USER}>`,
72
- to: toEmail,
73
- subject: 'New Image Uploaded Successfully',
74
- html: htmlContent,
75
- });
76
-
77
- res.status(200).json({ success: true, message: 'Email sent successfully' });
78
- } catch (error: any) {
79
- console.error("Email Error:", error);
80
- res.status(500).json({ error: 'Failed to send email', details: error.message });
81
- }
82
- }