diff --git a/api/src/graphql/events.sdl.ts b/api/src/graphql/events.sdl.ts index dcfa0e5..cc4fdf7 100644 --- a/api/src/graphql/events.sdl.ts +++ b/api/src/graphql/events.sdl.ts @@ -52,6 +52,7 @@ export const schema = gql` ownerEmail: String! title: String! responseConfig: ResponseConfig! + captcha: String! } input UpdateEventInput { diff --git a/api/src/services/events/events.ts b/api/src/services/events/events.ts index f7f3748..5a42345 100644 --- a/api/src/services/events/events.ts +++ b/api/src/services/events/events.ts @@ -96,8 +96,9 @@ export const eventByPreviewToken: QueryResolvers['eventByPreviewToken'] = async ({ previewToken }) => db.event.findUnique({ where: { previewToken } }) export const createEvent: MutationResolvers['createEvent'] = async ({ - input, + input: _input, }) => { + const { captcha, ...input } = _input validate(input.ownerEmail, 'email', { email: true }) validate(input.title, 'title', { custom: { diff --git a/app.config.ts b/app.config.ts index 023a632..053bd32 100644 --- a/app.config.ts +++ b/app.config.ts @@ -23,3 +23,5 @@ export const LOCALHOST = SITE_HOST.startsWith('localhost:') export const S3_REGION = process.env.S3_REGION export const S3_BUCKET = process.env.S3_BUCKET export const S3_NAMESPACE = process.env.S3_NAMESPACE + +export const RECAPTCHA_CLIENT_KEY = process.env.REDWOOD_ENV_RECAPTCHA_CLIENT_KEY diff --git a/web/package.json b/web/package.json index 297c7b3..0dd5c9f 100644 --- a/web/package.json +++ b/web/package.json @@ -26,11 +26,13 @@ "jotai": "^2.10.2", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-google-recaptcha": "^3.1.0", "react-select": "^5.8.2", "react-svg-spinners": "^0.3.1" }, "devDependencies": { "@redwoodjs/vite": "8.4.0", + "@types/react-google-recaptcha": "^2.1.9", "autoprefixer": "^10.4.20", "postcss": "^8.4.47", "postcss-loader": "^8.1.1", diff --git a/web/src/pages/NewEventPage/NewEventPage.tsx b/web/src/pages/NewEventPage/NewEventPage.tsx index 6161c96..a1d6374 100644 --- a/web/src/pages/NewEventPage/NewEventPage.tsx +++ b/web/src/pages/NewEventPage/NewEventPage.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' +import ReCAPTCHA from 'react-google-recaptcha' import { CreateEventMutation, CreateEventMutationVariables, @@ -18,6 +19,7 @@ import { import { navigate, routes } from '@redwoodjs/router' import { useMutation } from '@redwoodjs/web' +import { RECAPTCHA_CLIENT_KEY } from 'src/app.config' import FormField from 'src/components/FormField/FormField' import PageHead from 'src/components/PageHead/PageHead' import Typ from 'src/components/Typ/Typ' @@ -67,6 +69,7 @@ const NewEventPage = () => { const { formState } = formMethods const [redirecting, setRedirecting] = useState(false) + const [captcha, setCaptcha] = useState(null) const [create, { loading, error }] = useMutation< CreateEventMutation, @@ -89,7 +92,9 @@ const NewEventPage = () => {
create({ variables: { input } })} + onSubmit={(input: FormValues) => + create({ variables: { input: { ...input, captcha } } }) + } > @@ -135,9 +140,11 @@ const NewEventPage = () => { + + {loading || redirecting ? 'Creating...' : 'Create New Event'} diff --git a/yarn.lock b/yarn.lock index a59cdd4..2bae148 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10236,6 +10236,15 @@ __metadata: languageName: node linkType: hard +"@types/react-google-recaptcha@npm:^2.1.9": + version: 2.1.9 + resolution: "@types/react-google-recaptcha@npm:2.1.9" + dependencies: + "@types/react": "*" + checksum: 12b1806ef4083524cf108fb7c83f7438feb5746e672af2589a2645de26d2ac1ed0f4b687a1e7d482f51a92a75850c074a8696396c1024a3f80e8902a780d4f95 + languageName: node + linkType: hard + "@types/react-transition-group@npm:^4.4.0": version: 4.4.11 resolution: "@types/react-transition-group@npm:4.4.11" @@ -16894,7 +16903,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -21809,7 +21818,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.0, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -22109,6 +22118,18 @@ __metadata: languageName: node linkType: hard +"react-async-script@npm:^1.2.0": + version: 1.2.0 + resolution: "react-async-script@npm:1.2.0" + dependencies: + hoist-non-react-statics: ^3.3.0 + prop-types: ^15.5.0 + peerDependencies: + react: ">=16.4.1" + checksum: 89450912110c380abc08258ce17d2fb18d31d6b7179a74f6bc504c0761a4ca271edb671e402fa8e5ea4250b5c17fa953af80a9f1c4ebb26c9e81caee8476c903 + languageName: node + linkType: hard + "react-colorful@npm:^5.1.2": version: 5.6.1 resolution: "react-colorful@npm:5.6.1" @@ -22179,6 +22200,18 @@ __metadata: languageName: node linkType: hard +"react-google-recaptcha@npm:^3.1.0": + version: 3.1.0 + resolution: "react-google-recaptcha@npm:3.1.0" + dependencies: + prop-types: ^15.5.0 + react-async-script: ^1.2.0 + peerDependencies: + react: ">=16.4.1" + checksum: 5ecaa6b88f238defd939012cb2671b4cbda59fe03f059158994b8c5215db482412e905eae6a67d23cef220d77cfe0430ab91ce7849d476515cda72f3a8a0a746 + languageName: node + linkType: hard + "react-helmet-async@npm:2.0.5": version: 2.0.5 resolution: "react-helmet-async@npm:2.0.5" @@ -26035,6 +26068,7 @@ __metadata: "@redwoodjs/web": 8.4.0 "@sentry/browser": 7 "@sentry/react": 7 + "@types/react-google-recaptcha": ^2.1.9 "@vermaysha/discord-webhook": ^1.4.0 autoprefixer: ^10.4.20 bulma: <1 @@ -26046,6 +26080,7 @@ __metadata: postcss-loader: ^8.1.1 react: ^18.3.1 react-dom: ^18.3.1 + react-google-recaptcha: ^3.1.0 react-select: ^5.8.2 react-svg-spinners: ^0.3.1 sass: ^1.80.6