From ad7f0fbe55b1ea66ba9ee36ae967a3888ccf5629 Mon Sep 17 00:00:00 2001 From: Brunobrno Date: Sun, 7 Jun 2026 23:17:10 +0200 Subject: [PATCH] Enhance contact UI and email handling Add an email template and improve contact flow: create backend/templates/email/contact_me.html and update backend/advertisement/tasks.py to use SiteConfiguration.contact_email with a fallback to brunovontor@gmail.com when sending contact form emails. Revamp frontend contact experience: ContactMeForm is now open by default, uses controlled email/message inputs, shows loading/success/error states and posts to /api/advertisement/contact-me/ via publicApi. Update ContactPage and Home to include richer contact sections (framer-motion animations, icons, social links and responsive layouts). Also add a PowerShell helper entry to .claude/settings.local.json. --- .claude/settings.local.json | 3 +- backend/advertisement/tasks.py | 4 +- backend/templates/email/contact_me.html | 45 +++++ .../home/ContactMe/ContactMeForm.tsx | 99 +++++++--- frontend/src/pages/contact/ContactPage.tsx | 169 +++++++++++++++--- frontend/src/pages/home/home.tsx | 124 ++++++++++++- 6 files changed, 385 insertions(+), 59 deletions(-) create mode 100644 backend/templates/email/contact_me.html diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c24979e..b863243 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -15,7 +15,8 @@ "Bash(node -e \"const r = require\\('react-icons/fa'\\); const keys = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('migrat'\\) || k.toLowerCase\\(\\).includes\\('sync'\\) || k.toLowerCase\\(\\).includes\\('exchange'\\) || k.toLowerCase\\(\\).includes\\('arrow'\\)\\).slice\\(0,15\\); console.log\\(keys.join\\('\\\\n'\\)\\);\")", "Bash(node -e \"const r = require\\('react-icons/fa'\\); console.log\\('FaExchangeAlt' in r, 'FaSyncAlt' in r, 'FaCloudUploadAlt' in r, 'FaRandom' in r, 'FaDatabase' in r\\);\")", "Bash(node -e \"const r = require\\('react-icons/gi'\\); console.log\\('GiStabilizer' in r, 'GiDroneBoy' in r, 'GiCctvCamera' in r, 'GiFilmProjector' in r, 'GiGyroscope' in r\\);\")", - "Bash(node -e \"const r = require\\('react-icons/si'\\); const celery = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('celery'\\) || k.toLowerCase\\(\\).includes\\('worker'\\) || k.toLowerCase\\(\\).includes\\('task'\\)\\).slice\\(0,10\\); console.log\\(celery\\);\")" + "Bash(node -e \"const r = require\\('react-icons/si'\\); const celery = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('celery'\\) || k.toLowerCase\\(\\).includes\\('worker'\\) || k.toLowerCase\\(\\).includes\\('task'\\)\\).slice\\(0,10\\); console.log\\(celery\\);\")", + "Bash(Get-ChildItem -Path \"c:\\\\Users\\\\bruno\\\\Documents\\\\GitHub\\\\vontor-cz\\\\backend\\\\\" -Directory | Select-Object -ExpandProperty Name)" ] } } diff --git a/backend/advertisement/tasks.py b/backend/advertisement/tasks.py index d6b7845..30a7527 100644 --- a/backend/advertisement/tasks.py +++ b/backend/advertisement/tasks.py @@ -14,8 +14,10 @@ def send_contact_me_email_task(client_email, message_content): "client_email": client_email, "message_content": message_content } + config_email = SiteConfiguration.get_solo().contact_email + recipient = config_email if config_email else "brunovontor@gmail.com" send_email_with_context( - recipients=SiteConfiguration.get_solo().contact_email, + recipients=recipient, subject="Poptávka z kontaktního formuláře!!!", template_path="email/contact_me.html", context=context, diff --git a/backend/templates/email/contact_me.html b/backend/templates/email/contact_me.html new file mode 100644 index 0000000..1f9d9ab --- /dev/null +++ b/backend/templates/email/contact_me.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ 📬 Nová zpráva z kontaktního formuláře +

+

+ Přišla poptávka přes vontor.cz +

+
+

Od

+

+ {{ client_email }} +

+
+

Zpráva

+

{{ message_content }}

+
+ + Odpovědět + +
diff --git a/frontend/src/components/home/ContactMe/ContactMeForm.tsx b/frontend/src/components/home/ContactMe/ContactMeForm.tsx index ff4387b..f4ff139 100644 --- a/frontend/src/components/home/ContactMe/ContactMeForm.tsx +++ b/frontend/src/components/home/ContactMe/ContactMeForm.tsx @@ -1,35 +1,50 @@ import React, { useState, useRef } from "react" import styles from "./contact-me.module.css" import { LuMousePointerClick } from "react-icons/lu"; +import { publicApi } from "@/api/publicClient"; export default function ContactMeForm() { - const [opened, setOpened] = useState(false) - const [contentMoveUp, setContentMoveUp] = useState(false) - const [openingBehind, setOpeningBehind] = useState(false) - // const [success, setSuccess] = useState(false) + const [opened, setOpened] = useState(true) + const [contentMoveUp, setContentMoveUp] = useState(true) + const [openingBehind, setOpeningBehind] = useState(true) + const [email, setEmail] = useState("") + const [message, setMessage] = useState("") + const [loading, setLoading] = useState(false) + const [success, setSuccess] = useState(false) + const [error, setError] = useState("") const openingRef = useRef(null) - function handleSubmit() { - // form submission logic here + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setLoading(true) + setError("") + try { + await publicApi.post("/api/advertisement/contact-me/", { email, message, hp: "" }) + setSuccess(true) + setEmail("") + setMessage("") + } catch { + setError("Nepodařilo se odeslat zprávu. Zkuste to prosím znovu.") + } finally { + setLoading(false) + } } const toggleOpen = () => { if (!opened) { setOpened(true) setOpeningBehind(false) - setContentMoveUp(false) - // Wait for the rotate-opening animation to finish before moving content up - // The actual moveUp will be handled in onTransitionEnd + setContentMoveUp(false) } else { setContentMoveUp(false) setOpeningBehind(false) - setTimeout(() => setOpened(false), 1000) // match transition duration + setTimeout(() => setOpened(false), 1000) } } const handleTransitionEnd = (e: React.TransitionEvent) => { if (opened && e.propertyName === "transform") { - setContentMoveUp(true) + setContentMoveUp(true) setTimeout(() => setOpeningBehind(true), 10) } if (!opened && e.propertyName === "transform") { @@ -38,7 +53,6 @@ export default function ContactMeForm() { } return ( -
- +
-
- -