feat(cms): implement complete SysSiteCMS content management system
Initial implementation of the HomeSysPanel CMS built with Laravel 13.0. Includes complete admin panel, content management, user authentication, modular architecture for products/services/chatbot, and marketing tools. Key features: - User authentication with email verification and password reset - Role-based access control (Admin/User with granular permissions) - Content management (pages, news, FAQ, downloads, galleries, banners) - Contact management system with subjects and responses - Marketing tools (email campaigns, visitor analytics, social sharing) - Modular architecture (Products, Services, Chatbot modules) - AI Chatbot integration with OpenRouter.ai and LlamaCpp support - Visitor tracking with geolocation - SEO optimization with meta tags and Open Graph - Responsive design with Bootstrap 5 - Dark mode support - Maintenance mode - Feature request system
This commit is contained in:
Executable
+116
@@ -0,0 +1,116 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Nova Página')
|
||||
|
||||
@section('content')
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Nova Página</h1>
|
||||
<a href="{{ route('admin.pages.index') }}" class="btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="bi bi-arrow-left text-white-50"></i> Voltar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Criar Conteúdo</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.pages.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Título da Página</label>
|
||||
<input type="text" name="title" id="title" class="form-control @error('title') is-invalid @enderror" value="{{ old('title') }}" required>
|
||||
@error('title') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Conteúdo (HTML)</label>
|
||||
<textarea name="content" id="editor" class="form-control @error('content') is-invalid @enderror" rows="15">{{ old('content') }}</textarea>
|
||||
@error('content') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Slug (URL amigável)</label>
|
||||
<input type="text" name="slug" id="slug" class="form-control @error('slug') is-invalid @enderror" value="{{ old('slug') }}" required>
|
||||
<small class="text-muted">Ex: minha-nova-pagina</small>
|
||||
@error('slug') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Meta Description (SEO)</label>
|
||||
<textarea name="meta_description" class="form-control @error('meta_description') is-invalid @enderror" rows="3">{{ old('meta_description') }}</textarea>
|
||||
@error('meta_description') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h6 class="font-weight-bold text-primary mb-3"><i class="bi bi-search me-1"></i> SEO Adicional</h6>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="small font-weight-bold">Palavras-chave</label>
|
||||
<input type="text" name="meta_keywords" class="form-control form-control-sm" value="{{ old('meta_keywords') }}" placeholder="Ex: computador, assistência">
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="small font-weight-bold">URL Canônica</label>
|
||||
<input type="text" name="canonical_url" class="form-control form-control-sm" value="{{ old('canonical_url') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Status da Página</label>
|
||||
<select name="active" class="form-control">
|
||||
<option value="1" {{ old('active', '1') == '1' ? 'selected' : '' }}>Ativa (Visível no site)</option>
|
||||
<option value="0" {{ old('active') == '0' ? 'selected' : '' }}>Inativa (Oculta)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<button type="submit" class="btn btn-primary btn-block py-3 fw-bold shadow-sm">
|
||||
<i class="bi bi-floppy me-1"></i> Criar Página
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('css')
|
||||
<!-- Incluindo o editor Summernote para facilitar a edição HTML -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
|
||||
@endpush
|
||||
|
||||
@push('js')
|
||||
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#editor').summernote({
|
||||
height: 400,
|
||||
placeholder: 'Escreva o conteúdo da página aqui...',
|
||||
tabsize: 2,
|
||||
toolbar: [
|
||||
['style', ['style']],
|
||||
['font', ['bold', 'underline', 'clear']],
|
||||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['table', ['table']],
|
||||
['insert', ['link', 'picture', 'video']],
|
||||
['view', ['fullscreen', 'codeview', 'help']]
|
||||
]
|
||||
});
|
||||
|
||||
// Gerar slug automaticamente a partir do título
|
||||
document.getElementById('title').addEventListener('keyup', function() {
|
||||
let text = this.value.toLowerCase()
|
||||
.normalize('NFD').replace(/[\u0300-\u036f]/g, "") // Remove acentos
|
||||
.replace(/[^\w ]+/g, '')
|
||||
.replace(/ +/g, '-');
|
||||
document.getElementById('slug').value = text;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Executable
+176
@@ -0,0 +1,176 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Editar Página')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$managedPages = [
|
||||
'contato' => ['label' => 'Contato', 'route' => route('admin.contacts.index'), 'section' => 'Mensagens de Contato'],
|
||||
'faq' => ['label' => 'FAQ', 'route' => route('admin.faqs.index'), 'section' => 'Gerenciamento de FAQ'],
|
||||
'downloads' => ['label' => 'Downloads', 'route' => route('admin.downloads.index'), 'section' => 'Gerenciamento de Downloads'],
|
||||
'produtos' => ['label' => 'Produtos', 'route' => '#', 'section' => 'Módulo de Produtos'],
|
||||
'servicos' => ['label' => 'Serviços', 'route' => '#', 'section' => 'Módulo de Serviços'],
|
||||
];
|
||||
$isManaged = isset($managedPages[$page->slug]);
|
||||
@endphp
|
||||
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Editar Página: {{ $page->title }}</h1>
|
||||
<a href="{{ route('admin.pages.index') }}" class="btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="bi bi-arrow-left text-white-50"></i> Voltar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if($isManaged)
|
||||
<div class="alert alert-warning border-0 shadow-sm mb-4">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-exclamation-triangle me-3 mt-1"></i>
|
||||
<div>
|
||||
<h5 class="alert-heading mb-1">Página com conteúdo dinâmico</h5>
|
||||
<p class="mb-2">
|
||||
O conteúdo desta página (<strong>{{ $managedPages[$page->slug]['label'] }}</strong>) é gerado dinamicamente pelo sistema e <strong>não deve ser editado manualmente</strong> por aqui. As informações exibidas nela são controladas pela seção <strong>{{ $managedPages[$page->slug]['section'] }}</strong> no painel administrativo.
|
||||
</p>
|
||||
@if($managedPages[$page->slug]['route'] !== '#')
|
||||
<a href="{{ $managedPages[$page->slug]['route'] }}" class="btn btn-sm btn-warning">
|
||||
<i class="bi bi-box-arrow-up-right me-1"></i> Ir para {{ $managedPages[$page->slug]['section'] }}
|
||||
</a>
|
||||
@endif
|
||||
<p class="small text-muted mt-2 mb-0">
|
||||
<i class="bi bi-info-circle me-1"></i> Você ainda pode ajustar o título, slug, SEO e o status da página abaixo, mas o conteúdo principal será sobrescrito pelo módulo correspondente.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex justify-content-between align-items-center">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Editar Conteúdo</h6>
|
||||
<a href="{{ url($page->slug == 'index' ? '/' : $page->slug) }}" target="_blank" class="btn btn-sm btn-info">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Visualizar no Site
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.pages.update', $page) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Título da Página</label>
|
||||
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror" value="{{ old('title', $page->title) }}" required>
|
||||
@error('title') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3 {{ $isManaged ? 'opacity-50' : '' }}">
|
||||
<label class="font-weight-bold">
|
||||
Conteúdo (HTML)
|
||||
@if($isManaged)
|
||||
<span class="badge badge-warning ml-1"><i class="bi bi-lock me-1"></i>Gerenciado pelo sistema</span>
|
||||
@endif
|
||||
</label>
|
||||
<textarea name="content" id="editor" class="form-control @error('content') is-invalid @enderror" rows="20" {{ $isManaged ? 'disabled' : '' }}>{{ old('content', $page->content) }}</textarea>
|
||||
@error('content') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Slug (URL amigável)</label>
|
||||
<input type="text" name="slug" class="form-control @error('slug') is-invalid @enderror" value="{{ old('slug', $page->slug) }}" {{ in_array($page->slug, ['index', 'about', 'produtos', 'servicos', 'downloads', 'contato', 'faq']) ? 'readonly' : 'required' }}>
|
||||
<small class="text-muted">As páginas principais do sistema não podem ter o slug alterado.</small>
|
||||
@error('slug') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Meta Description (SEO)</label>
|
||||
<textarea name="meta_description" class="form-control @error('meta_description') is-invalid @enderror" rows="3">{{ old('meta_description', $page->meta_description) }}</textarea>
|
||||
@error('meta_description') <div class="invalid-feedback">{{ $message }}</div> @enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="font-weight-bold">Status</label>
|
||||
<select name="active" class="form-control">
|
||||
<option value="1" {{ old('active', $page->active) ? 'selected' : '' }}>Ativa (Visível)</option>
|
||||
<option value="0" {{ old('active', $page->active) ? '' : 'selected' }}>Inativa (Oculta)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h6 class="font-weight-bold text-primary mb-3"><i class="bi bi-search me-1"></i> Configurações Avançadas de SEO</h6>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="small font-weight-bold">Palavras-chave (Keywords)</label>
|
||||
<input type="text" name="meta_keywords" class="form-control form-control-sm" value="{{ old('meta_keywords', $page->meta_keywords) }}" placeholder="computador, manutenção, vendas">
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="small font-weight-bold">URL Canônica</label>
|
||||
<input type="text" name="canonical_url" class="form-control form-control-sm" value="{{ old('canonical_url', $page->canonical_url) }}" placeholder="https://exemplo.com/pagina">
|
||||
</div>
|
||||
|
||||
<div class="bg-light p-3 rounded border">
|
||||
<h6 class="small font-weight-bold mb-2">Redes Sociais (Open Graph)</h6>
|
||||
<div class="form-group mb-2">
|
||||
<label class="x-small">Título OG</label>
|
||||
<input type="text" name="og_title" class="form-control form-control-sm" value="{{ old('og_title', $page->og_title) }}">
|
||||
</div>
|
||||
<div class="form-group mb-2">
|
||||
<label class="x-small">Descrição OG</label>
|
||||
<textarea name="og_description" class="form-control form-control-sm" rows="2">{{ old('og_description', $page->og_description) }}</textarea>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<label class="x-small">URL da Imagem OG</label>
|
||||
<input type="text" name="og_image" class="form-control form-control-sm" value="{{ old('og_image', $page->og_image) }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<button type="submit" class="btn btn-primary btn-block py-3 fw-bold shadow-sm">
|
||||
<i class="bi bi-floppy me-1"></i> Salvar Alterações
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('css')
|
||||
<!-- Incluindo o editor Summernote para facilitar a edição HTML -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
|
||||
@endpush
|
||||
|
||||
@push('js')
|
||||
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
@if($isManaged)
|
||||
// Disable editor for system-managed pages
|
||||
$('#editor').summernote({
|
||||
height: 400,
|
||||
disabled: true,
|
||||
placeholder: 'O conteúdo desta página é gerenciado automaticamente pelo sistema.',
|
||||
tabsize: 2,
|
||||
toolbar: []
|
||||
});
|
||||
@else
|
||||
$('#editor').summernote({
|
||||
height: 400,
|
||||
placeholder: 'Escreva o conteúdo da página aqui...',
|
||||
tabsize: 2,
|
||||
toolbar: [
|
||||
['style', ['style']],
|
||||
['font', ['bold', 'underline', 'clear']],
|
||||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['table', ['table']],
|
||||
['insert', ['link', 'picture', 'video']],
|
||||
['view', ['fullscreen', 'codeview', 'help']]
|
||||
]
|
||||
});
|
||||
@endif
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Gerenciar Páginas')
|
||||
|
||||
@section('content')
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Páginas Institucionais</h1>
|
||||
<a href="{{ route('admin.pages.create') }}" class="btn btn-sm btn-primary shadow-sm">
|
||||
<i class="bi bi-plus-lg text-white-50"></i> Nova Página
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success border-0 shadow-sm">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Lista de Páginas</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Título</th>
|
||||
<th>Slug (URL)</th>
|
||||
<th>Status</th>
|
||||
<th>Última Atualização</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($pages as $page)
|
||||
@php
|
||||
$managedSlugs = ['contato', 'faq', 'downloads', 'produtos', 'servicos'];
|
||||
$isManaged = in_array($page->slug, $managedSlugs);
|
||||
@endphp
|
||||
<tr class="{{ $isManaged ? 'table-warning' : '' }}">
|
||||
<td>
|
||||
{{ $page->title }}
|
||||
@if($isManaged)
|
||||
<span class="badge badge-warning ml-1" title="Conteúdo gerenciado pelo sistema"><i class="bi bi-lock"></i> Sistema</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><code>/{{ $page->slug }}</code></td>
|
||||
<td>
|
||||
<span class="badge badge-{{ $page->active ? 'success' : 'secondary' }}">
|
||||
{{ $page->active ? 'Ativa' : 'Inativa' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ $page->updated_at->format('d/m/Y H:i') }}</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.pages.edit', $page) }}" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-pencil"></i> Editar
|
||||
</a>
|
||||
<a href="{{ url($page->slug == 'index' ? '/' : $page->slug) }}" target="_blank" class="btn btn-sm btn-info">
|
||||
<i class="bi bi-eye"></i> Ver
|
||||
</a>
|
||||
<form action="{{ route('admin.pages.destroy', $page) }}" method="POST" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Tem certeza que deseja excluir esta página?')">
|
||||
<i class="bi bi-trash"></i> Excluir
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user