Jelajahi Sumber

feat: implement live contact form with email notifications

unknown 2 hari lalu
induk
melakukan
540cb88123

+ 2 - 1
backend/main.py

@@ -12,7 +12,7 @@ from services.chat_manager import manager
 
 import locales
 import config
-from routers import auth, orders, catalog, portfolio, files, chat, blog, admin
+from routers import auth, orders, catalog, portfolio, files, chat, blog, admin, contact
 
 app = FastAPI(title="Radionica 3D API")
 
@@ -73,6 +73,7 @@ app.include_router(files.router)
 app.include_router(chat.router)
 app.include_router(blog.router)
 app.include_router(admin.router)
+app.include_router(contact.router)
 
 # WebSocket Global Handler (Centralized to handle various proxy prefixes)
 @app.websocket("/global")

+ 52 - 0
backend/routers/contact.py

@@ -0,0 +1,52 @@
+from fastapi import APIRouter, HTTPException, Depends, Form, UploadFile, File
+from typing import Optional
+import config
+from services.email_service import send_contact_form_email
+import os
+import uuid
+
+router = APIRouter(prefix="/contact", tags=["contact"])
+
+@router.post("")
+async def submit_contact_form(
+    name: str = Form(...),
+    email: str = Form(...),
+    subject: str = Form(...),
+    message: str = Form(...),
+    file: Optional[UploadFile] = File(None)
+):
+    """
+    Handle contact form submission with optional file attachment.
+    Sends an email notification to the administrator.
+    """
+    try:
+        # For now, we don't save to DB, just send email.
+        # If we had a contact_messages table, we would INSERT here.
+        
+        # We could handle the file by saving it or just noting its presence.
+        # Since send_email currently only supports HTML body without attachments, 
+        # we'll mention the file in the email. 
+        # In a real-world scenario, we'd attach the file to the email.
+        
+        file_info = ""
+        if file:
+            file_info = f"\n<p><strong>Attachment:</strong> {file.filename} ({file.size} bytes)</p>"
+            # Optional: Save file to a temporary location if needed for manual review
+            # but for simplified contact form, mentioning it is common.
+            
+        success = send_contact_form_email(
+            admin_email=config.SMTP_FROM,
+            name=name,
+            from_email=email,
+            subject=subject,
+            message=message + file_info
+        )
+        
+        if not success:
+            raise HTTPException(status_code=500, detail="Failed to send message. Please try again later.")
+            
+        return {"message": "Your message has been sent successfully."}
+        
+    except Exception as e:
+        print(f"Contact form error: {e}")
+        raise HTTPException(status_code=500, detail="Internal server error")

+ 6 - 0
backend/schemas.py

@@ -224,3 +224,9 @@ class OrderResponse(OrderCreate):
 
 class MessageCreate(BaseModel):
     message: str
+
+class ContactRequest(BaseModel):
+    name: str = Field(..., min_length=1)
+    email: EmailStr
+    subject: str = Field(..., min_length=1)
+    message: str = Field(..., min_length=1)

+ 17 - 0
backend/services/email_service.py

@@ -112,3 +112,20 @@ def send_password_reset_email(to_email: str, token: str, lang: str = "en"):
     
     tpl = templates.get(lang, templates["en"])
     return send_email(to_email, tpl["subject"], tpl["body"])
+
+def send_contact_form_email(admin_email: str, name: str, from_email: str, subject: str, message: str):
+    """Notify admin about new contact form submission"""
+    subject_line = f"Contact Form: {subject} (from {name})"
+    body = f"""
+        <h2 style="color: #000;">New Contact Form Submission</h2>
+        <hr>
+        <p><strong>From:</strong> {name} ({from_email})</p>
+        <p><strong>Subject:</strong> {subject}</p>
+        <p><strong>Message:</strong></p>
+        <div style="padding: 15px; background-color: #f9f9f9; border-left: 4px solid #000; white-space: pre-wrap;">
+            {message}
+        </div>
+        <hr>
+        <p style="font-size: 12px; color: #666;">This message was sent from the Radionica 3D Contact page.</p>
+    """
+    return send_email(admin_email, subject_line, body)

+ 25 - 0
src/lib/api.ts

@@ -98,6 +98,31 @@ export const getMyOrders = async (page = 1, size = 10) => {
 export const registerUser = async (userData: any) => {
   const response = await fetch(`${API_BASE_URL}/auth/register?lang=${i18n.global.locale.value}`, {
     method: 'POST',
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(userData)
+  });
+  
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to register'));
+  }
+  
+  return response.json();
+};
+
+export const submitContactForm = async (formData: FormData) => {
+  const response = await fetch(`${API_BASE_URL}/contact?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    body: formData,
+  });
+  
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to send message'));
+  }
+  
+  return response.json();
+};
     headers: { 
       'Content-Type': 'application/json'
     },

+ 21 - 6
src/pages/Contact.vue

@@ -236,6 +236,7 @@
 <script setup lang="ts">
 import { ref } from "vue";
 import { useI18n } from "vue-i18n";
+import { submitContactForm } from "../lib/api";
 
 const fileInputRef = ref<HTMLInputElement | null>(null);
 const { t } = useI18n();
@@ -260,11 +261,25 @@ const handleFileUpload = (event: Event) => {
 const handleSubmit = async () => {
   isSubmitting.value = true;
   
-  // Simulate API call
-  await new Promise(resolve => setTimeout(resolve, 1500));
-  
-  alert(t("contact.form.success"));
-  form.value = { name: "", email: "", subject: "", message: "", file: null };
-  isSubmitting.value = false;
+  try {
+    const formData = new FormData();
+    formData.append("name", form.value.name);
+    formData.append("email", form.value.email);
+    formData.append("subject", form.value.subject);
+    formData.append("message", form.value.message);
+    if (form.value.file) {
+      formData.append("file", form.value.file);
+    }
+
+    await submitContactForm(formData);
+    
+    alert(t("contact.form.success"));
+    form.value = { name: "", email: "", subject: "", message: "", file: null };
+  } catch (error: any) {
+    console.error("Contact form error:", error);
+    alert(error.message || "Failed to send message. Please try again later.");
+  } finally {
+    isSubmitting.value = false;
+  }
 };
 </script>