diff --git a/package-lock.json b/package-lock.json index a02d66f2..0ef25aef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,8 @@ "react-i18next": "^12.1.5", "sass": "^1.35.1", "styled-jsx": "^5.1.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "zod": "^3.22.4" }, "devDependencies": { "@types/dom-to-image": "^2.6.2", @@ -792,6 +793,36 @@ "glob": "7.1.7" } }, + "node_modules/@next/swc-android-arm-eabi": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz", + "integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-android-arm64": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz", + "integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-darwin-arm64": { "version": "13.2.4", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.4.tgz", @@ -807,6 +838,156 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.4.tgz", + "integrity": "sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-freebsd-x64": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz", + "integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz", + "integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.4.tgz", + "integrity": "sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.4.tgz", + "integrity": "sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.4.tgz", + "integrity": "sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.4.tgz", + "integrity": "sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.4.tgz", + "integrity": "sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.4.tgz", + "integrity": "sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz", + "integrity": "sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8022,6 +8203,14 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -8511,12 +8700,84 @@ "glob": "7.1.7" } }, + "@next/swc-android-arm-eabi": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz", + "integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==", + "optional": true + }, + "@next/swc-android-arm64": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz", + "integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==", + "optional": true + }, "@next/swc-darwin-arm64": { "version": "13.2.4", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.4.tgz", "integrity": "sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==", "optional": true }, + "@next/swc-darwin-x64": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.4.tgz", + "integrity": "sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==", + "optional": true + }, + "@next/swc-freebsd-x64": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz", + "integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==", + "optional": true + }, + "@next/swc-linux-arm-gnueabihf": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz", + "integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==", + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.4.tgz", + "integrity": "sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==", + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.4.tgz", + "integrity": "sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.4.tgz", + "integrity": "sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==", + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.4.tgz", + "integrity": "sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==", + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.4.tgz", + "integrity": "sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==", + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.4.tgz", + "integrity": "sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz", + "integrity": "sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==", + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -13859,6 +14120,11 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==" } } } diff --git a/package.json b/package.json index de51f16f..2dc58fe0 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "react-i18next": "^12.1.5", "sass": "^1.35.1", "styled-jsx": "^5.1.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "zod": "^3.22.4" }, "devDependencies": { "@types/dom-to-image": "^2.6.2", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index def2e6c0..263fbf8e 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -208,5 +208,6 @@ "loading": "Loading...", "topProject": "Top Project", "top_project_standards_fulfilled": "The project inspection revealed that this project fulfilled at least 12 of the 19 Top Project <2>standards.", - "standardsLink": "https://www.plant-for-the-planet.org/standards/" + "standardsLink": "https://www.plant-for-the-planet.org/standards/", + "enterValidEmail": "Please enter a valid email" } \ No newline at end of file diff --git a/src/Donations/Components/ContactsForm.tsx b/src/Donations/Components/ContactsForm.tsx index 7f07ca7a..6dbe254c 100644 --- a/src/Donations/Components/ContactsForm.tsx +++ b/src/Donations/Components/ContactsForm.tsx @@ -18,6 +18,7 @@ import { ContactDetails } from "@planet-sdk/common"; import { AddressCandidate, GeocodeSuggestion } from "src/Common/Types/arcgis"; import GiftIcon from "public/assets/icons/GiftIcon"; import { euCountries } from "src/Utils/countryUtils"; +import { isEmailValid } from "src/Utils/isEmailValid"; interface FormData extends ContactDetails { isPackageWanted: boolean; @@ -39,7 +40,7 @@ function ContactsForm(): ReactElement { client_id: process.env.ESRI_CLIENT_ID, client_secret: process.env.ESRI_CLIENT_SECRET, } - : {}, + : {} ); const { profile, @@ -62,7 +63,10 @@ function ContactsForm(): ReactElement { React.useEffect(() => { if (contactDetails) { - reset({ ...contactDetails, isPackageWanted: isPackageWanted !== false && isEligibleForPackage }); + reset({ + ...contactDetails, + isPackageWanted: isPackageWanted !== false && isEligibleForPackage, + }); if (contactDetails.companyname) { setIsCompany(true); } @@ -83,7 +87,7 @@ function ContactsForm(): ReactElement { React.useEffect(() => { const fiteredCountry = COUNTRY_ADDRESS_POSTALS.filter( - (country) => country.abbrev === contactDetails.country, + (country) => country.abbrev === contactDetails.country ); setPostalRegex(fiteredCountry[0]?.postal); }, [contactDetails.country]); @@ -95,7 +99,7 @@ function ContactsForm(): ReactElement { query: { ...router.query, step: PAYMENT }, }, undefined, - { shallow: true }, + { shallow: true } ); setContactDetails({ ...enteredContactDetails, @@ -108,8 +112,8 @@ function ContactsForm(): ReactElement { const [postalRegex, setPostalRegex] = React.useState( COUNTRY_ADDRESS_POSTALS.filter( - (country) => country.abbrev === contactDetails.country, - )[0]?.postal, + (country) => country.abbrev === contactDetails.country + )[0]?.postal ); const changeCountry = (country: string) => { @@ -201,7 +205,7 @@ function ContactsForm(): ReactElement { query: { ...router.query, step: DONATE }, }, undefined, - { shallow: true }, + { shallow: true } ); }} style={{ marginRight: "12px" }} @@ -275,9 +279,14 @@ function ContactsForm(): ReactElement { name="email" control={control} rules={{ - required: true, - pattern: - /^([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)$/i, + required: { + value: true, + message: t("emailRequired"), + }, + validate: { + emailInvalid: (value) => + value.length === 0 || isEmailValid(value), + }, }} render={({ field: { onChange, value } }) => ( )} /> - {errors.email && errors.email.type !== "validate" && ( - {t("emailRequired")} + {errors.email && ( + + {errors.email.type === "required" + ? t("emailRequired") + : t("enterValidEmail")} + )} {/* {errors.email && errors.email.type === "validate" && ( {t("useSameEmail")} @@ -550,7 +563,7 @@ function ContactsForm(): ReactElement { totalCost: getFormatedCurrency( i18n.language, currency, - paymentSetup.unitCost * quantity, + paymentSetup.unitCost * quantity ), frequency: frequency === "once" ? "" : t(frequency).toLowerCase(), diff --git a/src/Donations/Micros/GiftForm.tsx b/src/Donations/Micros/GiftForm.tsx index 4cd20472..a525cd46 100644 --- a/src/Donations/Micros/GiftForm.tsx +++ b/src/Donations/Micros/GiftForm.tsx @@ -8,6 +8,7 @@ import ToggleSwitch from "../../Common/InputTypes/ToggleSwitch"; import { useRouter } from "next/router"; import GiftIcon from "public/assets/icons/GiftIcon"; import { NoGift } from "@planet-sdk/common/build/types/donation"; +import { isEmailValid } from "src/Utils/isEmailValid"; type GiftFormData = { recipientName: string; @@ -130,9 +131,14 @@ export default function GiftForm(): ReactElement { name="recipientEmail" control={control} rules={{ - required: true, - pattern: - /^([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)$/i, + required: { + value: true, + message: t("emailRequired"), + }, + validate: { + emailInvalid: (value) => + value.length === 0 || isEmailValid(value), + }, }} render={({ field: { onChange, value } }) => ( )} /> - {errors.recipientEmail && ( - {t("emailRequired")} + + {errors.recipientEmail.type === "required" + ? t("emailRequired") + : t("enterValidEmail")} + )}
diff --git a/src/Donations/Micros/OnBehalf.tsx b/src/Donations/Micros/OnBehalf.tsx index ceeefdb2..855f3501 100644 --- a/src/Donations/Micros/OnBehalf.tsx +++ b/src/Donations/Micros/OnBehalf.tsx @@ -6,6 +6,7 @@ import MaterialTextField from "src/Common/InputTypes/MaterialTextField"; import ToggleSwitch from "src/Common/InputTypes/ToggleSwitch"; import { QueryParamContext } from "src/Layout/QueryParamContext"; import { OnBehalfDonor } from "src/Common/Types"; +import { isEmailValid } from "src/Utils/isEmailValid"; const OnBehalf: FC = () => { const { t } = useTranslation("common"); @@ -108,8 +109,14 @@ const OnBehalf: FC = () => { name="email" control={control} rules={{ - pattern: - /^([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)$/i, + required: { + value: true, + message: t("emailRequired"), + }, + validate: { + emailInvalid: (value) => + value.length === 0 || isEmailValid(value), + }, }} render={({ field: { onChange, value } }) => ( { /> )} /> - {errors.email && ( + {errors.email && errors.email.type === "required" && ( - {t("inValidField", { - fieldName: "Email", - })} + {errors.email.type === "required" + ? t("emailRequired") + : t("inValidField", { + fieldName: "Email", + })} )} diff --git a/src/Utils/isEmailValid.ts b/src/Utils/isEmailValid.ts new file mode 100644 index 00000000..c7d3fc90 --- /dev/null +++ b/src/Utils/isEmailValid.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; + +const INVALID_DOMAIN_LIST = ["dummy.de", "example.com", "company.de"]; + +export const isEmailValid = (value: string): boolean => { + try { + const emailSchema = z.string().email(); + const parsedEmail = emailSchema.parse(value); + + const domain = parsedEmail.split("@")[1]; + + // Check if the domain is in the INVALID_DOMAIN_LIST + if (INVALID_DOMAIN_LIST.includes(domain)) { + return false; + } + } catch (err) { + return false; + } + return true; +};