se agrego Componente formulario
This commit is contained in:
176
resources/views/components/form/basic-contact.blade.php
Normal file
176
resources/views/components/form/basic-contact.blade.php
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
@props([
|
||||||
|
'id' => 'contact-form',
|
||||||
|
'fields' => [],
|
||||||
|
'buttonLabel' => 'Enviar',
|
||||||
|
'successMessage' => '¡Gracias! Te contactaremos muy pronto.',
|
||||||
|
'action' => config('app.contact_form_action', request()->url()), {{-- Ajusta si usas una ruta específica --}}
|
||||||
|
'method' => 'POST',
|
||||||
|
'buttonClass' => 'btn btn-primary custom-btn text-center text-uppercase text-decoration-none border-0 py-0 px-5 font-weight-semibold',
|
||||||
|
'class' => 'row g-3',
|
||||||
|
'showHoneypot' => true,
|
||||||
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
// Normaliza campos y valores por defecto
|
||||||
|
$normalized = collect($fields)->map(function ($f) {
|
||||||
|
$f = collect($f);
|
||||||
|
return [
|
||||||
|
'type' => $f->get('type', 'text'),
|
||||||
|
'name' => $f->get('name', ''),
|
||||||
|
'label' => $f->get('label', ''),
|
||||||
|
'placeholder' => $f->get('placeholder', ''),
|
||||||
|
'value' => old($f->get('name'), $f->get('value', '')),
|
||||||
|
'required' => (bool) $f->get('required', false),
|
||||||
|
'rows' => (int) $f->get('rows', 6),
|
||||||
|
'options' => $f->get('options', []), // para selects/radios si en el futuro lo usas
|
||||||
|
'col' => $f->get('col', 'col-12'), // grid responsive opcional
|
||||||
|
'help' => $f->get('help', ''),
|
||||||
|
'attrs' => $f->get('attrs', []), // atributos HTML extra
|
||||||
|
];
|
||||||
|
});
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<form id="{{ $id }}"
|
||||||
|
action="{{ $action }}"
|
||||||
|
method="{{ strtoupper($method) === 'GET' ? 'GET' : 'POST' }}"
|
||||||
|
class="contact-form custom-form-style-1 {{ $class }}"
|
||||||
|
novalidate
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
@if(!in_array(strtoupper($method), ['GET','POST']))
|
||||||
|
@method($method)
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Porto: alertas de éxito / error --}}
|
||||||
|
@if (session('contact_success'))
|
||||||
|
<div class="contact-form-success alert alert-success mt-2" role="status">
|
||||||
|
<strong>¡Éxito!</strong> {{ session('contact_success') ?: $successMessage }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="contact-form-error alert alert-danger mt-2" role="alert">
|
||||||
|
<strong>¡Error!</strong> Verifica los datos del formulario.
|
||||||
|
<ul class="mb-0 mt-2">
|
||||||
|
@foreach ($errors->all() as $e)
|
||||||
|
<li>{{ $e }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Honeypot antispam (oculto a usuarios reales) --}}
|
||||||
|
@if($showHoneypot)
|
||||||
|
<div aria-hidden="true" class="d-none">
|
||||||
|
<label for="{{ $id }}-website">Sitio web</label>
|
||||||
|
<input type="text" name="website" id="{{ $id }}-website" tabindex="-1" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Render dinámico de campos --}}
|
||||||
|
@foreach($normalized as $field)
|
||||||
|
@php
|
||||||
|
$inputId = $id . '-' . $field['name'];
|
||||||
|
$required = $field['required'] ? 'required' : '';
|
||||||
|
$isInvalid = $errors->has($field['name']) ? 'is-invalid' : '';
|
||||||
|
$attrs = collect($field['attrs'])->map(fn($v, $k) => $k.'="'.e($v).'"')->implode(' ');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($field['type'] === 'hidden')
|
||||||
|
<input type="hidden" name="{{ $field['name'] }}" value="{{ $field['value'] }}">
|
||||||
|
@continue
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="form-group {{ $field['col'] }}">
|
||||||
|
@if($field['label'])
|
||||||
|
<label class="form-label mb-1 font-weight-bold text-dark" for="{{ $inputId }}">
|
||||||
|
{{ $field['label'] }}
|
||||||
|
@if($field['required']) <span class="text-danger" aria-hidden="true">*</span>@endif
|
||||||
|
</label>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@switch($field['type'])
|
||||||
|
@case('textarea')
|
||||||
|
<textarea
|
||||||
|
id="{{ $inputId }}"
|
||||||
|
name="{{ $field['name'] }}"
|
||||||
|
rows="{{ $field['rows'] }}"
|
||||||
|
class="form-control {{ $isInvalid }}"
|
||||||
|
placeholder="{{ $field['placeholder'] ?: $field['label'] }}"
|
||||||
|
{{ $required }}
|
||||||
|
aria-required="{{ $field['required'] ? 'true' : 'false' }}"
|
||||||
|
aria-invalid="{{ $errors->has($field['name']) ? 'true' : 'false' }}"
|
||||||
|
{!! $attrs !!}
|
||||||
|
>{{ $field['value'] }}</textarea>
|
||||||
|
@break
|
||||||
|
|
||||||
|
@case('select')
|
||||||
|
<select
|
||||||
|
id="{{ $inputId }}"
|
||||||
|
name="{{ $field['name'] }}"
|
||||||
|
class="form-select {{ $isInvalid }}"
|
||||||
|
{{ $required }}
|
||||||
|
aria-required="{{ $field['required'] ? 'true' : 'false' }}"
|
||||||
|
aria-invalid="{{ $errors->has($field['name']) ? 'true' : 'false' }}"
|
||||||
|
{!! $attrs !!}
|
||||||
|
>
|
||||||
|
<option value="" disabled {{ $field['value'] === '' ? 'selected' : '' }}>
|
||||||
|
{{ $field['placeholder'] ?: 'Selecciona una opción' }}
|
||||||
|
</option>
|
||||||
|
@foreach($field['options'] as $optValue => $optLabel)
|
||||||
|
<option value="{{ $optValue }}" {{ (string)$optValue === (string)$field['value'] ? 'selected' : '' }}>
|
||||||
|
{{ $optLabel }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@break
|
||||||
|
|
||||||
|
@default
|
||||||
|
<input
|
||||||
|
id="{{ $inputId }}"
|
||||||
|
type="{{ $field['type'] }}"
|
||||||
|
name="{{ $field['name'] }}"
|
||||||
|
value="{{ $field['value'] }}"
|
||||||
|
class="form-control {{ $isInvalid }}"
|
||||||
|
placeholder="{{ $field['placeholder'] ?: $field['label'] }}"
|
||||||
|
{{ $required }}
|
||||||
|
aria-required="{{ $field['required'] ? 'true' : 'false' }}"
|
||||||
|
aria-invalid="{{ $errors->has($field['name']) ? 'true' : 'false' }}"
|
||||||
|
{!! $attrs !!}
|
||||||
|
/>
|
||||||
|
@endswitch
|
||||||
|
|
||||||
|
@if($field['help'])
|
||||||
|
<div class="form-text">{{ $field['help'] }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@error($field['name'])
|
||||||
|
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- Botón enviar al estilo Porto --}}
|
||||||
|
<div class="form-group col-12 mt-2">
|
||||||
|
<button type="submit" class="{{ $buttonClass }}">
|
||||||
|
<span class="me-2">{{ $buttonLabel }}</span>
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{-- Mejora UX: evita reenvío al volver / scroll a errores --}}
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Evita reenvío en back/forward cache
|
||||||
|
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
|
||||||
|
|
||||||
|
const errorBox = document.querySelector('.contact-form-error');
|
||||||
|
if (errorBox) {
|
||||||
|
const y = errorBox.getBoundingClientRect().top + window.pageYOffset - 120;
|
||||||
|
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
Reference in New Issue
Block a user