289289 <!-- reCAPTCHA Validation -->
290290 <div class =" form-group" >
291291 <div class =" recaptcha-container" >
292- <div v-if =" formState.isRecaptchaLoading" class =" recaptcha-loading" >
293- <div class =" loading-spinner" ></div >
294- <span >Validating reCAPTCHA...</span >
295- </div >
296- <div v-else-if =" formState.isRecaptchaValid" class =" recaptcha-success" >
297- ✅ reCAPTCHA verified
298- </div >
299- <div v-else class =" recaptcha-info" >
300- 🔒 This form is protected by reCAPTCHA
292+ <!-- reCAPTCHA v2 Widget will be rendered here -->
293+ <div id =" recaptcha-widget" class =" recaptcha-widget" ></div >
294+ <div v-if =" recaptchaError" class =" recaptcha-error" >
295+ ❌ {{ recaptchaError }}
301296 </div >
302297 </div >
303298 </div >
299+
300+ <!-- Debug Validation State -->
301+ <div
302+ class =" form-group"
303+ style ="
304+ background : rgba (255 , 255 , 0 , 0.1 );
305+ padding : 10px ;
306+ border-radius : 4px ;
307+ font-size : 12px ;
308+ "
309+ >
310+ <strong >🔍 Debug - Form Validation State:</strong ><br />
311+ Name: {{ contactForm.name.trim().length > 0 ? '✅' : '❌' }} ({{
312+ contactForm.name.trim().length
313+ }}/1)<br />
314+ Email: {{ /[^\s@]+@[^\s@]+\.[^\s@]+$/.test(contactForm.email) ? '✅' : '❌'
315+ }}<br />
316+ Message: {{ contactForm.message.trim().length >= 10 ? '✅' : '❌' }} ({{
317+ contactForm.message.trim().length
318+ }}/10)<br />
319+ reCAPTCHA: {{ formState.isRecaptchaValid ? '✅' : '❌' }}<br />
320+ <strong >Overall Valid: {{ isFormValid ? '✅' : '❌' }}</strong >
321+ </div >
304322 <button
305323 type =" submit"
306324 class =" submit-btn"
394412import emailjs from ' @emailjs/browser'
395413import { onMounted , reactive , ref , computed } from ' vue'
396414import TerminalInput from ' ./TerminalInput.vue'
397- import { getRecaptchaToken , verifyRecaptchaToken , isRecaptchaAvailable } from ' ../utils/recaptcha'
415+ import { initRecaptchaV2 , getRecaptchaV2Response , isRecaptchaV2Available } from ' ../utils/recaptcha'
398416
399417// EmailJS configuration
400418const EMAILJS_SERVICE_ID = ' involvex'
@@ -598,6 +616,11 @@ const formState = reactive({
598616 isRecaptchaLoading: false ,
599617})
600618
619+ // reCAPTCHA v2 state
620+ const recaptchaWidgetId = ref <string >(' ' )
621+ const recaptchaError = ref <string >(' ' )
622+ const recaptchaResponse = ref <string >(' ' )
623+
601624// Computed property for form validation
602625const isFormValid = computed (() => {
603626 return (
@@ -609,36 +632,6 @@ const isFormValid = computed(() => {
609632 )
610633})
611634
612- // reCAPTCHA validation function
613- const validateRecaptcha = async (): Promise <boolean > => {
614- if (! isRecaptchaAvailable ()) {
615- formState .errors .push (' reCAPTCHA is not available. Please refresh the page and try again.' )
616- return false
617- }
618-
619- formState .isRecaptchaLoading = true
620-
621- try {
622- const token = await getRecaptchaToken (' contact_form' )
623- const isValid = await verifyRecaptchaToken (token )
624-
625- formState .recaptchaToken = token
626- formState .isRecaptchaValid = isValid
627-
628- if (! isValid ) {
629- formState .errors .push (' reCAPTCHA validation failed. Please try again.' )
630- }
631-
632- return isValid
633- } catch (error ) {
634- console .error (' reCAPTCHA validation error:' , error )
635- formState .errors .push (' reCAPTCHA validation failed. Please try again.' )
636- return false
637- } finally {
638- formState .isRecaptchaLoading = false
639- }
640- }
641-
642635// Enhanced contact form submission with EmailJS integration and reCAPTCHA
643636const submitContact = async () => {
644637 formState .errors = []
@@ -660,11 +653,27 @@ const submitContact = async () => {
660653 formState .errors .push (' Message must be at least 10 characters long' )
661654 }
662655
663- // reCAPTCHA validation
664- const recaptchaValid = await validateRecaptcha ()
665- if (! recaptchaValid ) {
666- formState .isSubmitting = false
667- return
656+ // Get reCAPTCHA v2 response
657+ let recaptchaToken = ' '
658+ if (recaptchaWidgetId .value && isRecaptchaV2Available ()) {
659+ try {
660+ recaptchaToken = getRecaptchaV2Response (recaptchaWidgetId .value )
661+ if (! recaptchaToken ) {
662+ formState .errors .push (' Please complete the reCAPTCHA verification.' )
663+ formState .isSubmitting = false
664+ return
665+ }
666+ formState .recaptchaToken = recaptchaToken
667+ } catch (error ) {
668+ console .error (' ❌ Failed to get reCAPTCHA response:' , error )
669+ formState .errors .push (' reCAPTCHA verification failed. Please try again.' )
670+ formState .isSubmitting = false
671+ return
672+ }
673+ } else {
674+ // Fallback if reCAPTCHA is not available
675+ recaptchaToken = ' fallback_no_recaptcha'
676+ formState .recaptchaToken = recaptchaToken
668677 }
669678
670679 try {
@@ -675,6 +684,8 @@ const submitContact = async () => {
675684 discord_name: contactForm .discordname || ' Not provided' ,
676685 message: contactForm .message ,
677686 to_name: ' Involvex' ,
687+ // Include reCAPTCHA token for EmailJS validation
688+ ' g-recaptcha-response' : formState .recaptchaToken || ' fallback_no_recaptcha' ,
678689 }
679690
680691 // Send email using EmailJS
@@ -835,23 +846,25 @@ const showCommandOutput = (command: string): string => {
835846}
836847
837848// Lifecycle
838- onMounted (() => {
849+ onMounted (async () => {
839850 fetchProjects ()
840851
841- // Initialize reCAPTCHA validation status
842- if (isRecaptchaAvailable ()) {
843- // Perform initial reCAPTCHA validation
844- validateRecaptcha ()
845- .then ((isValid ) => {
846- if (isValid ) {
847- console .log (' reCAPTCHA validated successfully' )
848- } else {
849- console .log (' reCAPTCHA validation failed' )
850- }
851- })
852- .catch ((error ) => {
853- console .error (' Initial reCAPTCHA validation error:' , error )
854- })
852+ // Initialize reCAPTCHA v2 widget
853+ console .log (' 🚀 Initializing reCAPTCHA v2 on component mount...' )
854+ try {
855+ recaptchaWidgetId .value = await initRecaptchaV2 (' recaptcha-widget' , (response : string ) => {
856+ console .log (' ✅ reCAPTCHA v2 completed:' , response .substring (0 , 20 ) + ' ...' )
857+ recaptchaResponse .value = response
858+ formState .recaptchaToken = response
859+ formState .isRecaptchaValid = true
860+ recaptchaError .value = ' '
861+ })
862+ console .log (' ✅ reCAPTCHA v2 widget initialized successfully' )
863+ formState .isRecaptchaValid = true
864+ } catch (error ) {
865+ console .error (' ❌ reCAPTCHA v2 initialization failed:' , error )
866+ recaptchaError .value = ' Failed to load reCAPTCHA. Form will work without validation.'
867+ formState .isRecaptchaValid = true // Fallback to allow form submission
855868 }
856869})
857870
@@ -1557,11 +1570,35 @@ export default {
15571570
15581571 .recaptcha-container {
15591572 padding : 12px ;
1573+ display : flex ;
1574+ flex-direction : column ;
1575+ gap : 10px ;
1576+ }
1577+
1578+ .recaptcha-widget {
1579+ display : flex ;
1580+ justify-content : center ;
1581+ align-items : center ;
1582+ min-height : 80px ;
1583+ background : rgba (0 , 0 , 0 , 0.3 );
1584+ border-radius : 4px ;
1585+ border : 1px solid rgba (0 , 255 , 0 , 0.2 );
1586+ }
1587+
1588+ .recaptcha-error {
1589+ color : #ff6b6b ;
1590+ font-size : 12px ;
1591+ text-align : center ;
1592+ padding : 8px ;
1593+ background : rgba (255 , 107 , 107 , 0.1 );
1594+ border-radius : 4px ;
1595+ border : 1px solid rgba (255 , 107 , 107 , 0.3 );
15601596 }
15611597
15621598 .recaptcha-loading {
15631599 flex-direction : column ;
15641600 gap : 8px ;
1601+ text-align : center ;
15651602 }
15661603}
15671604
0 commit comments