diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..0b48afb Binary files /dev/null and b/bun.lockb differ diff --git a/package-lock.json b/package-lock.json index 005d2ce..febaa61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-dom": "^18.2.0", "react-i18next": "^14.1.2", "react-icons": "^5.0.1", + "react-phone-input-2": "^2.15.1", "react-router-dom": "^6.22.3", "react-toastify": "^10.0.5", "sort-by": "^0.0.2" @@ -1945,6 +1946,11 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3077,12 +3083,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" + }, + "node_modules/lodash.startswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", + "integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3246,7 +3272,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3589,6 +3614,16 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3670,6 +3705,28 @@ "react": "*" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-phone-input-2": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz", + "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==", + "dependencies": { + "classnames": "^2.2.6", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.reduce": "^4.6.0", + "lodash.startswith": "^4.2.1", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", + "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", diff --git a/package.json b/package.json index 0662684..90b729d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "react-dom": "^18.2.0", "react-i18next": "^14.1.2", "react-icons": "^5.0.1", + "react-phone-number-input": "^3.4.3", "react-router-dom": "^6.22.3", "react-toastify": "^10.0.5", "sort-by": "^0.0.2" diff --git a/public/locales/common/en.json b/public/locales/common/en.json index f594656..9d038eb 100644 --- a/public/locales/common/en.json +++ b/public/locales/common/en.json @@ -14,7 +14,7 @@ "part1": "We do all the digital footprint for you,", "part2": "so you can focus on what's important!" }, - "button": "Yes, I want that!" + "button": "Contact Us" }, "services": { "heading": { @@ -159,13 +159,14 @@ "company": "Company", "phone": "Phone", "question": "What is your most important question?", - "message": "Message", + "reference": "How did you hear about us?", "button": "Send" }, "toasts": { "success": "Your message has been sent successfully!", "error": "An error occurred while sending your message. Please try again later.", - "empty": "Please fill out all the fields before sending the message." + "empty": "Please fill out all the fields before sending the message.", + "invalidCountryCode": "Please enter a valid country code." } }, "footer": { diff --git a/public/locales/common/pt.json b/public/locales/common/pt.json index 21978f0..14d847e 100644 --- a/public/locales/common/pt.json +++ b/public/locales/common/pt.json @@ -165,7 +165,8 @@ "toasts": { "success": "A tua mensagem foi enviada com sucesso!", "error": "Ocorreu um erro ao enviar a tua mensagem. Por favor, tenta novamente mais tarde.", - "empty": "Por favor, preenche todos os campos antes de enviar a mensagem." + "empty": "Por favor, preenche todos os campos antes de enviar a mensagem.", + "invalidCountryCode": "Por favor, preenche um código de país válido." } }, "footer": { diff --git a/src/Components/Pages/Contact/ContactForm.css b/src/Components/Pages/Contact/ContactForm.css new file mode 100644 index 0000000..5270b05 --- /dev/null +++ b/src/Components/Pages/Contact/ContactForm.css @@ -0,0 +1,6 @@ +.PhoneInputInput { + background-color: transparent; + border: none; + outline: none; + width: 100%; +} \ No newline at end of file diff --git a/src/Components/Pages/Contact/ContactForm.tsx b/src/Components/Pages/Contact/ContactForm.tsx index 08033d3..1ab36e5 100644 --- a/src/Components/Pages/Contact/ContactForm.tsx +++ b/src/Components/Pages/Contact/ContactForm.tsx @@ -1,11 +1,15 @@ -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 PhoneInput, { formatPhoneNumber, parsePhoneNumber } from "react-phone-number-input"; +import 'react-phone-number-input/style.css' +import "./ContactForm.css"; const ContactForm = () => { const { t } = useTranslation(); + const [phone, setPhone] = useState(undefined); const notify = () => { toast.success(t("contact.toasts.success")); @@ -18,6 +22,10 @@ const ContactForm = () => { const emptyForm = () => { toast.warning(t("contact.toasts.empty")); }; + + const invalidCountryCode = () => { + toast.error(t("contact.toasts.invalidCountryCode")); + }; const sendEmail = (e: React.FormEvent) => { e.preventDefault(); @@ -28,11 +36,25 @@ const ContactForm = () => { (input as HTMLInputElement).value.trim() === "" ); - if (isEmpty) { + if (isEmpty || !phone) { // Check if phone is also empty emptyForm(); return; } + let parsedPhone = parsePhoneNumber(phone); + let countryCode = parsedPhone?.countryCallingCode; + + if (!countryCode) { + invalidCountryCode(); + return; + } + + // Append phone number to the form + const phoneInput = document.createElement("input"); + phoneInput.setAttribute("type", "hidden"); + phoneInput.setAttribute("value", `+${countryCode} ${formatPhoneNumber(phone)}`); + e.currentTarget.appendChild(phoneInput); + emailjs .sendForm("service_o7kxotj", "template_f30zlog", e.currentTarget, { publicKey: "vcSOwPedqkRH3nmr9", @@ -49,20 +71,21 @@ const ContactForm = () => { ); e.currentTarget.reset(); + setPhone(undefined); // Reset phone state }; return ( -
+
{
- +
+ +
{ placeholder={t("contact.form.question")} > - + name="user_reference" + placeholder={t("contact.form.reference")} + >
-
+
diff --git a/src/Components/Pages/Hero/Hero.tsx b/src/Components/Pages/Hero/Hero.tsx index 1b91b77..5c6281c 100644 --- a/src/Components/Pages/Hero/Hero.tsx +++ b/src/Components/Pages/Hero/Hero.tsx @@ -21,7 +21,7 @@ const Hero = () => { hero
-
+
hero { {t("hero.subheading.part2")}
-
+
{ handleNavigation("Contact"); }} > - diff --git a/src/Components/Pages/Services/Buttons.tsx b/src/Components/Pages/Services/Buttons.tsx index a976d68..556e87d 100644 --- a/src/Components/Pages/Services/Buttons.tsx +++ b/src/Components/Pages/Services/Buttons.tsx @@ -16,7 +16,7 @@ const Buttons = ({ changeActiveGroup }: props) => { }; return ( -
+
handleClick("markCards")} @@ -33,7 +33,7 @@ const Buttons = ({ changeActiveGroup }: props) => { isSelected={selectedButton === "smCards"} /> handleClick("camCards")} isSelected={selectedButton === "camCards"} /> diff --git a/src/Components/Pages/Services/CameraCards.tsx b/src/Components/Pages/Services/CameraCards.tsx index 8581569..b8bbc83 100644 --- a/src/Components/Pages/Services/CameraCards.tsx +++ b/src/Components/Pages/Services/CameraCards.tsx @@ -9,7 +9,7 @@ const CameraCards = () => { const { t } = useTranslation(); return ( -
+
{ const buttonClass = isSelected - ? "w-full font-bold md:px-4 py-1 text-xs lg:text-sm xl:text-2xl cursor-pointer" + ? "w-full font-bold md:px-4 py-1 text-xs lg:text-sm xl:text-xl cursor-pointer" : "w-full font-medium md:px-4 py-1 text-xs lg:text-sm xl:text-xl text-gray-400 shadow-inner hover:text-text-gold cursor-pointer"; return ( diff --git a/src/Components/Pages/Services/CardGroup.tsx b/src/Components/Pages/Services/CardGroup.tsx index 77f1c82..4190fce 100644 --- a/src/Components/Pages/Services/CardGroup.tsx +++ b/src/Components/Pages/Services/CardGroup.tsx @@ -31,7 +31,7 @@ const CardGroup = () => { } return ( -
+
{ActiveCards && }
diff --git a/src/Components/Pages/Services/MarketingCards.tsx b/src/Components/Pages/Services/MarketingCards.tsx index f0b63c6..e26834a 100644 --- a/src/Components/Pages/Services/MarketingCards.tsx +++ b/src/Components/Pages/Services/MarketingCards.tsx @@ -9,7 +9,7 @@ const MarketingCards = () => { const { t } = useTranslation(); return ( -
+
{ return (
{ return ( -
+
-
+
); }; diff --git a/src/Components/Pages/Services/SocialMedia.tsx b/src/Components/Pages/Services/SocialMedia.tsx index 0e66f2e..c30a2cc 100644 --- a/src/Components/Pages/Services/SocialMedia.tsx +++ b/src/Components/Pages/Services/SocialMedia.tsx @@ -9,7 +9,7 @@ const SocialMediaCards = () => { const { t } = useTranslation(); return ( -
+
{ const { t } = useTranslation(); return ( -
+