se agrego Componente formulario

This commit is contained in:
2025-09-15 12:19:14 -06:00
parent 2e861d4af4
commit 16fd40531c

View 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