First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('admin/dashboard') }}">{{ __('dashboard.title') }}</a></li>
<li class="breadcrumb-item active">{{ __('contact.admin.title') }}</li>
</ol>
<div class="mb-4">
<div class="btn-group" role="group">
<a href="{{ url('admin/contacts?status=all') }}" class="btn {{ current_status == 'all' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.all') }}
</a>
<a href="{{ url('admin/contacts?status=unread') }}" class="btn {{ current_status == 'unread' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.unread') }}
</a>
<a href="{{ url('admin/contacts?status=read') }}" class="btn {{ current_status == 'read' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.read') }}
</a>
<a href="{{ url('admin/contacts?status=new') }}" class="btn {{ current_status == 'new' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.new') }}
</a>
<a href="{{ url('admin/contacts?status=in_progress') }}" class="btn {{ current_status == 'in_progress' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.in_progress') }}
</a>
<a href="{{ url('admin/contacts?status=resolved') }}" class="btn {{ current_status == 'resolved' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.resolved') }}
</a>
<a href="{{ url('admin/contacts?status=closed') }}" class="btn {{ current_status == 'closed' ? 'btn-primary' : 'btn-outline-primary' }}">
{{ __('contact.admin.statuses.closed') }}
</a>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<i class="bi bi-envelope me-1"></i>
{{ __('contact.admin.list_title') }}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>{{ __('contact.admin.columns.name') }}</th>
<th>{{ __('contact.admin.columns.email') }}</th>
<th>{{ __('contact.admin.columns.subject') }}</th>
<th>{{ __('contact.admin.columns.status') }}</th>
<th>{{ __('contact.admin.columns.date') }}</th>
<th>{{ __('contact.admin.columns.actions') }}</th>
</tr>
</thead>
<tbody>
{% for contact in contacts %}
<tr class="{{ not contact.isRead() ? 'table-primary' : '' }}">
<td>
{% if not contact.isRead() %}
<i class="bi bi-envelope-fill text-primary me-1"></i>
{% else %}
<i class="bi bi-envelope-open text-muted me-1"></i>
{% endif %}
{{ contact.contacts_name }}
</td>
<td>{{ contact.contacts_email }}</td>
<td>{{ contact.contacts_subject }}</td>
<td>
{% set statusColors = {
'new': 'primary',
'in_progress': 'warning',
'resolved': 'success',
'closed': 'secondary'
} %}
<span class="badge bg-{{ statusColors[contact.contacts_status] ?? 'secondary' }}">
{{ __('contact.admin.statuses.' ~ contact.contacts_status) }}
</span>
</td>
<td>{{ contact.formatted_created_at }}</td>
<td>
<a href="{{ url('admin/contacts/' ~ contact.contacts_id) }}" class="btn btn-info btn-sm" title="{{ __('contact.admin.actions.view') }}">
<i class="bi bi-eye"></i>
</a>
<button type="button" class="btn btn-sm {{ contact.isRead() ? 'btn-secondary' : 'btn-success' }}"
onclick="toggleReadStatus({{ contact.contacts_id }})"
title="{{ contact.isRead() ? __('contact.admin.actions.mark_unread') : __('contact.admin.actions.mark_read') }}">
<i class="bi {{ contact.isRead() ? 'bi-envelope' : 'bi-envelope-check' }}"></i>
</button>
<button type="button" class="btn btn-danger btn-sm"
onclick="deleteContact({{ contact.contacts_id }})"
title="{{ __('contact.admin.actions.delete') }}">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center">{{ __('contact.admin.no_messages') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function toggleReadStatus(id) {
fetch(`{{ url('admin/contacts/') }}${id}/toggle-read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message || '{{ __("contact.admin.error_occurred") }}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{{ __("contact.admin.error_occurred") }}');
});
}
function deleteContact(id) {
if (confirm("{{ __('contact.admin.confirm_delete') }}")) {
fetch(`{{ url('admin/contacts/') }}${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message || '{{ __("contact.admin.error_occurred") }}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{{ __("contact.admin.error_occurred") }}');
});
}
}
</script>
{% endblock %}
+212
View File
@@ -0,0 +1,212 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('admin/dashboard') }}">{{ __('dashboard.title') }}</a></li>
<li class="breadcrumb-item"><a href="{{ url('admin/contacts') }}">{{ __('contact.admin.title') }}</a></li>
<li class="breadcrumb-item active">{{ contact.contacts_subject }}</li>
</ol>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<i class="bi bi-envelope me-1"></i>
{{ __('contact.admin.details_title') }}
</div>
<div>
{% set statusColors = {
'new': 'primary',
'in_progress': 'warning',
'resolved': 'success',
'closed': 'secondary'
} %}
<span class="badge bg-{{ statusColors[contact.contacts_status] ?? 'secondary' }}">
{{ __('contact.admin.statuses.' ~ contact.contacts_status) }}
</span>
</div>
</div>
<div class="card-body">
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.name') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ contact.contacts_name }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.email') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">
<a href="mailto:{{ contact.contacts_email }}">{{ contact.contacts_email }}</a>
</p>
</div>
</div>
{% if contact.contacts_phone %}
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.phone') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ contact.contacts_phone }}</p>
</div>
</div>
{% endif %}
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.subject') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ contact.contacts_subject }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.message') }}</label>
<div class="col-sm-10">
<div class="border rounded p-3 bg-light">
{{ contact.contacts_message|nl2br }}
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.response') }}</label>
<div class="col-sm-10">
<form action="{{ url('admin/contacts/' ~ contact.contacts_id ~ '/response') }}" method="POST">
<div class="mb-3">
<textarea class="form-control" id="response" name="response" rows="5" placeholder="{{ __('contact.admin.placeholders.response') }}">{{ contact.contacts_response }}</textarea>
</div>
<div class="text-end">
<button type="submit" class="btn btn-primary">
<i class="bi bi-reply me-1"></i> {{ __('contact.admin.actions.save_response') }}
</button>
</div>
</form>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.ip_address') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ contact.contacts_ip_address }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.fields.date') }}</label>
<div class="col-sm-10">
<p class="form-control-plaintext">{{ contact.formatted_created_at }}</p>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label fw-bold">{{ __('contact.admin.columns.status') }}</label>
<div class="col-sm-10">
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ __('contact.admin.statuses.' ~ contact.contacts_status) }}
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="updateStatus('new')">{{ __('contact.admin.statuses.new') }}</a></li>
<li><a class="dropdown-item" href="#" onclick="updateStatus('in_progress')">{{ __('contact.admin.statuses.in_progress') }}</a></li>
<li><a class="dropdown-item" href="#" onclick="updateStatus('resolved')">{{ __('contact.admin.statuses.resolved') }}</a></li>
<li><a class="dropdown-item" href="#" onclick="updateStatus('closed')">{{ __('contact.admin.statuses.closed') }}</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-10 offset-sm-2">
<button type="button" class="btn btn-secondary me-2" onclick="toggleReadStatus()">
<i class="bi {{ contact.isRead() ? 'bi-envelope' : 'bi-envelope-check' }}"></i>
{{ contact.isRead() ? __('contact.admin.actions.mark_unread') : __('contact.admin.actions.mark_read') }}
</button>
<a href="{{ url('admin/contacts') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> {{ __('contact.admin.actions.back') }}
</a>
<button type="button" class="btn btn-danger float-end" onclick="deleteContact()">
<i class="bi bi-trash"></i> {{ __('contact.admin.actions.delete') }}
</button>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
function updateStatus(status) {
fetch(`{{ url('admin/contacts/') }}{{ contact.contacts_id }}/status`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
status: status
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message || '{{ __("contact.admin.error_occurred") }}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{{ __("contact.admin.error_occurred") }}');
});
}
function toggleReadStatus() {
fetch(`{{ url('admin/contacts/') }}{{ contact.contacts_id }}/toggle-read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message || '{{ __("contact.admin.error_occurred") }}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{{ __("contact.admin.error_occurred") }}');
});
}
function deleteContact() {
if (confirm("{{ __('contact.admin.confirm_delete') }}")) {
fetch(`{{ url('admin/contacts/') }}{{ contact.contacts_id }}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = '{{ url("admin/contacts") }}';
} else {
alert(data.message || '{{ __("contact.admin.error_occurred") }}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{{ __("contact.admin.error_occurred") }}');
});
}
}
</script>
{% endblock %}
+313
View File
@@ -0,0 +1,313 @@
{% extends "@index/layout.twig" %}
{% block title %}{{ __('contact.title') }} - {{ platformName }}{% endblock %}
{% block head %}
<style>
:root {
--primary-gradient: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
--ai-gradient: linear-gradient(135deg, #8b5cf6 0%, #d946ef 100%);
--dark-bg: #0f172a;
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: #f8fafc !important;
}
.contact-hero {
background: var(--dark-bg);
background-image:
radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.1) 0%, transparent 40%),
radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.1) 0%, transparent 40%);
color: white;
padding: 100px 0 60px;
text-align: center;
}
.contact-badge {
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.3);
color: #60a5fa;
padding: 8px 20px;
border-radius: 50px;
font-size: 0.9rem;
font-weight: 600;
display: inline-block;
margin-bottom: 20px;
}
.contact-title {
font-size: 3rem;
font-weight: 800;
margin-bottom: 20px;
}
.contact-card {
background: white;
border-radius: 24px;
border: 1px solid #e2e8f0;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0,0,0,0.05);
margin-top: -50px;
}
.contact-info-sidebar {
background: #f1f5f9;
padding: 40px;
height: 100%;
}
.info-item {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.info-icon {
width: 48px;
height: 48px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: #3b82f6;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
flex-shrink: 0;
}
.info-content h5 {
font-weight: 700;
margin-bottom: 5px;
color: #0f172a;
}
.info-content p {
color: #64748b;
margin-bottom: 0;
}
.contact-form-body {
padding: 40px;
}
.form-label {
font-weight: 600;
color: #334155;
margin-bottom: 8px;
}
.form-control {
border-radius: 12px;
padding: 12px 16px;
border: 1px solid #e2e8f0;
transition: all 0.3s;
}
.form-control:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
.btn-submit {
background: var(--primary-gradient);
color: white;
border: none;
padding: 14px 30px;
border-radius: 12px;
font-weight: 700;
transition: all 0.3s;
width: 100%;
}
.btn-submit:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3);
color: white;
}
.captcha-box {
background: #f8fafc;
border-radius: 12px;
padding: 15px;
border: 1px dashed #cbd5e1;
margin-bottom: 20px;
}
.alert-toast {
position: fixed;
top: 20px;
right: 20px;
z-index: 1060;
min-width: 300px;
}
</style>
{% endblock %}
{% block layout_body %}
<div class="alert-toast" x-data="{ show: false, message: '', type: 'success' }"
@show-alert.window="show = true; message = $event.detail.message; type = $event.detail.type; setTimeout(() => show = false, 5000)">
<div x-show="show" x-transition
:class="`alert alert-${type} shadow-lg border-0 rounded-4`" role="alert">
<div class="d-flex align-items-center gap-2">
<i class="bi" :class="type === 'success' ? 'bi-check-circle-fill' : 'bi-exclamation-triangle-fill'"></i>
<span x-text="message"></span>
</div>
</div>
</div>
<section class="contact-hero">
<div class="container">
<div class="contact-badge">Contato</div>
<h1 class="contact-title">{{ __('contact.title') }}</h1>
<p class="lead opacity-75 mx-auto" style="max-width: 600px;">
{{ __('contact.subtitle') }}
</p>
</div>
</section>
<section class="pb-5">
<div class="container">
<div class="contact-card">
<div class="row g-0">
<div class="col-lg-5">
<div class="contact-info-sidebar">
<h3 class="fw-bold mb-4" style="color: #0f172a;">Informações</h3>
<div class="info-item">
<div class="info-icon"><i class="bi bi-geo-alt"></i></div>
<div class="info-content">
<h5>{{ __('contact.address_title') }}</h5>
<p>{{ __('contact.address_line1') }}<br>{{ __('contact.address_line2') }}</p>
</div>
</div>
<div class="info-item">
<div class="info-icon"><i class="bi bi-telephone"></i></div>
<div class="info-content">
<h5>{{ __('contact.phone_title') }}</h5>
<p>{{ __('contact.phone_number') }}<br><small>{{ __('contact.phone_hours') }}</small></p>
</div>
</div>
<div class="info-item">
<div class="info-icon"><i class="bi bi-envelope"></i></div>
<div class="info-content">
<h5>{{ __('contact.email_title') }}</h5>
<p>{{ __('contact.email_info') }}{% if __('contact.email_support') %}<br>{{ __('contact.email_support') }}{% endif %}</p>
</div>
</div>
</div>
</div>
<div class="col-lg-7">
<div class="contact-form-body" x-data="contactForm()">
<h3 class="fw-bold mb-4" style="color: #0f172a;">{{ __('contact.form_title') }}</h3>
<form @submit.prevent="submitForm">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">{{ __('contact.form_name') }}</label>
<input type="text" class="form-control" x-model="formData.name" required placeholder="Seu nome completo">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">{{ __('contact.form_email') }}</label>
<input type="email" class="form-control" x-model="formData.email" required placeholder="seu@email.com">
</div>
</div>
<div class="mb-3">
<label class="form-label">{{ __('contact.form_subject') }}</label>
<input type="text" class="form-control" x-model="formData.subject" required placeholder="Como podemos ajudar?">
</div>
<div class="mb-4">
<label class="form-label">{{ __('contact.form_message') }}</label>
<textarea class="form-control" rows="5" x-model="formData.message" required placeholder="Descreva sua dúvida ou sugestão..."></textarea>
</div>
{% if require_captcha_contact %}
<div class="captcha-box" x-data="{
num1: 0, num2: 0, userAnswer: '', solved: false,
generate() {
this.num1 = Math.floor(Math.random() * 10) + 1;
this.num2 = Math.floor(Math.random() * 10) + 1;
this.userAnswer = ''; this.solved = false;
},
check() { this.solved = (parseInt(this.userAnswer) === (this.num1 + this.num2)); }
}" x-init="generate()" @reset-captcha.window="generate()">
<label class="form-label d-block mb-2">{{ __('contact.security_check') }}</label>
<div class="d-flex align-items-center gap-3">
<div class="bg-white px-3 py-2 rounded border fw-bold">
<span x-text="num1"></span> + <span x-text="num2"></span> =
</div>
<input type="number" class="form-control" style="width: 100px;" x-model="userAnswer" @input="check()" placeholder="?">
<span x-show="solved" class="text-success"><i class="bi bi-check-circle-fill"></i></span>
</div>
<input type="hidden" name="captcha_valid" :value="solved ? '1' : ''">
</div>
{% endif %}
<button type="submit" class="btn-submit" :disabled="loading">
<span x-show="!loading"><i class="bi bi-send-fill me-2"></i> {{ __('contact.form_submit') }}</span>
<span x-show="loading" class="spinner-border spinner-border-sm me-2"></span>
<span x-show="loading">{{ __('contact.sending') }}</span>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block scripts %}
<script>
function contactForm() {
return {
formData: { name: '', email: '', subject: '', message: '' },
loading: false,
submitForm() {
const captchaInput = document.querySelector('input[name="captcha_valid"]');
if (captchaInput && captchaInput.value !== '1') {
window.dispatchEvent(new CustomEvent('show-alert', {
detail: { message: '{{ __("contact.js_invalid_captcha") }}', type: 'danger' }
}));
return;
}
this.loading = true;
// Inclui o captcha_valid no corpo da requisição JSON para o backend
const payload = { ...this.formData };
if (captchaInput) {
payload.captcha_valid = captchaInput.value;
}
fetch('/contact/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
if (data.success) {
window.dispatchEvent(new CustomEvent('show-alert', {
detail: { message: '{{ __("contact.js_success") }}', type: 'success' }
}));
this.formData = { name: '', email: '', subject: '', message: '' };
window.dispatchEvent(new CustomEvent('reset-captcha'));
} else {
throw new Error(data.message);
}
})
.catch(err => {
window.dispatchEvent(new CustomEvent('show-alert', {
detail: { message: err.message || '{{ __("contact.js_error") }}', type: 'danger' }
}));
})
.finally(() => { this.loading = false; });
}
}
}
</script>
{% endblock %}