From b487e2cc234cc0d4dbe6d5e6737dd9c37b036f9d Mon Sep 17 00:00:00 2001 From: Gaurav Date: Sun, 16 Nov 2025 22:24:37 -0800 Subject: [PATCH 1/3] PBMS 48 I created a button so when gallery of a client is submitted from the admin, the client should be notified via email and told to log in to their account. This should happen securely and with authentication. PBMS 133 I developed the backend logic to trigger an email notification to the client when their gallery is published from the admin dashboard. Ensure the email contains a secure link or instructions to log in and view the gallery. PBMS 134 I created a clear and professional email template that informs the client their gallery is published and provides instructions for accessing it. Ensure branding and tone are consistent with the business. --- backend/.env | 17 ----- backend/controllers/galleryController.js | 52 +++++++++++++ backend/emails/galleryTemp.js | 15 ++++ backend/server.js | 8 +- backend/services/emailService.js | 31 ++++++++ src/admin/components/Frame/frame.jsx | 10 +-- src/admin/pages/Galleries/galleries.jsx | 93 ++++++++++++++++++++++- src/admin/pages/Home/home.jsx | 6 ++ src/admin/routes.jsx | 3 +- src/pages.zip | Bin 0 -> 29978 bytes 10 files changed, 203 insertions(+), 32 deletions(-) delete mode 100644 backend/.env create mode 100644 backend/controllers/galleryController.js create mode 100644 backend/emails/galleryTemp.js create mode 100644 backend/services/emailService.js create mode 100644 src/pages.zip diff --git a/backend/.env b/backend/.env deleted file mode 100644 index 8916796..0000000 --- a/backend/.env +++ /dev/null @@ -1,17 +0,0 @@ -# Database connection -PG_HOST=localhost -PG_PORT=5432 -PG_USER=laziest -PG_PASSWORD= -PG_DATABASE=pbms - -# JWT Secret Key -JWT_SECRET=supersecretkey - -# Email (for forgot password) -EMAIL_USER=youremail@gmail.com -EMAIL_PASS=your_app_password -FRONTEND_URL=http://localhost:5173 - -# Port -PORT=5001 diff --git a/backend/controllers/galleryController.js b/backend/controllers/galleryController.js new file mode 100644 index 0000000..d55f1ac --- /dev/null +++ b/backend/controllers/galleryController.js @@ -0,0 +1,52 @@ +const pool = require("../db"); +const galleryTemplate = require("../emails/galleryTemp"); +const { sendGalleryPublishedEmail } = require("../services/emailService"); + +exports.publishGallery = async (req, res) => { + try { + const galleryId = req.params.id; + + const galleryQuery = ` + SELECT g.*, u.email AS client_email, u.fullname AS client_name + FROM "Gallery" g + JOIN "User" u ON u.id = g.client_id + WHERE g.id = $1 + `; + const galleryResult = await pool.query(galleryQuery, [galleryId]); + + if (galleryResult.rows.length === 0) { + return res.status(404).json({ message: "Gallery not found" }); + } + + const gallery = galleryResult.rows[0]; + + const updateQuery = ` + UPDATE "Gallery" + SET is_published = TRUE, + published_at = NOW() + WHERE id = $1 + `; + await pool.query(updateQuery, [galleryId]); + + const notificationQuery = ` + INSERT INTO "Notification" (user_id, message, created_at) + VALUES ($1, $2, NOW()) + `; + await pool.query(notificationQuery, [ + gallery.client_id, + `Your gallery "${gallery.gallery_name}" has been published.`, + ]); + + await sendGalleryPublishedEmail( + gallery.client_email, + gallery.client_name, + gallery.gallery_name, + galleryTemplate + ); + + return res.json({ message: "Gallery published and email sent" }); + } catch (err) { + console.error("Publish error:", err); + res.status(500).json({ message: "Internal server error" }); + } +}; \ No newline at end of file diff --git a/backend/emails/galleryTemp.js b/backend/emails/galleryTemp.js new file mode 100644 index 0000000..043ce87 --- /dev/null +++ b/backend/emails/galleryTemp.js @@ -0,0 +1,15 @@ +module.exports = function galleryPublishedTemplate(clientName, galleryName) { + return ` +
+

Your Gallery Is Now Available

+

Hello ${clientName},

+

Your gallery ${galleryName} has been published.

+

Please log in to your dashboard to view and download your photos.

+ + Log In + +

Thank you for choosing our services.

+
+ `; +}; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 5c87978..b4a0315 100644 --- a/backend/server.js +++ b/backend/server.js @@ -20,10 +20,16 @@ pool.connect() .then(() => console.log(" PostgreSQL connected")) .catch(err => console.error("Database connection error:", err)); -// Routes +// Auth Routes const authRoutes = require("./routes/auth"); app.use("/api/auth", authRoutes); +// ⭐ ADD THIS +const adminRoutes = require("./routes/admin"); +app.use("/api/admin", adminRoutes); +// ⭐ Now /api/admin/send-notification works + +// Test route app.get("/test-db", async (req, res) => { try { const result = await pool.query("SELECT * FROM users LIMIT 5;"); diff --git a/backend/services/emailService.js b/backend/services/emailService.js new file mode 100644 index 0000000..5762c74 --- /dev/null +++ b/backend/services/emailService.js @@ -0,0 +1,31 @@ +const nodemailer = require("nodemailer"); + +const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, +}); + +async function sendEmail(to, subject, html) { + return transporter.sendMail({ + from: process.env.EMAIL_USER, + to, + subject, + html, + }); +} + +async function sendGalleryPublishedEmail(to, clientName, galleryName, template) { + return sendEmail( + to, + "Your Gallery Is Published", + template(clientName, galleryName) + ); +} + +module.exports = { + sendEmail, + sendGalleryPublishedEmail, +}; \ No newline at end of file diff --git a/src/admin/components/Frame/frame.jsx b/src/admin/components/Frame/frame.jsx index d8300e2..759f151 100644 --- a/src/admin/components/Frame/frame.jsx +++ b/src/admin/components/Frame/frame.jsx @@ -1,15 +1,9 @@ function Frame() { - // place-holder store admin user name - const username = "Admin" return ( -
-
-

Welcome, {username}

-
-

+
); } -export default Frame \ No newline at end of file +export default Frame diff --git a/src/admin/pages/Galleries/galleries.jsx b/src/admin/pages/Galleries/galleries.jsx index cb57a2e..e51c122 100644 --- a/src/admin/pages/Galleries/galleries.jsx +++ b/src/admin/pages/Galleries/galleries.jsx @@ -1,17 +1,102 @@ import Sidebar from "../../components/Sidebar/sidebar"; import Frame from "../../components/Frame/frame"; +import { supabase } from "../../../lib/supabaseClient"; +import { useState, useEffect } from "react"; function Admin() { + const [users, setUsers] = useState([]); + + useEffect(() => { + async function loadUsers() { + const { data, error } = await supabase + .from("User") + .select("id, email, first_name, last_name, phone"); + + if (!error) setUsers(data); + } + loadUsers(); + }, []); + + async function handleSubmit(e) { + e.preventDefault(); + + const email = document.getElementById("userDropdown").value; + + if (email === "OPTION_SELECT") { + alert("Please select a user."); + return; + } + + try { + const response = await fetch( + "https://zccwrooyhkpkslgqdkvq.supabase.co/functions/v1/hyper-worker", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + import.meta.env.VITE_SUPABASE_ANON_KEY, + }, + body: JSON.stringify({ email }), + } + ); + + const result = await response.json(); + + if (!response.ok) { + console.error(result); + alert("❌ Failed to send email."); + return; + } + + alert("✅ Email sent!"); + } catch (err) { + console.error(err); + alert("❌ Error sending email."); + } + } + return ( -
-
+
+
-
+
+ +
+

Send Notification

+ + {users.length === 0 ? ( +

No users detected.

+ ) : ( +
+ + +
+ + + +
+
+ )} +
); } -export default Admin \ No newline at end of file +export default Admin; \ No newline at end of file diff --git a/src/admin/pages/Home/home.jsx b/src/admin/pages/Home/home.jsx index cb57a2e..be4667e 100644 --- a/src/admin/pages/Home/home.jsx +++ b/src/admin/pages/Home/home.jsx @@ -2,6 +2,9 @@ import Sidebar from "../../components/Sidebar/sidebar"; import Frame from "../../components/Frame/frame"; function Admin() { + // place-holder store admin user name + const username = "Admin" + return (
@@ -9,6 +12,9 @@ function Admin() {
+
+

Welcome, {username}

+
); diff --git a/src/admin/routes.jsx b/src/admin/routes.jsx index 9d713c0..2997dbd 100644 --- a/src/admin/routes.jsx +++ b/src/admin/routes.jsx @@ -1,4 +1,3 @@ -// src/admin/routes.jsx import { Outlet, Route } from 'react-router-dom'; import AdminHome from './pages/Home/home'; import Sessions from './pages/Sessions/sessions'; @@ -23,4 +22,4 @@ export default function AdminRoutes() { } /> ); -} +} \ No newline at end of file diff --git a/src/pages.zip b/src/pages.zip new file mode 100644 index 0000000000000000000000000000000000000000..22d3be973f86c7b432cced10ba523e21b40e1ee6 GIT binary patch literal 29978 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 literal 0 HcmV?d00001 From 12f061157760366e1232f6926dfb5a569a5991d5 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Sun, 16 Nov 2025 23:52:40 -0800 Subject: [PATCH 2/3] PBMS 133 I was able to develop the backend logic to trigger an email notification to the client when their gallery is published from the admin dashboard. Ensure the email contains a secure link or instructions to log in and view the gallery. --- src/admin/pages/Galleries/galleries.jsx | 56 ++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/admin/pages/Galleries/galleries.jsx b/src/admin/pages/Galleries/galleries.jsx index e51c122..2c3142b 100644 --- a/src/admin/pages/Galleries/galleries.jsx +++ b/src/admin/pages/Galleries/galleries.jsx @@ -6,17 +6,20 @@ import { useState, useEffect } from "react"; function Admin() { const [users, setUsers] = useState([]); + // Load users on page load useEffect(() => { async function loadUsers() { const { data, error } = await supabase .from("User") - .select("id, email, first_name, last_name, phone"); + .select("id, email, first_name, last_name"); - if (!error) setUsers(data); + if (error) console.error(error); + else setUsers(data); } loadUsers(); }, []); + // Handle notification sending async function handleSubmit(e) { e.preventDefault(); @@ -27,31 +30,24 @@ function Admin() { return; } - try { - const response = await fetch( - "https://zccwrooyhkpkslgqdkvq.supabase.co/functions/v1/hyper-worker", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + import.meta.env.VITE_SUPABASE_ANON_KEY, - }, - body: JSON.stringify({ email }), - } - ); - - const result = await response.json(); - - if (!response.ok) { - console.error(result); - alert("❌ Failed to send email."); - return; - } - - alert("✅ Email sent!"); - } catch (err) { - console.error(err); - alert("❌ Error sending email."); + const selectedUser = users.find((u) => u.email === email); + + if (!selectedUser) { + alert("User not found."); + return; + } + + // Insert notification into custom table + const { error } = await supabase.from("user_notifications").insert({ + user_id: selectedUser.id, + message: "Admin sent you a notification.", + }); + + if (error) { + console.error(error); + alert("❌ Failed to save notification."); + } else { + alert("✅ Notification saved!"); } } @@ -60,6 +56,7 @@ function Admin() {
+
@@ -73,10 +70,11 @@ function Admin() {
- + + {users.map((u) => (
diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest new file mode 100644 index 0000000..d4bb21d --- /dev/null +++ b/supabase/.temp/cli-latest @@ -0,0 +1 @@ +v2.58.5 \ No newline at end of file diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..41e2a3c --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,11 @@ + +[functions.send-admin-notification] +enabled = true +verify_jwt = true +import_map = "./functions/send-admin-notification/deno.json" +# Uncomment to specify a custom file path to the entrypoint. +# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx +entrypoint = "./functions/send-admin-notification/index.ts" +# Specifies static files to be bundled with the function. Supports glob patterns. +# For example, if you want to serve static HTML pages in your function: +# static_files = [ "./functions/send-admin-notification/*.html" ] diff --git a/supabase/functions/send-admin-notification/.npmrc b/supabase/functions/send-admin-notification/.npmrc new file mode 100644 index 0000000..48c6388 --- /dev/null +++ b/supabase/functions/send-admin-notification/.npmrc @@ -0,0 +1,3 @@ +# Configuration for private npm package dependencies +# For more information on using private registries with Edge Functions, see: +# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries diff --git a/supabase/functions/send-admin-notification/deno.json b/supabase/functions/send-admin-notification/deno.json new file mode 100644 index 0000000..f6ca845 --- /dev/null +++ b/supabase/functions/send-admin-notification/deno.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/supabase/functions/send-admin-notification/index.ts b/supabase/functions/send-admin-notification/index.ts new file mode 100644 index 0000000..5c0d4a9 --- /dev/null +++ b/supabase/functions/send-admin-notification/index.ts @@ -0,0 +1,63 @@ +import "jsr:@supabase/functions-js/edge-runtime.d.ts"; + +Deno.serve(async (req) => { + try { + const { email } = await req.json(); + + if (!email) { + return new Response(JSON.stringify({ error: "Missing email" }), { + status: 400, + }); + } + + const SENDGRID_KEY = Deno.env.get("SENDGRID_KEY"); + + if (!SENDGRID_KEY) { + return new Response(JSON.stringify({ error: "Missing SENDGRID_KEY" }), { + status: 500, + }); + } + + const subject = "Admin Notification"; + const html = ` +

Hello!

+

The admin has sent you a new notification.

+

This is a test email sent from PBMS.

+ `; + + // Send email through SendGrid + const response = await fetch("https://api.sendgrid.com/v3/mail/send", { + method: "POST", + headers: { + Authorization: `Bearer ${SENDGRID_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + personalizations: [{ to: [{ email }] }], + from: { email: "admin@pbms.com" }, // MUST be verified in SendGrid + subject, + content: [{ type: "text/html", value: html }], + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + return new Response( + JSON.stringify({ error: "Failed to send email", details: errorText }), + { status: 500 } + ); + } + + return new Response(JSON.stringify({ message: "Email sent!" }), { + status: 200, + }); + } catch (err) { + return new Response( + JSON.stringify({ + error: "Server error", + details: err.message ?? "Unknown error", + }), + { status: 500 } + ); + } +});