elic-dev / laravel-math-captcha

A simple math captcha for Laravel form validation.
MIT License
12 stars 9 forks source link

captcha reset with delay #8

Open schel4ok opened 2 years ago

schel4ok commented 2 years ago

I use your captcha in ajax form. All is working, except that after successfull POST request I still see old captcha and if I try to send message again with the same captcha I got validation error. It is changed only when I refresh the page after successfull POST request. What is wrong? And how to make button for manual captcha reset?

blade view

  <form method="POST" action="/modalform" method="POST" @submit.prevent="submitData()">
    @csrf

  <div class="bg-white">
    <div class="modalheader flex place-items-center text-center border-b cursor-pointer text-lg leading-6 font-medium text-gray-900">
        <h3 class="p-2 hover:bg-blue-500 hover:text-white"    
            @click="$dispatch('callback')"
            :class="callback ? 'bg-blue-500 text-white' : ''"
            >
            Перезвоните мне
        </h3>
        <h3 class="p-2 hover:bg-blue-500 hover:text-white"    
            @click="$dispatch('zamer')"
            :class="zamer ? 'bg-blue-500 text-white' : ''"
            >
            Записаться на замер
        </h3>
        <h3 class="p-2 hover:bg-blue-500 hover:text-white"    
            @click="$dispatch('eskiz')"
            :class="eskiz ? 'bg-blue-500 text-white' : ''"
            >
            Отправить эскиз
        </h3>
        <div class="p-2 place-self-stretch hover:bg-blue-500 hover:text-white" @click="closeModal()" >
            <span class="text-3xl">&times;</span>
        </div>
    </div>

    <div class="modalbody flex items-center w-full h-full p-5" 
        x-show="sent"
        x-text="message"
        x-transition:enter="transition ease-out duration-500"
        x-transition:enter-start="opacity-0 scale-90"
        x-transition:enter-end="opacity-100 scale-100"
        x-transition:leave="transition ease-in duration-200"
        x-transition:leave-start="opacity-100 "
        x-transition:leave-end="opacity-0 "
        >
    </div>

    <div class="modalbody flex items-start flex-wrap p-5" 
        x-show="!sent"
        x-transition:enter="transition ease-out duration-500"
        x-transition:enter-start="opacity-0 scale-90"
        x-transition:enter-end="opacity-100 scale-100"
        x-transition:leave="transition ease-in duration-200"
        >

      <div class="text-left w-full">

        <div class="mt-2 grid grid-cols-2 gap-x-4 gap-y-2 mb-2">

            <!-- Name --> 
            <div class="name" 
                :class="errorData.name ? 'text-red-500' : ''" 
                >
                <x-modules.label for="name" :value="__('auth.user.name')" />
                <div class="relative text-gray-400 focus-within:text-gray-800">
                    <div class="absolute flex border border-transparent left-0 top-0 h-full w-10" >
                        <x-modules.svg type="user-solid" class="flex items-center justify-center rounded-l bg-gray-100 h-full w-full px-0.5"/>
                    </div>
                    <x-modules.input id="name" class="block w-full pl-12" type="text" name="name" :value="old('name')" x-model="formData.name" placeholder="Введите имя" autofocus />
                </div>
                <span x-text="errorData.name" class="text-red-500 text-xs"> </span>
            </div>

            <!-- Phone -->
            <div class="phone" 
                :class="errorData.phone ? 'text-red-500' : ''"
                    >
                <x-modules.label for="phone" :value="__('auth.user.phone')" />
                <div class="relative text-gray-400 focus-within:text-gray-800">
                    <div class="absolute flex border border-transparent left-0 top-0 h-full w-10 ">
                        <x-modules.svg type="phone-ringing-outline" class="flex items-center justify-center rounded-l bg-gray-100 h-full w-full px-0.5"/>
                    </div>
                    <x-modules.input id="phone" class="block w-full pl-12" type="text" name="phone" :value="old('phone')" x-model="formData.phone" placeholder="Введите телефон" required autofocus />
                </div>
                <span x-text="errorData.phone" class="text-red-500 text-xs"> </span>
            </div>

            <!-- Email Address -->
            <div class="email" 
                x-show="zamer || eskiz" 
                :class="errorData.email ? 'text-red-500' : ''" 
                    >                
                <x-modules.label for="email" :value="__('email')" />
                <div class="relative text-gray-400 focus-within:text-gray-800">
                    <div class="absolute flex border border-transparent left-0 top-0 h-full w-10 ">
                        <x-modules.svg type="envelope-outline" class="flex items-center justify-center rounded-l bg-gray-100 h-full w-full px-0.5"/>
                    </div>
                    <x-modules.input id="email" class="block w-full pl-12" type="email" name="email" :value="old('email')" x-model="formData.email"  autofocus />
                </div>
                <span x-text="errorData.email" class="text-red-500 text-xs"> </span>
            </div>

            <!-- Address -->
            <div class="address" 
                x-show="zamer || eskiz" 
                :class="errorData.address ? 'text-red-500' : ''"
                    >
                <x-modules.label for="address" :value="__('auth.user.address')" />
                <div class="relative text-gray-400 focus-within:text-gray-800">
                    <div class="absolute flex border border-transparent left-0 top-0 h-full w-10 ">
                        <x-modules.svg type="facade" class="flex items-center justify-center rounded-l bg-gray-100 h-full w-full px-0.5"/>
                    </div>
                    <x-modules.input id="address" class="block w-full pl-12" type="text" name="address" :value="old('address')" x-model="formData.address" autofocus />
                </div>
                <span x-text="errorData.address" class="text-red-500 text-xs"> </span>
            </div>

            <!-- Upload field -->
            <div class="upload" x-show="eskiz">
                <label class="flex items-center justify-evenly p-2 bg-white text-gray-700 rounded-lg shadow-lg border border-gray-300 cursor-pointer hover:bg-blue-500 hover:text-white">
                    <x-modules.svg type="upload" class="w-8 h-8"/>
                    <span>Выберите файл</span>
                    <input type="file" class="hidden" multiple />
                </label>
            </div>

        </div>

        <!-- Message -->
        <div class="message">
            <x-modules.label for="message" :value="__('auth.user.message')" />
            <x-modules.textarea rows="2" id="message" class="block w-full" name="message" x-model="formData.message" placeholder="Кратко опишите ваш вопрос"/></textarea>
            <span x-text="errorData.message" class="text-red-500 text-xs"> </span>
        </div>

        <div class="mathcaptcha" :class="errorData.mathcaptcha ? 'text-red-500' : ''">
            <label class="" for="mathcaptcha">Введите результат функции:  {{ app('mathcaptcha')->label() }}</label>
            <div class="relative text-gray-400 focus-within:text-gray-800">
                <div class="absolute flex border border-transparent left-0 top-0 h-full w-10 ">
                    <x-modules.svg type="refresh" class="flex items-center justify-center rounded-l bg-gray-100 h-full w-full px-0.5" id="reload"/>
                </div>
                {!! app('mathcaptcha')->input(['class' => 'appearance-none rounded-md shadow-sm border-gray-300 placeholder-gray-400 focus:border-sky-500 focus:ring-1 focus:ring-sky-500 focus:outline-none valid:border-green-500 invalid:border-red-500 block w-full pl-12', 'id' => 'mathcaptcha', 'type' => 'text', 'name' => 'mathcaptcha', 'x-model' => 'formData.mathcaptcha']) !!}
            </div>
            <span x-text="errorData.mathcaptcha" class="text-red-500 text-xs"> </span>
        </div>   

      </div>
    </div>

    <div class="modalfooter bg-gray-50 px-4 py-3 sm:px-6 flex justify-between ">
      <x-modules.button text="Отмена" style="black-outline" class="px-4" @click.prevent="closeModal()" />
      <x-modules.button x-text="buttonLabel" style="blue-solid" class="px-4" @click.prevent="submitData()" />
    </div>

  </div>

  </form>

<script>

    function topbar() {
        return {
            mailTooltip: false,
            instagramTooltip: false,
            openModal: false,
            callback: true,
            zamer: false,
            eskiz: false,

            formData: {
              name: '',
              phone: '',
              email: '',
              address: '',
              message: '',
              mathcaptcha: '',
              _token: '{{ csrf_token() }}'
            },

            message: '',
            responseData: [],
            errorStates: {
              name: false,
              phone: false,
              email: false,
              address: false,
              message: false,
              mathcaptcha: false
            },
            errorData: [],
            loading: false,
            sent: false,
            buttonLabel: 'Отправить',

            resetFields() {
                this.formData.name = '',
                this.formData.phone = '',
                this.formData.email = '',
                this.formData.address = '',
                this.formData.message = '',
                this.formData.mathcaptcha = ''
            },

            closeModal() {
                this.openModal = false;
                this.callback = false;
                this.zamer = false;
                this.eskiz = false;
            },

            submitData() {

                axios.post('/modalform', this.formData)
                .then( (response) => {
                    this.buttonLabel = 'Отправляем...';
                    this.loading = true;
                    console.log(response);
                    this.resetFields();
                    this.sent = true;
                    this.message = 'Сообщение успешно отправлено!';
                    this.responseData = response.data;
                })

                .then( () => {
                    this.loading = false;
                    this.sent = false;
                    this.closeModal();
                    this.buttonLabel = 'Отправить';
                    this.message = '';
                })

                .catch( (error) => {
                    console.log(error);
                    this.message = 'Ooops! Что-то пошло не так!'
                    this.errorData = error.response.data.errors;
                    this.isErrorName();
                });
            },

            isErrorName() {
                if (error.response.data.errors.name === 'undefined') {
                    this.errorStates.name = false;
                } else {
                    this.errorStates.name = true;
                }
            },

        }

    }

</script>

routes/web.php

Route::post('/modalform',    'MainController@modalform')->name('modalform');

app/Http/Controllers/MainController.php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Mail\Modalform;
use App\Http\Requests\ModalformRequest;

class MainController extends Controller
{

    public function modalform(ModalformRequest $request) {

        Mail::to( config('mail.to.address') )->send(new Modalform());
        app('mathcaptcha')->reset();

        return response()->json([
            'status'            => 'success',
            'messageHeader'     => 'Ваш вопрос отправлен!', 
            'messageContent'    => 'В ближайшее время мы свяжемся с вами.',
            ]);

    }

}

app/Mail/Modalform.php

use Illuminate\Http\Request;
use App\Http\Requests\ModalformRequest;
use Illuminate\Mail\Mailable;

class Modalform extends Mailable
{

    public function build(ModalformRequest $request)
    {
        $this->from( config('mail.from.address') )
             ->view('emails.modalform')
             ->withRequest($request);
    }
}

App/Http/Requests/ModalformRequest.php

<?php

use Illuminate\Foundation\Http\FormRequest;

class ModalformRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name'      => 'bail|required|string|between:2,20',        
            'phone'     => 'bail|required',
            'email'     => 'bail|email:rfc|nullable',
            'address'   => 'bail|string|max:100|nullable',
            'message'   => 'bail|string|max:500|nullable',
            'mathcaptcha'   => 'required|mathcaptcha',
        ];
    }
}
L1lle commented 2 years ago

Hi,

I can see you are calling the reset function. But I can not see, how you update your UI with a new captcha.

I guess your "modalform" json response needs to include the new captcha and your javascript needs to replace the input html element.

One idea would be, not to call "->reset()" in order to keep your existing captcha valid as long as the user has the form open. (In app/Http/Controllers/MainController.php)

I don't see this as a problem with this package, more with your approach to UI and JavaScript.

schel4ok commented 2 years ago

I managed to change captcha after successfull POST request and after click on refresh button like that

blade view


                <!-- mathcaptcha --> 
                <div class="mathcaptcha" :class="errorData.mathcaptcha ? 'text-red-500' : ''">
                    <label class="" for="mathcaptcha">Введите результат функции: <span x-text="mathcaptchaLabel" ></span></label>
                    <div class="flex space-x-4 text-gray-400 focus-within:text-gray-800">
                        <x-modules.button icon="refresh" style="black-outline" class="px-4" @click.prevent="mathcaptchaReset()" />
                        {!! app('mathcaptcha')->input(['class' => 'appearance-none rounded-md shadow-sm border-gray-300 placeholder-gray-400 focus:border-sky-500 focus:ring-1 focus:ring-sky-500 focus:outline-none valid:border-green-500 invalid:border-red-500 block w-full', 'id' => 'mathcaptcha', 'type' => 'text', 'name' => 'mathcaptcha', 'x-model' => 'formData.mathcaptcha']) !!}
                    </div>
                    <span x-text="errorData.mathcaptcha" class="text-red-500 text-xs"> </span>
                </div>  

    <script>

        function topbar() {
            return {

                mathcaptchaLabel: '{{ app('mathcaptcha')->label() }}',

                mathcaptchaReset() {
                    axios.get('/reload-captcha')
                    .then( (response) => {
                        console.log(response);
                        this.mathcaptchaLabel = response.data.mathcaptchaLabel; // assign mathcaptchaLabel value new captcha from json response
                    });
                },

                submitData() {

                    axios.post('/modalform', this.formData)
                    .then( (response) => {
                        ...
                        this.mathcaptchaLabel = response.data.mathcaptchaLabel; // assign mathcaptchaLabel value new captcha from json response
                    })

                },

routes/web.php

Route::get('/reload-captcha','MainController@reloadCaptcha')->name('reloadCaptcha');

MainController.php

    public function modalform(ModalformRequest $request) {

        Mail::to( config('mail.to.address') )->send(new Modalform());
        app('mathcaptcha')->reset();

        return response()->json([
            'status'            => 'success',
            'messageHeader'     => 'Ваш вопрос отправлен!', 
            'messageContent'    => 'В ближайшее время мы свяжемся с вами.',
            'mathcaptchaLabel'  => app('mathcaptcha')->label(),
            ]);

    }

    public function reloadCaptcha()
    {
        app('mathcaptcha')->reset();

        return response()->json([
            'mathcaptchaLabel'  => app('mathcaptcha')->label(),
        ]);
    }