diff --git a/.gitlab-ci-template.yml b/.gitlab-ci-template.yml index a645d9e..89722a3 100644 --- a/.gitlab-ci-template.yml +++ b/.gitlab-ci-template.yml @@ -18,27 +18,52 @@ script: - !reference [ .image-tag-template, script ] - | - echo "Building Docker image for $IMAGE_NAME in $WORKDIR_PATH" - cd $WORKDIR_PATH + echo "Building Docker image for service: $IMAGE_NAME" + 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" - 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 - echo "Pushing Docker image $DOCKER_IMAGE:$TAG" + echo "Pushing Docker image: $DOCKER_IMAGE:$TAG" docker push $DOCKER_IMAGE:$TAG - # After pushing the image - DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $DOCKER_IMAGE:$TAG | cut -d '@' -f2) + echo "Inspecting image digest..." + DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$DOCKER_IMAGE:$TAG" | cut -d '@' -f2) echo "$DIGEST" > "$CI_PROJECT_DIR/digest-${IMAGE_NAME}.txt" echo "Digest for $IMAGE_NAME: $DIGEST" else - echo "Skipping push for non-dev/non-production branch: $TAG" + echo "Skipping push (branch/tag '$TAG' is not dev/production/pipeline)" fi artifacts: paths: diff --git a/frontend/components/Contact/Section/ContactFormSection.tsx b/frontend/components/Contact/Section/ContactFormSection.tsx index c2902fe..0d2f299 100644 --- a/frontend/components/Contact/Section/ContactFormSection.tsx +++ b/frontend/components/Contact/Section/ContactFormSection.tsx @@ -2,7 +2,8 @@ import React, {useState} from 'react' 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 [form, setForm] = useState({ @@ -30,6 +31,12 @@ const ContactFormSection = () => { setLoading(true) setError('') + if (!captchaToken) { + setError('Bitte bestätige, dass du kein Roboter bist.') + setLoading(false) + return + } + const res = await fetch('/api/contact', { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -38,10 +45,21 @@ const ContactFormSection = () => { if (res.ok) { setSubmitted(true) - setForm({name: '', email: '', company: '', phone: '', website: '', message: ''}) + setForm({ + name: '', + email: '', + company: '', + phone: '', + website: '', + message: '', + }) + setCaptchaToken('') } else { 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) @@ -142,7 +160,9 @@ const ContactFormSection = () => { viewport={{once: true}} transition={{duration: 0.5, delay: 0.6}} > - Deine Nachricht * + + Deine Nachricht * + { /> - {/* - - - - */} + + setCaptchaToken(token || '')} + /> + {error && ( @@ -179,13 +200,9 @@ const ContactFormSection = () => { viewport={{once: true}} transition={{duration: 0.5, delay: 0.8}} > - + {loading ? 'Sende...' : '📩 Nachricht senden'} - + )} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 077ae19..b606bff 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,6 +25,7 @@ "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-google-recaptcha": "^3.1.0", "react-icons": "^5.4.0", "react-scroll": "^1.9.3", "react-simple-typewriter": "^5.0.1", @@ -43,6 +44,7 @@ "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-google-recaptcha": "^2.1.9", "@types/react-scroll": "^1.8.10", "@types/react-vertical-timeline-component": "^3.3.6", "autoprefixer": "^10.4.21", @@ -2104,6 +2106,16 @@ "@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": { "version": "1.8.10", "resolved": "https://registry.npmjs.org/@types/react-scroll/-/react-scroll-1.8.10.tgz", @@ -4715,6 +4727,15 @@ "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": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6433,6 +6454,19 @@ "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": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -6445,6 +6479,19 @@ "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": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7860d8f..13b19a9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-google-recaptcha": "^3.1.0", "react-icons": "^5.4.0", "react-scroll": "^1.9.3", "react-simple-typewriter": "^5.0.1", @@ -44,6 +45,7 @@ "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-google-recaptcha": "^2.1.9", "@types/react-scroll": "^1.8.10", "@types/react-vertical-timeline-component": "^3.3.6", "autoprefixer": "^10.4.21",