Replace hCaptcha with Google reCAPTCHA in contact page
This commit is contained in:
@@ -18,27 +18,52 @@
|
|||||||
script:
|
script:
|
||||||
- !reference [ .image-tag-template, script ]
|
- !reference [ .image-tag-template, script ]
|
||||||
- |
|
- |
|
||||||
echo "Building Docker image for $IMAGE_NAME in $WORKDIR_PATH"
|
echo "Building Docker image for service: $IMAGE_NAME"
|
||||||
cd $WORKDIR_PATH
|
echo "Switching to workdir: $WORKDIR_PATH"
|
||||||
|
cd "$WORKDIR_PATH"
|
||||||
|
|
||||||
|
echo "Image Tag: $TAG"
|
||||||
|
echo "Docker Image: $DOCKER_IMAGE:$TAG"
|
||||||
|
echo "Dockerfile: $DOCKERFILE_PATH"
|
||||||
|
|
||||||
BUILD_ARGS="--build-arg IMAGE_TAG=$TAG"
|
BUILD_ARGS="--build-arg IMAGE_TAG=$TAG"
|
||||||
if [ -n "$COMMON_IMAGE" ]; then BUILD_ARGS="$BUILD_ARGS --build-arg COMMON_IMAGE=$COMMON_IMAGE:$TAG"; fi
|
|
||||||
if [ -n "$BUILD_FOLDER" ]; then BUILD_ARGS="$BUILD_ARGS --build-arg BUILD_FOLDER=$BUILD_FOLDER"; fi
|
|
||||||
if [ -n "$IMAGE_NAME" ]; then BUILD_ARGS="$BUILD_ARGS --build-arg IMAGE_NAME=$IMAGE_NAME"; fi
|
|
||||||
if [ -n "$MAIN_CLASS" ]; then BUILD_ARGS="$BUILD_ARGS --build-arg MAIN_CLASS=$MAIN_CLASS"; fi
|
|
||||||
|
|
||||||
docker build $BUILD_ARGS -t $DOCKER_IMAGE:$TAG -f $DOCKERFILE_PATH .
|
if [ -n "$COMMON_IMAGE" ]; then
|
||||||
|
COMMON_IMAGE="${COMMON_IMAGE%:}" # Strip trailing colon if any
|
||||||
|
echo "Using COMMON_IMAGE: $COMMON_IMAGE:$TAG"
|
||||||
|
BUILD_ARGS="$BUILD_ARGS --build-arg COMMON_IMAGE=$COMMON_IMAGE:$TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$BUILD_FOLDER" ]; then
|
||||||
|
echo "BUILD_FOLDER: $BUILD_FOLDER"
|
||||||
|
BUILD_ARGS="$BUILD_ARGS --build-arg BUILD_FOLDER=$BUILD_FOLDER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$IMAGE_NAME" ]; then
|
||||||
|
echo "IMAGE_NAME: $IMAGE_NAME"
|
||||||
|
BUILD_ARGS="$BUILD_ARGS --build-arg IMAGE_NAME=$IMAGE_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$MAIN_CLASS" ]; then
|
||||||
|
echo "MAIN_CLASS: $MAIN_CLASS"
|
||||||
|
BUILD_ARGS="$BUILD_ARGS --build-arg MAIN_CLASS=$MAIN_CLASS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Final docker build command:"
|
||||||
|
echo "docker build $BUILD_ARGS -t $DOCKER_IMAGE:$TAG -f $DOCKERFILE_PATH ."
|
||||||
|
|
||||||
|
docker build $BUILD_ARGS -t $DOCKER_IMAGE:$TAG -f "$DOCKERFILE_PATH" .
|
||||||
|
|
||||||
if [[ "$TAG" == "dev" || "$TAG" == "production" || "$TAG" == "pipeline" ]]; then
|
if [[ "$TAG" == "dev" || "$TAG" == "production" || "$TAG" == "pipeline" ]]; then
|
||||||
echo "Pushing Docker image $DOCKER_IMAGE:$TAG"
|
echo "Pushing Docker image: $DOCKER_IMAGE:$TAG"
|
||||||
docker push $DOCKER_IMAGE:$TAG
|
docker push $DOCKER_IMAGE:$TAG
|
||||||
|
|
||||||
# After pushing the image
|
echo "Inspecting image digest..."
|
||||||
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $DOCKER_IMAGE:$TAG | cut -d '@' -f2)
|
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$DOCKER_IMAGE:$TAG" | cut -d '@' -f2)
|
||||||
echo "$DIGEST" > "$CI_PROJECT_DIR/digest-${IMAGE_NAME}.txt"
|
echo "$DIGEST" > "$CI_PROJECT_DIR/digest-${IMAGE_NAME}.txt"
|
||||||
echo "Digest for $IMAGE_NAME: $DIGEST"
|
echo "Digest for $IMAGE_NAME: $DIGEST"
|
||||||
else
|
else
|
||||||
echo "Skipping push for non-dev/non-production branch: $TAG"
|
echo "Skipping push (branch/tag '$TAG' is not dev/production/pipeline)"
|
||||||
fi
|
fi
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {motion} from 'framer-motion'
|
import {motion} from 'framer-motion'
|
||||||
// import HCaptcha from '@hcaptcha/react-hcaptcha'
|
import {Button} from '@/components/ui/button'
|
||||||
|
import ReCAPTCHA from 'react-google-recaptcha'
|
||||||
|
|
||||||
const ContactFormSection = () => {
|
const ContactFormSection = () => {
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
@@ -30,6 +31,12 @@ const ContactFormSection = () => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError('')
|
setError('')
|
||||||
|
|
||||||
|
if (!captchaToken) {
|
||||||
|
setError('Bitte bestätige, dass du kein Roboter bist.')
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch('/api/contact', {
|
const res = await fetch('/api/contact', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
@@ -38,10 +45,21 @@ const ContactFormSection = () => {
|
|||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setSubmitted(true)
|
setSubmitted(true)
|
||||||
setForm({name: '', email: '', company: '', phone: '', website: '', message: ''})
|
setForm({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
company: '',
|
||||||
|
phone: '',
|
||||||
|
website: '',
|
||||||
|
message: '',
|
||||||
|
})
|
||||||
|
setCaptchaToken('')
|
||||||
} else {
|
} else {
|
||||||
const resJson = await res.json()
|
const resJson = await res.json()
|
||||||
setError(resJson?.error || 'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.')
|
setError(
|
||||||
|
resJson?.error ||
|
||||||
|
'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -142,7 +160,9 @@ const ContactFormSection = () => {
|
|||||||
viewport={{once: true}}
|
viewport={{once: true}}
|
||||||
transition={{duration: 0.5, delay: 0.6}}
|
transition={{duration: 0.5, delay: 0.6}}
|
||||||
>
|
>
|
||||||
<label className="block font-semibold mb-1 text-foreground">Deine Nachricht *</label>
|
<label className="block font-semibold mb-1 text-foreground">
|
||||||
|
Deine Nachricht *
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="message"
|
name="message"
|
||||||
rows={4}
|
rows={4}
|
||||||
@@ -154,7 +174,6 @@ const ContactFormSection = () => {
|
|||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/*
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="pt-2"
|
className="pt-2"
|
||||||
initial={{opacity: 0, y: 10}}
|
initial={{opacity: 0, y: 10}}
|
||||||
@@ -162,9 +181,11 @@ const ContactFormSection = () => {
|
|||||||
viewport={{once: true}}
|
viewport={{once: true}}
|
||||||
transition={{duration: 0.5, delay: 0.7}}
|
transition={{duration: 0.5, delay: 0.7}}
|
||||||
>
|
>
|
||||||
<HCaptcha sitekey={hCaptchaSiteKey} onVerify={setCaptchaToken} />
|
<ReCAPTCHA
|
||||||
|
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!}
|
||||||
|
onChange={(token) => setCaptchaToken(token || '')}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
*/}
|
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-red-600 font-medium pt-2">
|
<div className="text-red-600 font-medium pt-2">
|
||||||
@@ -179,13 +200,9 @@ const ContactFormSection = () => {
|
|||||||
viewport={{once: true}}
|
viewport={{once: true}}
|
||||||
transition={{duration: 0.5, delay: 0.8}}
|
transition={{duration: 0.5, delay: 0.8}}
|
||||||
>
|
>
|
||||||
<button
|
<Button type="submit" disabled={loading} size="lg">
|
||||||
type="submit"
|
|
||||||
disabled={loading}
|
|
||||||
className="px-6 py-3 bg-primary text-white text-sm sm:text-base font-semibold rounded-md shadow hover:bg-primary/90 transition-all disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{loading ? 'Sende...' : '📩 Nachricht senden'}
|
{loading ? 'Sende...' : '📩 Nachricht senden'}
|
||||||
</button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
47
frontend/package-lock.json
generated
47
frontend/package-lock.json
generated
@@ -25,6 +25,7 @@
|
|||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-scroll": "^1.9.3",
|
"react-scroll": "^1.9.3",
|
||||||
"react-simple-typewriter": "^5.0.1",
|
"react-simple-typewriter": "^5.0.1",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
"@types/react-google-recaptcha": "^2.1.9",
|
||||||
"@types/react-scroll": "^1.8.10",
|
"@types/react-scroll": "^1.8.10",
|
||||||
"@types/react-vertical-timeline-component": "^3.3.6",
|
"@types/react-vertical-timeline-component": "^3.3.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
@@ -2104,6 +2106,16 @@
|
|||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-google-recaptcha": {
|
||||||
|
"version": "2.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz",
|
||||||
|
"integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-scroll": {
|
"node_modules/@types/react-scroll": {
|
||||||
"version": "1.8.10",
|
"version": "1.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-scroll/-/react-scroll-1.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-scroll/-/react-scroll-1.8.10.tgz",
|
||||||
@@ -4715,6 +4727,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -6433,6 +6454,19 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-async-script": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hoist-non-react-statics": "^3.3.0",
|
||||||
|
"prop-types": "^15.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
@@ -6445,6 +6479,19 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-google-recaptcha": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.5.0",
|
||||||
|
"react-async-script": "^1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-scroll": "^1.9.3",
|
"react-scroll": "^1.9.3",
|
||||||
"react-simple-typewriter": "^5.0.1",
|
"react-simple-typewriter": "^5.0.1",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
"@types/react-google-recaptcha": "^2.1.9",
|
||||||
"@types/react-scroll": "^1.8.10",
|
"@types/react-scroll": "^1.8.10",
|
||||||
"@types/react-vertical-timeline-component": "^3.3.6",
|
"@types/react-vertical-timeline-component": "^3.3.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
|||||||
Reference in New Issue
Block a user