diff --git a/backend/app/main.py b/backend/app/main.py index e482a9b..56a600a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -92,6 +92,7 @@ app.include_router(orders.router) app.include_router(wishlist.router) app.include_router(contact.router) app.include_router(contact.admin_router) # Admin contact messages endpoints +app.include_router(contact.user_router) # User messages endpoints # Mount static files for uploads app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads") diff --git a/backend/app/routers/contact.py b/backend/app/routers/contact.py index ef38a90..22fb12e 100644 --- a/backend/app/routers/contact.py +++ b/backend/app/routers/contact.py @@ -4,7 +4,9 @@ from typing import List, Optional from app.database.database import get_db from app.models import ContactMessage, User from app.schemas.contact import ContactMessageCreate, ContactMessageResponse, ContactMessageUpdate -from app.services.auth import get_current_admin_user +from app.services.auth import get_current_admin_user, get_current_user +from app.services.email import send_contact_notification_to_admin, send_admin_response_to_customer +from app.config import settings router = APIRouter(prefix="/api/contact", tags=["contact"]) @@ -17,6 +19,20 @@ def send_contact_message(message: ContactMessageCreate, db: Session = Depends(ge db.add(db_message) db.commit() db.refresh(db_message) + + # Send email notification to admin + try: + send_contact_notification_to_admin( + admin_email=settings.admin_email, + customer_name=db_message.full_name, + customer_email=db_message.email, + subject=db_message.subject, + message=db_message.message, + phone=db_message.phone + ) + except Exception as e: + print(f"Failed to send admin notification email: {e}") + return db_message @@ -81,11 +97,32 @@ def update_message( raise HTTPException(status_code=404, detail="Message not found") update_data = message_update.model_dump(exclude_unset=True) if hasattr(message_update, 'model_dump') else message_update.dict(exclude_unset=True) + + # Check if admin_notes is being added/updated + send_email_to_customer = False + if 'admin_notes' in update_data and update_data['admin_notes']: + # Only send if there's actually content and it's different from before + if not message.admin_notes or update_data['admin_notes'] != message.admin_notes: + send_email_to_customer = True + for field, value in update_data.items(): setattr(message, field, value) db.commit() db.refresh(message) + + # Send email to customer if admin responded + if send_email_to_customer: + try: + send_admin_response_to_customer( + customer_email=message.email, + customer_name=message.full_name, + original_subject=message.subject, + admin_notes=message.admin_notes + ) + except Exception as e: + print(f"Failed to send customer response email: {e}") + return message @@ -103,3 +140,38 @@ def delete_message( db.delete(message) db.commit() return {"message": "Contact message deleted successfully"} + + +# User/Customer endpoints +user_router = APIRouter(prefix="/api/my-messages", tags=["user-messages"]) + + +@user_router.get("", response_model=List[ContactMessageResponse]) +def get_my_messages( + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Get all contact messages sent by the current user""" + messages = db.query(ContactMessage).filter( + ContactMessage.email == current_user.email + ).order_by(ContactMessage.created_at.desc()).all() + return messages + + +@user_router.get("/{message_id}", response_model=ContactMessageResponse) +def get_my_message( + message_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Get a specific contact message by ID (only if sent by current user)""" + message = db.query(ContactMessage).filter( + ContactMessage.id == message_id, + ContactMessage.email == current_user.email + ).first() + + if not message: + raise HTTPException(status_code=404, detail="Message not found") + + return message + diff --git a/backend/app/services/email.py b/backend/app/services/email.py index 136485a..3ddfe3c 100644 --- a/backend/app/services/email.py +++ b/backend/app/services/email.py @@ -231,3 +231,178 @@ Brand Master Team """ return send_email(email, subject, body, html_body) + + +def send_contact_notification_to_admin(admin_email: str, customer_name: str, customer_email: str, subject: str, message: str, phone: str = None) -> bool: + """ + Send notification to admin when a customer submits a contact message. + + Args: + admin_email: Admin's email address + customer_name: Customer's full name + customer_email: Customer's email + subject: Message subject + message: Message content + phone: Customer's phone number (optional) + + Returns: + bool: True if email sent successfully + """ + email_subject = f"New Contact Message from {customer_name}" + + # Plain text version + body = f""" +Hello Admin, + +You have received a new contact message from a customer. + +Customer Details: +- Name: {customer_name} +- Email: {customer_email} +- Phone: {phone or 'Not provided'} + +Subject: {subject} + +Message: +{message} + +Please log in to the admin dashboard to view and respond to this message. +https://brand-master.dvirlabs.com/admin + +Best regards, +Brand Master System +""" + + # HTML version + html_body = f""" + + + + + + +
+
+

📧 New Contact Message

+
+
+

Hello Admin,

+

You have received a new contact message from a customer.

+ +
+

Customer Details

+

Name: {customer_name}

+

Email: {customer_email}

+

Phone: {phone or 'Not provided'}

+

Subject: {subject}

+
+ +
+

Message:

+

{message.replace(chr(10), '
')}

+
+ +
+ View in Admin Dashboard +
+ + +
+
+ + +""" + + return send_email(admin_email, email_subject, body, html_body) + + +def send_admin_response_to_customer(customer_email: str, customer_name: str, original_subject: str, admin_notes: str) -> bool: + """ + Send admin's response to customer's contact message. + + Args: + customer_email: Customer's email address + customer_name: Customer's full name + original_subject: Original message subject + admin_notes: Admin's response/notes + + Returns: + bool: True if email sent successfully + """ + email_subject = f"Re: {original_subject}" + + # Plain text version + body = f""" +Hello {customer_name}, + +Thank you for contacting Brand Master. We have reviewed your message and here is our response: + +{admin_notes} + +If you have any additional questions, please don't hesitate to contact us again or visit our website. + +Best regards, +Brand Master Team +https://brand-master.dvirlabs.com +""" + + # HTML version + html_body = f""" + + + + + + +
+
+

Response to Your Message

+

Re: {original_subject}

+
+
+

Hello {customer_name},

+

Thank you for contacting Brand Master. We have reviewed your message and here is our response:

+ +
+

Our Response:

+

{admin_notes.replace(chr(10), '
')}

+
+ +

If you have any additional questions, please don't hesitate to contact us again.

+ +
+ Contact Us Again +
+ + +
+
+ + +""" + + return send_email(customer_email, email_subject, body, html_body) + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9430c8a..716c442 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -18,6 +18,7 @@ import Orders from './pages/Orders' import Wishlist from './pages/Wishlist' import About from './pages/About' import Contact from './pages/Contact' +import MyMessages from './pages/MyMessages' import Sales from './pages/Sales' import Admin from './pages/Admin' import Models from './pages/Models' @@ -43,6 +44,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index a225ff5..104d1f4 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -52,6 +52,9 @@ export default function Navbar() { 🛒 {cart.length > 0 && {cart.length}} + + 💬 + 👤 diff --git a/frontend/src/pages/MyMessages.jsx b/frontend/src/pages/MyMessages.jsx new file mode 100644 index 0000000..3b2c7d0 --- /dev/null +++ b/frontend/src/pages/MyMessages.jsx @@ -0,0 +1,268 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import api from '../services/api'; + +function MyMessages() { + const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [selectedMessage, setSelectedMessage] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + fetchMessages(); + }, []); + + const fetchMessages = async () => { + try { + const token = localStorage.getItem('token'); + if (!token) { + navigate('/login'); + return; + } + + const response = await api.get('/my-messages'); + setMessages(response.data); + setLoading(false); + } catch (err) { + console.error('Error fetching messages:', err); + setError('Failed to load messages'); + setLoading(false); + + if (err.response?.status === 401) { + navigate('/login'); + } + } + }; + + const getStatusBadge = (status) => { + const statusColors = { + new: 'bg-blue-100 text-blue-800', + read: 'bg-yellow-100 text-yellow-800', + replied: 'bg-green-100 text-green-800', + }; + return statusColors[status] || 'bg-gray-100 text-gray-800'; + }; + + const formatDate = (dateString) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + if (loading) { + return ( +
+
Loading your messages...
+
+ ); + } + + return ( +
+
+
+

My Messages

+

+ View your contact messages and admin responses +

+
+ + {error && ( +
+ {error} +
+ )} + + {messages.length === 0 ? ( +
+ + + +

+ No messages yet +

+

+ You haven't sent any contact messages yet. +

+ +
+ ) : ( +
+ {messages.map((message) => ( +
+
+ {/* Header */} +
+
+

+ {message.subject} +

+

+ Sent on {formatDate(message.created_at)} +

+
+ + {message.status.toUpperCase()} + +
+ + {/* Original Message */} +
+

+ Your Message: +

+
+

+ {message.message} +

+
+
+ + {/* Admin Response */} + {message.admin_notes && ( +
+

+ + + + Admin Response: +

+
+

+ {message.admin_notes} +

+
+
+ )} + + {!message.admin_notes && message.status === 'new' && ( +
+

+ ⏳ Waiting for admin response... +

+
+ )} + + {/* Message Details */} +
+ + + {selectedMessage?.id === message.id && ( +
+
+
+ + Name: + +

{message.full_name}

+
+
+ + Email: + +

{message.email}

+
+ {message.phone && ( +
+ + Phone: + +

{message.phone}

+
+ )} +
+ + Status: + +

+ {message.is_read ? 'Read' : 'Unread'} +

+
+
+
+ )} +
+
+
+ ))} +
+ )} + + {/* Back Button */} +
+ +
+
+
+ ); +} + +export default MyMessages;