diff --git a/packages/web/components/templates/auth/EmailSignup.tsx b/packages/web/components/templates/auth/EmailSignup.tsx index 1f920f7ba..9cd21d206 100644 --- a/packages/web/components/templates/auth/EmailSignup.tsx +++ b/packages/web/components/templates/auth/EmailSignup.tsx @@ -1,7 +1,7 @@ import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' import { StyledText, StyledTextSpan } from '../../elements/StyledText' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { BorderedFormInput, FormLabel } from '../../elements/FormElements' import { TermAndConditionsFooter } from '../LoginForm' import { fetchEndpoint } from '../../../lib/appConfig' @@ -10,26 +10,19 @@ import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation import { useRouter } from 'next/router' import { formatMessage } from '../../../locales/en/messages' import { parseErrorCodes } from '../../../lib/queryParamParser' +import { + GoogleReCaptchaProvider, + GoogleReCaptchaCheckbox, +} from '@google-recaptcha/react' import Link from 'next/link' -export function EmailSignup(): JSX.Element { - const router = useRouter() +const SignUpForm = (): JSX.Element => { const [email, setEmail] = useState() const [password, setPassword] = useState() const [fullname, setFullname] = useState() const [username, setUsername] = useState() const [debouncedUsername, setDebouncedUsername] = useState() - const [errorMessage, setErrorMessage] = useState() - - useEffect(() => { - if (!router.isReady) return - const errorCode = parseErrorCodes(router.query) - const errorMsg = errorCode - ? formatMessage({ id: `error.${errorCode}` }) - : undefined - setErrorMessage(errorMsg) - }, [router.isReady, router.query]) const { isUsernameValid, usernameErrorMessage } = useValidateUsernameQuery({ username: debouncedUsername ?? '', @@ -46,174 +39,228 @@ export function EmailSignup(): JSX.Element { ) return ( -
- - - Sign Up - - - - Email - { - e.preventDefault() - setEmail(e.target.value) - }} - required - /> - - - - Password - setPassword(e.target.value)} - required - /> - - - - Full Name - setFullname(e.target.value)} - required - /> - - - - Username - - - {username && username.length > 0 && usernameErrorMessage && ( - - {usernameErrorMessage} - - )} - {isUsernameValid && ( - - Username is available. - - )} - - - {errorMessage && {errorMessage}} - + + + Email + { + e.preventDefault() + setEmail(e.target.value) + }} + required + /> + + + Password + setPassword(e.target.value)} + required + /> + + + Full Name + setFullname(e.target.value)} + required + /> + + + Username + + + {username && username.length > 0 && usernameErrorMessage && ( - Omnivore will send you daily tips for your first week as a new user. - If you don't like them you can unsubscribe. + {usernameErrorMessage} - - - - - - + )} + {isUsernameValid && ( - Already have an account?{' '} - - - Login instead - - + Username is available. - - - + )} +
+ ) +} + +type RecaptchaProps = { + setRecaptchaToken: (string) => void +} + +const Recaptcha = (props: RecaptchaProps): JSX.Element => { + return ( + <> + { + console.log('recaptcha: ', token) + props.setRecaptchaToken(token) + }} + /> + + ) +} + +export function EmailSignup(): JSX.Element { + const router = useRouter() + const recaptchaTokenRef = useRef(null) + const [errorMessage, setErrorMessage] = useState() + + useEffect(() => { + if (!router.isReady) return + const errorCode = parseErrorCodes(router.query) + const errorMsg = errorCode + ? formatMessage({ id: `error.${errorCode}` }) + : undefined + setErrorMessage(errorMsg) + }, [router.isReady, router.query]) + + return ( + <> +
+ + + Sign Up + + + + + { + if (recaptchaTokenRef.current) { + recaptchaTokenRef.current.value = token + } else { + console.log('error updating recaptcha token') + } + }} + /> + + + {errorMessage && ( + {errorMessage} + )} + + + Omnivore will send you daily tips for your first week as a new user. + If you don't like them you can unsubscribe. + + + + + + + + + Already have an account?{' '} + + + Login instead + + + + + +
+ ) } diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 07228c7f9..6cf984120 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -1,13 +1,13 @@ const ContentSecurityPolicy = ` default-src 'self'; base-uri 'self'; - connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://proxy-prod.omnivore-image-cache.app https://accounts.google.com https://proxy-demo.omnivore-image-cache.app https://storage.googleapis.com https://api.segment.io https://cdn.segment.com https://widget.intercom.io https://api-iam.intercom.io https://static.intercomassets.com https://downloads.intercomcdn.com https://platform.twitter.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io wss://nexus-europe-websocket.intercom.io wss://nexus-australia-websocket.intercom.io https://uploads.intercomcdn.com https://tools.applemediaservices.com; + connect-src 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://proxy-prod.omnivore-image-cache.app https://accounts.google.com https://proxy-demo.omnivore-image-cache.app https://storage.googleapis.com https://widget.intercom.io https://api-iam.intercom.io https://static.intercomassets.com https://downloads.intercomcdn.com https://platform.twitter.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io wss://nexus-europe-websocket.intercom.io wss://nexus-australia-websocket.intercom.io https://uploads.intercomcdn.com https://tools.applemediaservices.com; font-src 'self' data: https://cdn.jsdelivr.net https://js.intercomcdn.com https://fonts.intercomcdn.com; form-action 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://getpocket.com/auth/authorize https://intercom.help https://api-iam.intercom.io https://api-iam.eu.intercom.io https://api-iam.au.intercom.io https://www.notion.so https://api.notion.com; frame-ancestors 'none'; - frame-src 'self' https://accounts.google.com https://platform.twitter.com https://www.youtube.com https://www.youtube-nocookie.com; + frame-src 'self' https://accounts.google.com https://platform.twitter.com https://www.youtube.com https://www.youtube-nocookie.com https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/; manifest-src 'self'; - script-src 'self' 'unsafe-inline' 'unsafe-eval' accounts.google.com https://widget.intercom.io https://js.intercomcdn.com https://platform.twitter.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://cdn.segment.com; + script-src 'self' 'unsafe-inline' 'unsafe-eval' accounts.google.com https://widget.intercom.io https://js.intercomcdn.com https://platform.twitter.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; style-src 'self' 'unsafe-inline' https://accounts.google.com https://cdnjs.cloudflare.com; img-src 'self' blob: data: https:; worker-src 'self' blob:; diff --git a/packages/web/package.json b/packages/web/package.json index 467f44d12..c10a0573c 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@floating-ui/react": "^0.26.9", + "@google-recaptcha/react": "^1.0.3", "@radix-ui/react-avatar": "^0.1.1", "@radix-ui/react-checkbox": "^0.1.5", "@radix-ui/react-dialog": "1.0.5", @@ -106,4 +107,4 @@ "volta": { "extends": "../../package.json" } -} \ No newline at end of file +} diff --git a/packages/web/pages/auth/email-signup.tsx b/packages/web/pages/auth/email-signup.tsx index 7c2d24d8d..3487ecc8c 100644 --- a/packages/web/pages/auth/email-signup.tsx +++ b/packages/web/pages/auth/email-signup.tsx @@ -1,15 +1,24 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' import { EmailSignup } from '../../components/templates/auth/EmailSignup' +import { GoogleReCaptchaProvider } from '@google-recaptcha/react' export default function EmailRegistrationPage(): JSX.Element { return ( <> - - - - -
+ + + + + + ) }