redesigned app for cleaner ux

This commit is contained in:
FernandoJVideira committed 2026-09-12 13:09:17 +01:00
1 parent fe468885b0
commit 2a3f542e98
33 files changed
+1379 -374

No files matched your search

+94 -24
View File
@@ -1,11 +1,60 @@
import React from "react";
import React, { useState } from "react";
import emailjs from "@emailjs/browser";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { useTranslation } from "react-i18next";
import { motion, useReducedMotion } from "framer-motion";
import PhoneCodeSelect from "./PhoneCodeSelect";
const PHONE_PATTERNS: Record<string, string> = {
"+351": "### ### ###",
"+34": "### ### ###",
"+33": "# ## ## ## ##",
"+44": "#### ######",
"+1": "(###) ###-####",
"+55": "(##) #####-####",
};
const digitsOnly = (value: string) => value.replace(/\D/g, "");
const maxDigitsFor = (pattern: string) =>
pattern.split("").filter((c) => c === "#").length;
const formatPhoneNumber = (digits: string, pattern: string) => {
let result = "";
let digitIndex = 0;
for (let i = 0; i < pattern.length && digitIndex < digits.length; i++) {
const ch = pattern[i];
if (ch === "#") {
result += digits[digitIndex];
digitIndex++;
} else {
result += ch;
}
}
return result;
};
const ContactForm = () => {
const { t } = useTranslation();
const prefersReducedMotion = useReducedMotion();
const [countryCode, setCountryCode] = useState("+351");
const [phoneDigits, setPhoneDigits] = useState("");
const activePattern = PHONE_PATTERNS[countryCode];
const formattedPhone = formatPhoneNumber(phoneDigits, activePattern);
const handleCountryChange = (code: string) => {
setCountryCode(code);
setPhoneDigits((prev) =>
prev.slice(0, maxDigitsFor(PHONE_PATTERNS[code]))
);
};
const handlePhoneChange = (raw: string) => {
const digits = digitsOnly(raw).slice(0, maxDigitsFor(activePattern));
setPhoneDigits(digits);
};
const notify = () => {
toast.success(t("contact.toasts.success"));
@@ -22,11 +71,12 @@ const ContactForm = () => {
const sendEmail = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
let isEmpty = Array.from(e.currentTarget.elements).some(
(input) =>
(input as HTMLInputElement).type !== "submit" &&
(input as HTMLInputElement).value.trim() === ""
);
const isEmpty = Array.from(e.currentTarget.elements).some((el) => {
if (el.tagName !== "INPUT" && el.tagName !== "TEXTAREA") return false;
const field = el as HTMLInputElement | HTMLTextAreaElement;
if (field.type === "hidden") return false;
return field.value.trim() === "";
});
if (isEmpty) {
emptyForm();
@@ -49,20 +99,31 @@ const ContactForm = () => {
);
e.currentTarget.reset();
setPhoneDigits("");
setCountryCode("+351");
};
return (
<div className="mt-10 mx-5 lg:mx-0 flex justify-center items-center">
<form onSubmit={sendEmail} className="w-full md:w-auto">
<div className="mt-10 mx-5 mb-10 lg:mx-0 flex justify-center items-center">
<motion.form
onSubmit={sendEmail}
className="w-full md:w-[42rem] rounded-3xl glass-panel shadow-glass-lg p-6 md:p-10"
initial={prefersReducedMotion ? { opacity: 0 } : { opacity: 0, y: 24 }}
whileInView={
prefersReducedMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
viewport={{ once: true, margin: "-80px" }}
transition={{ type: "spring", damping: 22, stiffness: 140 }}
>
<div className="flex flex-col md:flex-row md:gap-3">
<input
className="form-input"
className="form-input flex-1"
type="text"
name="user_name"
placeholder={t("contact.form.name")}
/>
<input
className="form-input"
className="form-input flex-1"
type="email"
name="user_email"
placeholder={t("contact.form.email")}
@@ -70,17 +131,24 @@ const ContactForm = () => {
</div>
<div className="flex flex-col md:flex-row md:gap-3">
<input
className="form-input"
className="form-input flex-1"
type="text"
name="user_company"
placeholder={t("contact.form.company")}
/>
<input
className="form-input"
type="number"
name="user_phone"
placeholder={t("contact.form.phone")}
/>
<div className="phone-input-group flex-1 flex items-center">
<PhoneCodeSelect value={countryCode} onChange={handleCountryChange} />
<span className="h-5 w-px bg-white/10 mx-2" aria-hidden="true" />
<input
className="phone-number-input"
type="tel"
inputMode="numeric"
name="user_phone"
value={formattedPhone}
onChange={(e) => handlePhoneChange(e.target.value)}
placeholder={t("contact.form.phone")}
/>
</div>
</div>
<div className="flex flex-col">
<input
@@ -91,20 +159,22 @@ const ContactForm = () => {
></input>
<textarea
className="form-input"
className="form-input resize-none"
name="message"
placeholder={t("contact.form.message")}
rows={6}
rows={5}
></textarea>
</div>
<div className="flex mt-5 mb-10 lg:mt-12 mx-10 lg:mx-20 rounded-xl bg-gradient-to-br from-gold-light to-gold-dark p-0.5 shadow-lg">
<button
<div className="flex mt-6 rounded-xl bg-gradient-to-br from-gold-light to-gold-mid p-0.5 shadow-gold-glow">
<motion.button
type="submit"
className="flex-1 font-medium md:px-8 py-3 text-2xl md:text-3xl bg-gradient-to-br from-custom-bglight to-custom-bgdark rounded-xl hover:bg-transparent hover:from-transparent hover:to-transparent transition duration-500"
className="flex-1 font-semibold px-8 py-3 text-xl md:text-2xl bg-custom-bgdark text-white rounded-[0.65rem] hover:bg-transparent transition-colors duration-300"
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
{t("contact.form.button")}
</button>
</motion.button>
</div>
<ToastContainer
@@ -119,7 +189,7 @@ const ContactForm = () => {
pauseOnHover
theme="dark"
/>
</form>
</motion.form>
</div>
);
};