protonemedia / laravel-splade

šŸ’« The magic of Inertia.js with the simplicity of Blade šŸ’« - Splade provides a super easy way to build Single Page Applications (SPA) using standard Laravel Blade templates, and sparkle it to make it interactive. All without ever leaving Blade.
https://splade.dev
MIT License
1.47k stars 111 forks source link

FriendlyCaptcha Solution not sending with x-splade-form #631

Closed p4rad0xus closed 6 months ago

p4rad0xus commented 6 months ago

Description:

In a x-splade-form component I want to youse FriendlyCaptcha to protect the form for spam. To do so I created a Vue component and place this in the x-splade-form.

The captcha will automatically create a input field named frc-captcha-solution like descripted in the documentation. If the form is submitted the field frc-captcha-solution is not submitted in the request. If I used a normal form tag the field and the value is submitted.

Example of the whole form in the browser:

<form data-splade-id="..." method="POST" action="...">
    <fieldset class="flex flex-col space-y-4">
        <div class="flex flex-col md:flex-row md:items-start space-y-4 md:space-y-0 md:space-x-4">
            <div class="md:grow"><label class="block"><span class="block mb-1 text-gray-700 font-sans"> Name </span>
                    <div class="flex rounded-md border border-gray-300 shadow-sm"><input
                                class="block w-full border-0 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 disabled:opacity-50 disabled:bg-gray-50 disabled:cursor-not-allowed rounded-md"
                                name="name" type="text" data-validation-key="name"></div>
                </label><!--v-if--></div>
            <div class="md:grow"><label class="block"><span class="block mb-1 text-gray-700 font-sans"> E-Mail <span
                                aria-hidden="true" class="text-red-600" title="This field is required">*</span></span>
                    <div class="flex rounded-md border border-gray-300 shadow-sm"><input
                                class="block w-full border-0 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 disabled:opacity-50 disabled:bg-gray-50 disabled:cursor-not-allowed rounded-md"
                                name="email" type="email" data-validation-key="email" required=""></div>
                </label><!--v-if--></div>
        </div>
        <div class=""><label class="block"><span class="block mb-1 text-gray-700 font-sans"> Subject <span
                            aria-hidden="true" class="text-red-600" title="This field is required">*</span></span>
                <div class="flex rounded-md border border-gray-300 shadow-sm"><input
                            class="block w-full border-0 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 disabled:opacity-50 disabled:bg-gray-50 disabled:cursor-not-allowed rounded-md"
                            name="subject" type="text" data-validation-key="subject" required=""></div>
            </label><!--v-if--></div>
        <div><label class="block"><span class="block mb-1 text-gray-700 font-sans"> Message <span aria-hidden="true"
                                                                                                    class="text-red-600"
                                                                                                    title="This field is required">*</span></span><textarea
                        name="message" data-validation-key="message"
                        class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 disabled:opacity-50"
                        required=""
                        style="overflow: hidden; overflow-wrap: break-word; resize: none; height: 66px;"></textarea></label>
            <!--v-if--></div>

        <div class="frc-captcha" data-sitekey="...">
            <div class="frc-container frc-success">
                <svg class="frc-icon" role="img" xmlns="http://www.w3.org/2000/svg" height="32" width="32"
                     viewBox="0 0 24 24"><title>Automatic spam check completed</title>
                    <path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"></path>
                </svg>
                <div class="frc-content">
                    <span class="frc-text" data-debug="1s (16938K/s)">I am human</span>
                </div>
            </div>
            <span class="frc-banner"><a lang="en" href="https://friendlycaptcha.com/" rel="noopener" target="_blank"><b>Friendly</b>Captcha ā‡—</a></span>
            <input name="frc-captcha-solution" class="frc-captcha-solution" type="hidden"
                   value="...">
        </div>

        <div class="">
            <button type="submit"
                    class="border rounded-md shadow-sm font-bold py-2 px-4 focus:outline-none focus:ring focus:ring-opacity-50 bg-emerald-400 hover:bg-emerald-500">
                <div class="flex flex-row items-center justify-center"><!--v-if--><span class=""> Send </span></div>
            </button>
        </div>
    </fieldset>
</form>

Steps To Reproduce Issue:

Follow the installation guide on the documentation

Use the Option B to install the code with npm npm install --save friendly-challenge

Create a Vue component (see Widget-API):

<template>
  <div ref="container" class="frc-captcha" data-sitekey="YOUR_SITE_KEY"></div>
</template>

<script lang="ts" setup>
import { WidgetInstance } from "friendly-challenge";
import { ref, onMounted, onUnmounted } from "vue";

const container = ref();
const widget = ref();

const doneCallback = (solution) => {
  console.log('Captcha was solved. The form can be submitted.');
  console.log(solution);
}

const errorCallback = (err) => {
  console.log('There was an error when trying to solve the Captcha.');
  console.log(err);
}

onMounted(() => {
  if (container.value) {
    widget.value = new WidgetInstance(container.value, {
      startMode: "auto",
      doneCallback,
      errorCallback
    });
  }
});

onUnmounted(() => {
  if (widget.value) {
    widget.value.destroy();
  }
});
</script>

Configure the app.js file:

...
import { createApp, defineAsyncComponent } from "vue/dist/vue.esm-bundler.js";
...

createApp({
    render: renderSpladeApp({ el })
})
    .use(SpladePlugin, {
        "max_keep_alive": 10,
        "transform_anchors": false,
        "progress_bar": true
    })
    .component('FriendlyCaptcha', defineAsyncComponent(() => import("./Components/FriendlyCaptcha.vue")))
    .mount(el);

Use the Vue Component in Blade-File:

<x-splade-form action="{{ route('contact.send') }}"
               class="flex flex-col space-y-4">
    <div class="flex flex-col md:flex-row md:items-start space-y-4 md:space-y-0 md:space-x-4">
        <x-splade-input name="name"
                        label="Name"
                        class="md:grow" />
        <x-splade-input name="email"
                        type="email"
                        label="E-Mail"
                        class="md:grow"
                        required />
    </div>
    <x-splade-input name="subject"
                    label="Subject"
                    required />
    <x-splade-textarea name="message"
                       label="Message"
                       autosize
                       required />
    <FriendlyCaptcha />
    <x-splade-submit label="Send"
                     class="bg-emerald-400 hover:bg-emerald-500" />
</x-splade-form>