diff --git a/backend/.env.example b/backend/.env.example index b4ce94d..a43034d 100755 --- a/backend/.env.example +++ b/backend/.env.example @@ -29,3 +29,9 @@ DATABASE_URL=postgresql+asyncpg://modcomms:modcomms_dev@localhost:5432/modcomms # File Storage Path (for uploaded proofs) # Defaults to ../storage relative to backend/ # FILE_STORAGE_PATH=/path/to/storage + +# Mailgun Configuration (for support emails) +MAILGUN_API_URL=https://api.mailgun.net/v3/your-domain/messages +MAILGUN_API_KEY=your_mailgun_api_key_here +MAILGUN_FROM=noreply@your-domain.com +SUPPORT_EMAIL=support@your-domain.com diff --git a/backend/app/api/schemas.py b/backend/app/api/schemas.py index caa36a6..74c1d2e 100755 --- a/backend/app/api/schemas.py +++ b/backend/app/api/schemas.py @@ -173,3 +173,11 @@ class UserResponse(BaseModel): class Config: from_attributes = True + + +# Support email schemas +class SupportEmailRequest(BaseModel): + message: str + subject: str + user_name: Optional[str] = None + user_email: Optional[str] = None diff --git a/backend/app/config.py b/backend/app/config.py index f3788ba..5801621 100755 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -35,6 +35,12 @@ class Settings: _default_storage = Path(__file__).parent.parent.parent / "storage" FILE_STORAGE_PATH: str = os.getenv("FILE_STORAGE_PATH", str(_default_storage)) + # Mailgun Configuration for support emails + MAILGUN_API_URL: str = os.getenv("MAILGUN_API_URL", "") + MAILGUN_API_KEY: str = os.getenv("MAILGUN_API_KEY", "") + MAILGUN_FROM: str = os.getenv("MAILGUN_FROM", "") + SUPPORT_EMAIL: str = os.getenv("SUPPORT_EMAIL", "BAICsupport@oliver.agency") + def validate(self) -> None: """Validate required settings are present.""" if not self.GEMINI_API_KEY: diff --git a/backend/app/services/email_service.py b/backend/app/services/email_service.py new file mode 100644 index 0000000..435d8c4 --- /dev/null +++ b/backend/app/services/email_service.py @@ -0,0 +1,43 @@ +"""Email service for sending support emails via Mailgun.""" +import httpx +from app.config import settings + + +class EmailService: + """Service for sending emails via Mailgun API.""" + + async def send_support_email( + self, + message: str, + subject: str, + user_name: str | None = None, + user_email: str | None = None, + ) -> bool: + """Send a support email via Mailgun API. + + Args: + message: The message body + subject: Email subject line + user_name: Optional name of the user submitting + user_email: Optional email of the user submitting + + Returns: + True if email was sent successfully, False otherwise + """ + body = f"From: {user_name or 'Anonymous'}\nEmail: {user_email or 'Not provided'}\n\n{message}" + + async with httpx.AsyncClient() as client: + response = await client.post( + settings.MAILGUN_API_URL, + auth=("api", settings.MAILGUN_API_KEY), + data={ + "from": settings.MAILGUN_FROM, + "to": settings.SUPPORT_EMAIL, + "subject": subject, + "text": body, + }, + ) + return response.status_code == 200 + + +email_service = EmailService() diff --git a/frontend/components/Login.tsx b/frontend/components/Login.tsx index 262a1ba..31736d8 100755 --- a/frontend/components/Login.tsx +++ b/frontend/components/Login.tsx @@ -6,19 +6,49 @@ import { XIcon } from './icons/XIcon'; import { MicrosoftLogo } from './icons/MicrosoftLogo'; import { loginRequest } from '../services/authConfig'; +const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000'; + const SupportModal: React.FC<{ isOpen: boolean; onClose: () => void; - onSubmit: (query: string) => void; -}> = ({ isOpen, onClose, onSubmit }) => { +}> = ({ isOpen, onClose }) => { + const [query, setQuery] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitStatus, setSubmitStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(null); + if (!isOpen) return null; - const [query, setQuery] = useState(''); - - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (query.trim()) { - onSubmit(query); + if (!query.trim()) return; + + setIsSubmitting(true); + setSubmitStatus(null); + + try { + const response = await fetch(`${API_URL}/api/support/email`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + message: query, + subject: 'Support Request from Mod Comms Login', + }), + }); + + if (!response.ok) { + throw new Error('Failed to send'); + } + + setSubmitStatus({ type: 'success', message: 'Thank you for your query. A member of the support team will be in touch with you shortly.' }); + setQuery(''); + setTimeout(() => { + onClose(); + setSubmitStatus(null); + }, 3000); + } catch { + setSubmitStatus({ type: 'error', message: 'Failed to send your message. Please try again later.' }); + } finally { + setIsSubmitting(false); } }; @@ -40,6 +70,11 @@ const SupportModal: React.FC<{
@@ -76,12 +123,6 @@ export const Login: React.FC = () => { const [isLoggingIn, setIsLoggingIn] = useState(false); const [loginError, setLoginError] = useState