From 273c3405467f4cc49f412a1ef5fc716f4e2d2de1 Mon Sep 17 00:00:00 2001 From: Dmitriy Myakotin <75628188+MDI74@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:41:24 +0500 Subject: [PATCH 01/29] feat: add mail.ru smtp email sender and add smartcaptcha --- .env.development | 8 +- .env.production | 7 +- components/Form/Form.tsx | 6 +- .../redesign/FormRedesign/FormRedesign.scss | 6 + .../redesign/FormRedesign/FormRedesign.tsx | 277 +++++++++--------- package-lock.json | 21 +- package.json | 2 + packages.d.ts | 1 + pages/api/sendEmail.ts | 46 +++ pages/api/validateCaptchaToken.ts | 41 +++ pages/index.tsx | 3 +- services/emailService/emailService.ts | 24 +- .../validateCaptchaToken.ts | 20 ++ tsconfig.json | 3 +- 14 files changed, 301 insertions(+), 164 deletions(-) create mode 100644 packages.d.ts create mode 100644 pages/api/sendEmail.ts create mode 100644 pages/api/validateCaptchaToken.ts create mode 100644 services/smartCaptchaService/validateCaptchaToken.ts diff --git a/.env.development b/.env.development index fdf469b5..07c382c3 100644 --- a/.env.development +++ b/.env.development @@ -4,4 +4,10 @@ NEXT_PUBLIC_EMAIL_TEMPLATE_ID=template_cz5xqkf NEXT_PUBLIC_EMAIL_PUBLIC_KEY=cmeaOeNpGPYjN8WzV NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=UA-171018032-1 NEXT_PUBLIC_YANDEX_METRIKA_ID=89913543 -NEXT_PUBLIC_ARTICLE_URL=https://raw.githubusercontent.com/TourmalineCore/TourmalineCore.Articles/master/articles/ \ No newline at end of file +NEXT_PUBLIC_ARTICLE_URL=https://raw.githubusercontent.com/TourmalineCore/TourmalineCore.Articles/master/articles/ +NEXT_PUBLIC_TARGET_EMAIL=targetEmail@tourmalinecore.com +MAILRU_EMAIL=test@tourmalinecore.com +MAILRU_PASSWORD=yourpassword +NEXT_PUBLIC_SMARTCAPTCHA_CLIENT_KEY=ysc1_ruZxAsOm5Z3ubTqoerfrehmutrhl0YsP7Tkg3r4mfMPB14652ea0 +SMARTCAPTCHA_SERVER_KEY=ysc2_ruZxAsOm5Z3ubTqoerfrehmutrhl0YsP7Tkg3r4mfMPB14652ea0 + \ No newline at end of file diff --git a/.env.production b/.env.production index fdf469b5..a7d3ec2a 100644 --- a/.env.production +++ b/.env.production @@ -4,4 +4,9 @@ NEXT_PUBLIC_EMAIL_TEMPLATE_ID=template_cz5xqkf NEXT_PUBLIC_EMAIL_PUBLIC_KEY=cmeaOeNpGPYjN8WzV NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=UA-171018032-1 NEXT_PUBLIC_YANDEX_METRIKA_ID=89913543 -NEXT_PUBLIC_ARTICLE_URL=https://raw.githubusercontent.com/TourmalineCore/TourmalineCore.Articles/master/articles/ \ No newline at end of file +NEXT_PUBLIC_ARTICLE_URL=https://raw.githubusercontent.com/TourmalineCore/TourmalineCore.Articles/master/articles/ +NEXT_PUBLIC_TARGET_EMAIL=targetEmail@tourmalinecore.com +MAILRU_EMAIL=test@tourmalinecore.com +MAILRU_PASSWORD=yourpassword +NEXT_PUBLIC_SMARTCAPTCHA_CLIENT_KEY=ysc1_ruZxAsOm5Z3ubTqoerfrehmutrhl0YsP7Tkg3r4mfMPB14652ea0 +SMARTCAPTCHA_SERVER_KEY=ysc2_ruZxAsOm5Z3ubTqoerfrehmutrhl0YsP7Tkg3r4mfMPB14652ea0 \ No newline at end of file diff --git a/components/Form/Form.tsx b/components/Form/Form.tsx index c7a912f3..a8788695 100644 --- a/components/Form/Form.tsx +++ b/components/Form/Form.tsx @@ -16,7 +16,6 @@ import { Textarea } from './components/Textarea/Textarea'; import { Spinner } from '../Spinner/Spinner'; import { DEFAULT_LOCALE } from '../../common/constants'; import { isChineseLanguage } from '../../common/utils'; -import { ReCAPTCHALanguage } from '../../common/enums/captcha'; export function Form({ onSubmit = () => {}, @@ -113,14 +112,13 @@ export function Form({ - - + /> */} ); diff --git a/components/redesign/FormRedesign/FormRedesign.scss b/components/redesign/FormRedesign/FormRedesign.scss index 184ea0dd..fad727dc 100644 --- a/components/redesign/FormRedesign/FormRedesign.scss +++ b/components/redesign/FormRedesign/FormRedesign.scss @@ -1,6 +1,7 @@ .form-redesign { $this: &; + position: relative; display: flex; align-items: center; flex-direction: column; @@ -193,6 +194,11 @@ text-underline-offset: 2px; } + &__captcha { + position: absolute; + transform: translate(0, -20%); + } + &.is-modal { padding-top: 28px; diff --git a/components/redesign/FormRedesign/FormRedesign.tsx b/components/redesign/FormRedesign/FormRedesign.tsx index c96a61f4..45d5a9b8 100644 --- a/components/redesign/FormRedesign/FormRedesign.tsx +++ b/components/redesign/FormRedesign/FormRedesign.tsx @@ -1,22 +1,15 @@ import { Trans, useTranslation } from 'next-i18next'; -import { - ChangeEvent, - FormEvent, - useMemo, - useRef, - useState, -} from 'react'; +import { ChangeEvent, FormEvent, useState } from 'react'; import clsx from 'clsx'; import Link from 'next/link'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import ReCAPTCHA from 'react-google-recaptcha'; +import { SmartCaptcha } from '@yandex/smart-captcha'; import { InputRedesign } from './components/InputRedesign/InputRedesign'; import { TextareaRedesign } from './components/TextareaRedesign/TextareaRedesign'; import { Spinner } from '../../Spinner/Spinner'; -import { DEFAULT_LOCALE } from '../../../common/constants'; -import { ReCAPTCHALanguage } from '../../../common/enums/captcha'; +import { validateCaptchaToken } from '../../../services/smartCaptchaService/validateCaptchaToken'; export function FormRedesign({ onSubmit, @@ -37,20 +30,12 @@ export function FormRedesign({ locale, } = useRouter(); - const recaptchaRef = useRef(null); - - const routerLocale = useMemo(() => { - if (!locale) { - return DEFAULT_LOCALE; - } - - return locale; - }, [locale]); - const [isLoading, setIsLoading] = useState(false); - const [email, setEmail] = useState(``); + const [showCaptcha, setShowCaptcha] = useState(false); + const [isVerified, setIsVerified] = useState(false); + const { nameLabel, emailLabel, @@ -63,30 +48,29 @@ export function FormRedesign({ } = getTranslations(); return ( - <> -
- { - isSubmit && ( -
- -
- ) - } -

- {isSubmit ? `${titleSubmitted}` : t(`title`)} -

- { - isSubmit + + { + isSubmit && ( +
+ +
+ ) + } +

+ {isSubmit ? `${titleSubmitted}` : t(`title`)} +

+ { + isSubmit && (

{description} @@ -99,106 +83,106 @@ export function FormRedesign({

) - } + } + { + !isSubmit && ( +

+ {t(`description`)} +

+ ) + } + { + !isSubmit && ( + <> + { + if (e.key === `Enter`) { + e.preventDefault(); + } + }} + required + /> + ) => setEmail(e.target.value)} + onKeyDown={(e) => { + if (e.key === `Enter`) { + e.preventDefault(); + } + }} + required + /> + + + ) + } +
{ - !isSubmit && ( -

- {t(`description`)} -

+ isSubmit ? ( + + ) : ( + ) } { !isSubmit && ( - <> - { - if (e.key === `Enter`) { - e.preventDefault(); - } +
+ , }} - required - /> - ) => setEmail(e.target.value)} - onKeyDown={(e) => { - if (e.key === `Enter`) { - e.preventDefault(); - } - }} - required - /> - - +
) } -
- { - isSubmit ? ( - - ) : ( - - ) - } - { - !isSubmit && ( -
- , - }} - /> -
- ) - } +
+ + {showCaptcha && ( +
+
- - - - + )} + ); function getTranslations() { @@ -227,26 +211,31 @@ export function FormRedesign({ }; } + async function handleCaptchaSuccess(captchaToken: string) { + const response = await validateCaptchaToken(captchaToken); + + if (response.status === `ok`) { + setIsVerified(true); + } + + setShowCaptcha(false); + } + async function handleFormSubmit(event: FormEvent) { event.preventDefault(); setIsLoading(true); try { - if (!recaptchaRef.current) { - return; - } - - const token = await recaptchaRef.current.executeAsync(); - - if (!token) { + if (!isVerified) { + setShowCaptcha(true); return; } const formData = new FormData(event.target as HTMLFormElement); - formData.append(`g-recaptcha-response`, token); onSubmit(formData); - recaptchaRef.current.reset(); + + setIsVerified(false); } finally { setIsLoading(false); } diff --git a/package-lock.json b/package-lock.json index f12f6e86..0ddfd73e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,10 @@ "packages": { "": { "name": "tourmalinecore_website", - "version": "3.0.0", + "version": "3.1.3", "dependencies": { "@emailjs/browser": "^3.6.2", + "@yandex/smart-captcha": "^2.8.0", "clsx": "^2.1.1", "cookies-next": "^6.0.0", "gitalk": "^1.7.2", @@ -16,6 +17,7 @@ "next": "^15.3.2", "next-i18next": "^8.6.0", "next-sitemap": "^3.1.11", + "nodemailer": "^7.0.5", "react": "18.2.0", "react-dom": "18.2.0", "react-focus-lock": "^2.13.6", @@ -1992,6 +1994,14 @@ "win32" ] }, + "node_modules/@yandex/smart-captcha": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@yandex/smart-captcha/-/smart-captcha-2.8.0.tgz", + "integrity": "sha512-be1lrTB+J6qc/eLj9aiTZmh4ulRLs4xJvW5ER6UUvZ0+l2MMJuz7TnHBf2tenATlhTX8IHgnsvIvDYypN66pvg==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -6888,6 +6898,15 @@ "warning": "^3.0.0" } }, + "node_modules/nodemailer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", + "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", diff --git a/package.json b/package.json index bcee6ff1..a1eee737 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@emailjs/browser": "^3.6.2", + "@yandex/smart-captcha": "^2.8.0", "clsx": "^2.1.1", "cookies-next": "^6.0.0", "gitalk": "^1.7.2", @@ -26,6 +27,7 @@ "next": "^15.3.2", "next-i18next": "^8.6.0", "next-sitemap": "^3.1.11", + "nodemailer": "^7.0.5", "react": "18.2.0", "react-dom": "18.2.0", "react-focus-lock": "^2.13.6", diff --git a/packages.d.ts b/packages.d.ts new file mode 100644 index 00000000..27fe752c --- /dev/null +++ b/packages.d.ts @@ -0,0 +1 @@ +declare module 'nodemailer' diff --git a/pages/api/sendEmail.ts b/pages/api/sendEmail.ts new file mode 100644 index 00000000..53ceef36 --- /dev/null +++ b/pages/api/sendEmail.ts @@ -0,0 +1,46 @@ +/* eslint-disable no-console */ +import { NextApiRequest, NextApiResponse } from 'next'; +import nodemailer from 'nodemailer'; + +export default async function sendEmail(req: NextApiRequest, res: NextApiResponse) { + try { + const { + to, subject, message, html, + } = req.body; + + // Create Mail.ru transporter + const transporter = nodemailer.createTransport({ + host: `smtp.mail.ru`, + port: 465, + secure: true, + auth: { + user: process.env.MAILRU_EMAIL, + pass: process.env.MAILRU_PASSWORD, + }, + }); + + const mailOptions = { + from: `"Tourmaline core" ${process.env.MAILRU_EMAIL}`, + to, + subject, + message, + html, + }; + + const info = await transporter.sendMail(mailOptions); + + return res.status(200) + .json({ + success: true, + messageId: info.messageId, + }); + } catch (error: any) { + console.error(`Error sending email:`, error); + return res.status(500) + .json({ + success: false, + message: `Error sending email`, + error: error.message, + }); + } +} diff --git a/pages/api/validateCaptchaToken.ts b/pages/api/validateCaptchaToken.ts new file mode 100644 index 00000000..f88db515 --- /dev/null +++ b/pages/api/validateCaptchaToken.ts @@ -0,0 +1,41 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +export default async function validateCaptchaToken(req: NextApiRequest, res: NextApiResponse) { + try { + const { + token, + } = req.body; + + const formData = new URLSearchParams(); + formData.append(`secret`, process.env.SMARTCAPTCHA_SERVER_KEY as string); + formData.append(`token`, token); + + const response = await fetch(`https://smartcaptcha.yandexcloud.net/validate`, { + method: `POST`, + headers: { + 'Content-Type': `application/x-www-form-urlencoded`, + }, + body: formData, + }); + + const responseData = await response.json(); + + if (response.ok) { + return res.status(200) + .json(responseData); + } + return res.status(400) + .json({ + status: `failed`, + message: responseData.message || `Captcha verification failed`, + }); + } catch (error) { + // eslint-disable-next-line no-console + console.error(`Captcha verification error:`, error); + return res.status(500) + .json({ + status: `failed`, + message: `Server error during captcha check`, + }); + } +} diff --git a/pages/index.tsx b/pages/index.tsx index 9ac79b7c..a7c4b6ac 100755 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -13,6 +13,7 @@ import { ProjectsWithTextBlockRedesign } from '../components/redesign/ProjectsWi import { CollageWithLinkRedesign } from '../components/redesign/CollageWithLinkRedesign/CollageWithLinkRedesign'; import { ServicesRedesign } from '../components/redesign/ServicesRedesign/ServicesRedesign'; import { useDeviceSize } from '../common/hooks'; +import { FormBlockRedesign } from '../components/redesign/FormBlockRedesign/FormBlockRedesign'; export default function HomePage() { const { @@ -56,7 +57,7 @@ export default function HomePage() { /> {isTablet && } {isTablet && } - {/* */} + Имя: ${messageData.name}

Описание задачи:
${messageData.message}`, + }), + }); } catch (error) { // eslint-disable-next-line @typescript-eslint/no-throw-literal throw error || `Error`; diff --git a/services/smartCaptchaService/validateCaptchaToken.ts b/services/smartCaptchaService/validateCaptchaToken.ts new file mode 100644 index 00000000..e2b2ad32 --- /dev/null +++ b/services/smartCaptchaService/validateCaptchaToken.ts @@ -0,0 +1,20 @@ +export async function validateCaptchaToken( + token: string, +) { + try { + const response = await fetch(`/api/validateCaptchaToken`, { + method: `POST`, + headers: { + 'Content-Type': `application/json`, + }, + body: JSON.stringify({ + token, + }), + }); + + return await response.json(); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw error || `Error`; + } +} diff --git a/tsconfig.json b/tsconfig.json index 9ab3d2b4..03174b9c 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,8 @@ "**/*.tsx", "**/*.jsx", "**/*.config.js", - "next-sitemap.config.js" + "next-sitemap.config.js" , + "packages.d.ts" ], "exclude": ["node_modules"] } From cc0315f4447822e3f1f85f1ee533b1a9cf5c41a8 Mon Sep 17 00:00:00 2001 From: Dmitriy Myakotin <75628188+MDI74@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:52:21 +0500 Subject: [PATCH 02/29] refactor(FormRedesign): rename state variable for verification captcha --- components/redesign/FormRedesign/FormRedesign.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/redesign/FormRedesign/FormRedesign.tsx b/components/redesign/FormRedesign/FormRedesign.tsx index 45d5a9b8..349aded0 100644 --- a/components/redesign/FormRedesign/FormRedesign.tsx +++ b/components/redesign/FormRedesign/FormRedesign.tsx @@ -34,7 +34,7 @@ export function FormRedesign({ const [email, setEmail] = useState(``); const [showCaptcha, setShowCaptcha] = useState(false); - const [isVerified, setIsVerified] = useState(false); + const [isCaptchaVerified, setIsCaptchaVerified] = useState(false); const { nameLabel, @@ -215,7 +215,7 @@ export function FormRedesign({ const response = await validateCaptchaToken(captchaToken); if (response.status === `ok`) { - setIsVerified(true); + setIsCaptchaVerified(true); } setShowCaptcha(false); @@ -226,7 +226,7 @@ export function FormRedesign({ setIsLoading(true); try { - if (!isVerified) { + if (!isCaptchaVerified) { setShowCaptcha(true); return; } @@ -235,7 +235,7 @@ export function FormRedesign({ onSubmit(formData); - setIsVerified(false); + setIsCaptchaVerified(false); } finally { setIsLoading(false); } From 46e3356cef31f776a9e8f64b6804353c6aedaeec Mon Sep 17 00:00:00 2001 From: Dmitriy Myakotin <75628188+MDI74@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:53:09 +0500 Subject: [PATCH 03/29] fix(validateCaptchaTokenApi): fix handle errors --- pages/api/validateCaptchaToken.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/api/validateCaptchaToken.ts b/pages/api/validateCaptchaToken.ts index f88db515..195ea673 100644 --- a/pages/api/validateCaptchaToken.ts +++ b/pages/api/validateCaptchaToken.ts @@ -20,10 +20,11 @@ export default async function validateCaptchaToken(req: NextApiRequest, res: Nex const responseData = await response.json(); - if (response.ok) { + if (responseData.status === `ok`) { return res.status(200) .json(responseData); } + return res.status(400) .json({ status: `failed`, From 1d3ac1f2d65e0de590af4a77ff8c53d3b11dc3b2 Mon Sep 17 00:00:00 2001 From: Dmitriy Myakotin <75628188+MDI74@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:56:51 +0500 Subject: [PATCH 04/29] fix: lint fix --- components/Form/Form.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/components/Form/Form.tsx b/components/Form/Form.tsx index a8788695..5bfec43f 100644 --- a/components/Form/Form.tsx +++ b/components/Form/Form.tsx @@ -2,10 +2,8 @@ import clsx from 'clsx'; import { Trans, useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import { - FormEvent, - useMemo, - useRef, - useState, + FormEvent, useRef, + useState } from 'react'; import ReCAPTCHA from 'react-google-recaptcha'; @@ -14,7 +12,6 @@ import { Input } from './components/Input/Input'; import { PrimaryButton } from '../PrimaryButton/PrimaryButton'; import { Textarea } from './components/Textarea/Textarea'; import { Spinner } from '../Spinner/Spinner'; -import { DEFAULT_LOCALE } from '../../common/constants'; import { isChineseLanguage } from '../../common/utils'; export function Form({ @@ -33,13 +30,13 @@ export function Form({ const recaptchaRef = useRef(null); - const routerLocale = useMemo(() => { - if (!router.locale) { - return DEFAULT_LOCALE; - } + // const routerLocale = useMemo(() => { + // if (!router.locale) { + // return DEFAULT_LOCALE; + // } - return router.locale; - }, [router.locale]); + // return router.locale; + // }, [router.locale]); return ( <> From 95db9f826fc8d564bbb1f7066e339a0704935b4c Mon Sep 17 00:00:00 2001 From: Dmitriy Myakotin <75628188+MDI74@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:58:55 +0500 Subject: [PATCH 05/29] fix: lint fix --- components/Form/Form.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/Form/Form.tsx b/components/Form/Form.tsx index 5bfec43f..bbec91a7 100644 --- a/components/Form/Form.tsx +++ b/components/Form/Form.tsx @@ -1,10 +1,7 @@ import clsx from 'clsx'; import { Trans, useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; -import { - FormEvent, useRef, - useState -} from 'react'; +import { FormEvent, useRef, useState } from 'react'; import ReCAPTCHA from 'react-google-recaptcha'; import { ExternalLink } from '../ExternalLink/ExternalLink'; From 9f2457d69e857cfcfb3a821b0bbd2b57818a7c61 Mon Sep 17 00:00:00 2001 From: Dmitriy Myakotin <75628188+MDI74@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:47:08 +0500 Subject: [PATCH 06/29] chore: return all forms --- components/Cta/Cta.scss | 1 - components/Cta/Cta.tsx | 12 +- components/Form/Form.scss | 8 + components/Form/Form.tsx | 197 +++++++++--------- components/FormModal/FormModal.tsx | 1 + .../HeroBlockTechnology.scss | 1 - .../HeroBlockTechnology.tsx | 11 +- .../FormBlockRedesign/FormBlockRedesign.tsx | 4 + .../redesign/FormRedesign/FormRedesign.scss | 5 +- .../redesign/FormRedesign/FormRedesign.tsx | 45 ++-- pages/backend/index.tsx | 6 +- pages/design/index.tsx | 6 +- pages/embedded/index.tsx | 6 +- pages/frontend-team/index.tsx | 6 +- pages/frontend/index.tsx | 6 +- pages/ml/index.tsx | 6 +- pages/qa/index.tsx | 6 +- pages/teams/index.tsx | 6 +- 18 files changed, 188 insertions(+), 145 deletions(-) diff --git a/components/Cta/Cta.scss b/components/Cta/Cta.scss index e72154d7..5be0106f 100644 --- a/components/Cta/Cta.scss +++ b/components/Cta/Cta.scss @@ -107,7 +107,6 @@ border-radius: 100px; padding: 12px 40px; line-height: 1.5; - text-decoration: none; color: $color-white; background-image: none; diff --git a/components/Cta/Cta.tsx b/components/Cta/Cta.tsx index 8b7ee12f..8f7bcb3e 100644 --- a/components/Cta/Cta.tsx +++ b/components/Cta/Cta.tsx @@ -4,6 +4,7 @@ import { useBodyScrollHidden } from '../../common/hooks/useBodyScrollHiden'; import { TechnologyPageAnchorLink } from '../../common/enums'; import { usePath } from '../../common/hooks'; import { FormModal } from '../FormModal/FormModal'; +import { PrimaryButton } from '../PrimaryButton/PrimaryButton'; export function Cta() { const { @@ -25,19 +26,12 @@ export function Cta() {

{t(`title`)}

- - {t(`buttonText`)} - - {/* Todo: uncomment after editing the form */} - {/* setIsOpen(true)} className={`cta__button cta__button--${slicePathname}`} > {t(`buttonText`)} - */} +
diff --git a/components/Form/Form.scss b/components/Form/Form.scss index e8bb6e1b..2525ae35 100644 --- a/components/Form/Form.scss +++ b/components/Form/Form.scss @@ -11,6 +11,7 @@ } &__footer { + position: relative; margin-top: 40px; @include tablet { @@ -25,6 +26,7 @@ &__button { margin-bottom: 24px; + cursor: pointer; @include tablet { flex: none; @@ -48,6 +50,12 @@ } } + &__captcha { + position: absolute; + left: 0; + top: 0; + } + &--zh { #{$this}__approval { font-size: 16px; diff --git a/components/Form/Form.tsx b/components/Form/Form.tsx index bbec91a7..e010fc52 100644 --- a/components/Form/Form.tsx +++ b/components/Form/Form.tsx @@ -1,15 +1,17 @@ import clsx from 'clsx'; import { Trans, useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; -import { FormEvent, useRef, useState } from 'react'; -import ReCAPTCHA from 'react-google-recaptcha'; +import { FormEvent, useMemo, useState } from 'react'; +import { SmartCaptcha } from '@yandex/smart-captcha'; import { ExternalLink } from '../ExternalLink/ExternalLink'; import { Input } from './components/Input/Input'; import { PrimaryButton } from '../PrimaryButton/PrimaryButton'; import { Textarea } from './components/Textarea/Textarea'; import { Spinner } from '../Spinner/Spinner'; import { isChineseLanguage } from '../../common/utils'; +import { validateCaptchaToken } from '../../services/smartCaptchaService/validateCaptchaToken'; +import { DEFAULT_LOCALE } from '../../common/constants'; export function Form({ onSubmit = () => {}, @@ -25,118 +27,123 @@ export function Form({ const [isLoading, setIsLoading] = useState(false); - const recaptchaRef = useRef(null); + const routerLocale = useMemo(() => { + if (!router.locale) { + return DEFAULT_LOCALE; + } - // const routerLocale = useMemo(() => { - // if (!router.locale) { - // return DEFAULT_LOCALE; - // } + return router.locale; + }, [router.locale]); - // return router.locale; - // }, [router.locale]); + const [showCaptcha, setShowCaptcha] = useState(false); + const [isCaptchaVerified, setIsCaptchaVerified] = useState(false); return ( - <> -
- { - if (e.key === `Enter`) { - e.preventDefault(); - } - }} - required - /> - { - if (e.key === `Enter`) { - e.preventDefault(); - } - }} - required - /> -