diff --git a/README.md b/README.md
index 5b2d40f..92347d8 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,11 @@
-
-
+
+
+
@@ -23,7 +24,7 @@ A **PHP-PB** é uma comunidade de desenvolvedores PHP do estado da Paraíba, Bra
### O que fazemos
-- **Grupo no Telegram** - Discussões diárias sobre PHP, Laravel, dúvidas técnicas
+- **Grupo no WhatsApp** - Discussões diárias sobre PHP, Laravel, dúvidas técnicas
- **Meetups & Hangouts** - Encontros presenciais e online com palestras
- **Blog** - Artigos técnicos escritos pela comunidade
- **Vagas** - Oportunidades de emprego compartilhadas
@@ -38,7 +39,9 @@ A **PHP-PB** é uma comunidade de desenvolvedores PHP do estado da Paraíba, Bra
| **Tailwind CSS** | 4 | Framework CSS utility-first |
| **Alpine.js** | 3 | Interatividade (incluso no Livewire) |
| **MySQL** | 8 | Banco de dados |
-| **FilamentPHP** | 5 | Painel administrativo |
+| **FilamentPHP** | 5 | Painel administrativo e área de membro |
+| **Phosphor Icons** | - | Ícones do design system |
+| **Pest** | - | Testes automatizados |
## Estrutura do Projeto
@@ -151,23 +154,36 @@ npm run build
npm run dev
```
+## CI/CD
+
+- **CI** (`.github/workflows/ci.yml`): roda em todo push/PR na `master` - Pint (formatação), Larastan (análise estática) e a suíte de testes Pest.
+- **Deploy** (`.github/workflows/deploy.yml`): só dispara ao criar uma tag no formato `v*.*.*`. Builda os assets, empacota a aplicação e publica via SSH/SCP na Hostinger.
+
+```bash
+# Disparar um deploy em produção
+git tag v1.0.0
+git push origin v1.0.0
+```
+
## Comunidades Parceiras
Fazemos parte de uma rede de comunidades PHP do Nordeste:
-| Comunidade | Estado | Link |
-|------------|--------|------|
-| **PHP-PB** | Paraíba | [php-pb.net](https://php-pb.net) |
-| **PHP com Rapadura** | Ceará | [phpcomrapadura.org](https://phpcomrapadura.org) |
-| **PHP-PE** | Pernambuco | - |
-| **PHP-RN** | Rio Grande do Norte | - |
+| Comunidade | Estado |
+|------------|--------|
+| **PHP-PB** | Paraíba |
+| **PHP-PI** | Piauí |
+| **PHP-MA** | Maranhão |
+| **PHP com Rapadura** | Ceará |
+| **PHP-PE** | Pernambuco |
+| **PHP-RN** | Rio Grande do Norte |
Juntos organizamos o **[PHPeste](https://phpeste.dev)** - a maior conferência PHP do Nordeste brasileiro!
## Links
- [Site Oficial](https://php-pb.net)
-- [Telegram](https://t.me/phppb)
+- [WhatsApp](https://chat.whatsapp.com/JaWCta8t2DF9Af0zgIb8LB?mode=gi_t)
- [GitHub](https://github.com/php-pb)
- [Twitter/X](https://twitter.com/phppb)
- [Facebook](https://www.facebook.com/groups/php.pb)
diff --git a/site_novo/app/Http/Controllers/SitemapController.php b/site_novo/app/Http/Controllers/SitemapController.php
new file mode 100644
index 0000000..acc1fe7
--- /dev/null
+++ b/site_novo/app/Http/Controllers/SitemapController.php
@@ -0,0 +1,29 @@
+orderBy('published_at', 'desc')->get(['slug', 'updated_at']);
+
+ $staticRoutes = [
+ ['route' => route('home'), 'priority' => '1.0', 'changefreq' => 'weekly'],
+ ['route' => route('blog.index'), 'priority' => '0.8', 'changefreq' => 'daily'],
+ ['route' => route('events.index'), 'priority' => '0.8', 'changefreq' => 'daily'],
+ ['route' => route('jobs.index'), 'priority' => '0.6', 'changefreq' => 'weekly'],
+ ['route' => route('speakers.index'), 'priority' => '0.5', 'changefreq' => 'monthly'],
+ ];
+
+ $xml = view('sitemap', [
+ 'staticRoutes' => $staticRoutes,
+ 'posts' => $posts,
+ ])->render();
+
+ return response($xml, 200)->header('Content-Type', 'application/xml');
+ }
+}
diff --git a/site_novo/app/Models/Post.php b/site_novo/app/Models/Post.php
index 8f4f5a6..d21f133 100644
--- a/site_novo/app/Models/Post.php
+++ b/site_novo/app/Models/Post.php
@@ -81,4 +81,14 @@ public function getMainImage(): ?string
return $this->main_image_url;
}
+
+ public function getExcerpt(int $length = 160): string
+ {
+ $text = collect($this->content_blocks ?? [])
+ ->first(fn (array $block) => ($block['type'] ?? null) === 'paragraph' && filled($block['data']['text'] ?? null));
+
+ $plainText = trim(html_entity_decode(strip_tags($text['data']['text'] ?? ''), ENT_QUOTES));
+
+ return str($plainText)->limit($length)->toString();
+ }
}
diff --git a/site_novo/public/robots.txt b/site_novo/public/robots.txt
index eb05362..82953e3 100644
--- a/site_novo/public/robots.txt
+++ b/site_novo/public/robots.txt
@@ -1,2 +1,5 @@
User-agent: *
-Disallow:
+Disallow: /admin
+Disallow: /membro
+
+Sitemap: https://php-pb.net/sitemap.xml
diff --git a/site_novo/resources/views/blog/index.blade.php b/site_novo/resources/views/blog/index.blade.php
index 8880a50..778ff9d 100644
--- a/site_novo/resources/views/blog/index.blade.php
+++ b/site_novo/resources/views/blog/index.blade.php
@@ -1,4 +1,7 @@
-
+
diff --git a/site_novo/resources/views/blog/show.blade.php b/site_novo/resources/views/blog/show.blade.php
index 442d90f..40e6edf 100644
--- a/site_novo/resources/views/blog/show.blade.php
+++ b/site_novo/resources/views/blog/show.blade.php
@@ -1,4 +1,39 @@
-
+
+
+
+
+
diff --git "a/site_novo/resources/views/components/landing/\342\232\241footer.blade.php" "b/site_novo/resources/views/components/landing/\342\232\241footer.blade.php"
index 0761495..5c55995 100644
--- "a/site_novo/resources/views/components/landing/\342\232\241footer.blade.php"
+++ "b/site_novo/resources/views/components/landing/\342\232\241footer.blade.php"
@@ -46,9 +46,9 @@
class="text-azul-marinho hover:text-azul-principal transition-colors">
-
-
+
diff --git a/site_novo/resources/views/components/layouts/app.blade.php b/site_novo/resources/views/components/layouts/app.blade.php
index 8df4b88..beaa853 100644
--- a/site_novo/resources/views/components/layouts/app.blade.php
+++ b/site_novo/resources/views/components/layouts/app.blade.php
@@ -1,11 +1,44 @@
+@props([
+ 'title' => null,
+ 'description' => 'A PHP PB é a comunidade de desenvolvedores PHP da Paraíba. Conectamos pessoas, compartilhamos conhecimento e fortalecemos o ecossistema PHP no nosso estado.',
+ 'image' => null,
+ 'type' => 'website',
+ 'noindex' => false,
+])
+
+@php
+ $pageTitle = $title ?? config('app.name');
+ $ogImage = $image ?? asset('images/hero/farol-cena.png');
+ $canonical = rtrim(config('app.url'), '/').request()->getRequestUri();
+@endphp
+
-
+
+
+ @if ($noindex)
+
+ @endif
+
+ {{ $pageTitle }}
- {{ $title ?? config('app.name') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18,6 +51,8 @@
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
+
+ {{ $head ?? '' }}
{{ $slot }}
diff --git a/site_novo/resources/views/errors/403.blade.php b/site_novo/resources/views/errors/403.blade.php
index 6c7a681..ba3d4cc 100644
--- a/site_novo/resources/views/errors/403.blade.php
+++ b/site_novo/resources/views/errors/403.blade.php
@@ -1,4 +1,4 @@
-
+
diff --git a/site_novo/resources/views/errors/404.blade.php b/site_novo/resources/views/errors/404.blade.php
index cb5bac5..308dc77 100644
--- a/site_novo/resources/views/errors/404.blade.php
+++ b/site_novo/resources/views/errors/404.blade.php
@@ -1,4 +1,4 @@
-
+
diff --git a/site_novo/resources/views/events/index.blade.php b/site_novo/resources/views/events/index.blade.php
index 6659836..9861aeb 100644
--- a/site_novo/resources/views/events/index.blade.php
+++ b/site_novo/resources/views/events/index.blade.php
@@ -1,4 +1,43 @@
-
+
+ @if ($events->count() > 0)
+
+
+
+ @endif
+
diff --git a/site_novo/resources/views/jobs/index.blade.php b/site_novo/resources/views/jobs/index.blade.php
index 9401038..124595f 100644
--- a/site_novo/resources/views/jobs/index.blade.php
+++ b/site_novo/resources/views/jobs/index.blade.php
@@ -1,4 +1,7 @@
-
+
diff --git a/site_novo/resources/views/pages/home.blade.php b/site_novo/resources/views/pages/home.blade.php
index 96128b9..b82950b 100644
--- a/site_novo/resources/views/pages/home.blade.php
+++ b/site_novo/resources/views/pages/home.blade.php
@@ -1,4 +1,27 @@
-
+
+
+
+
+
diff --git a/site_novo/resources/views/sitemap.blade.php b/site_novo/resources/views/sitemap.blade.php
new file mode 100644
index 0000000..97b44c2
--- /dev/null
+++ b/site_novo/resources/views/sitemap.blade.php
@@ -0,0 +1,18 @@
+'; ?>
+
+@foreach ($staticRoutes as $item)
+
+ {{ $item['route'] }}
+ {{ $item['changefreq'] }}
+ {{ $item['priority'] }}
+
+@endforeach
+@foreach ($posts as $post)
+
+ {{ route('blog.show', $post->slug) }}
+ {{ $post->updated_at->toAtomString() }}
+ monthly
+ 0.7
+
+@endforeach
+
diff --git a/site_novo/resources/views/speakers/index.blade.php b/site_novo/resources/views/speakers/index.blade.php
index c997dc3..23c72ce 100644
--- a/site_novo/resources/views/speakers/index.blade.php
+++ b/site_novo/resources/views/speakers/index.blade.php
@@ -1,4 +1,7 @@
-
+
diff --git a/site_novo/routes/web.php b/site_novo/routes/web.php
index 60047b4..ce95c6f 100644
--- a/site_novo/routes/web.php
+++ b/site_novo/routes/web.php
@@ -3,6 +3,7 @@
use App\Http\Controllers\EventController;
use App\Http\Controllers\JobListingController;
use App\Http\Controllers\PostController;
+use App\Http\Controllers\SitemapController;
use App\Http\Controllers\SpeakerController;
use Illuminate\Support\Facades\Route;
@@ -10,6 +11,8 @@
return view('pages.home');
})->name('home');
+Route::get('/sitemap.xml', [SitemapController::class, 'index'])->name('sitemap');
+
Route::get('/blog', [PostController::class, 'index'])->name('blog.index');
Route::get('/blog/{slug}', [PostController::class, 'show'])->name('blog.show');
diff --git a/site_novo/tests/Feature/SeoTest.php b/site_novo/tests/Feature/SeoTest.php
new file mode 100644
index 0000000..b028cdc
--- /dev/null
+++ b/site_novo/tests/Feature/SeoTest.php
@@ -0,0 +1,57 @@
+get(route('home'))->assertOk();
+
+ $response->assertSee('name="description" content="A PHP PB é a comunidade', false);
+ $response->assertSee('assertSee('property="og:title"', false);
+ $response->assertSee('name="twitter:card" content="summary_large_image"', false);
+ $response->assertSee('"@type":"Organization"', false);
+});
+
+test('a blog post has its own meta description, og:image and blogposting jsonld', function () {
+ $post = Post::factory()->create([
+ 'title' => 'Como testar SEO no Laravel',
+ ]);
+
+ $response = $this->get(route('blog.show', $post->slug))->assertOk();
+
+ $response->assertSee('Como testar SEO no Laravel - PHP-PB', false);
+ $response->assertSee('property="og:type" content="article"', false);
+ $response->assertSee('"@type":"BlogPosting"', false);
+ $response->assertSee('"headline":"Como testar SEO no Laravel"', false);
+});
+
+test('the events page includes event jsonld only when there are upcoming events', function () {
+ $this->get(route('events.index'))
+ ->assertOk()
+ ->assertDontSee('application/ld+json', false);
+
+ Event::factory()->create(['title' => 'Meetup PHP PB de Teste']);
+
+ $response = $this->get(route('events.index'))->assertOk();
+
+ $response->assertSee('"@type":"Event"', false);
+ $response->assertSee('"name":"Meetup PHP PB de Teste"', false);
+});
+
+test('error pages are marked as noindex', function () {
+ $this->get('/rota-que-nao-existe')
+ ->assertNotFound()
+ ->assertSee('name="robots" content="noindex, nofollow"', false);
+});
+
+test('the sitemap lists static pages and published posts', function () {
+ $post = Post::factory()->create();
+
+ $response = $this->get('/sitemap.xml')->assertOk();
+
+ $response->assertHeader('Content-Type', 'application/xml');
+ $response->assertSee('', false);
+ $response->assertSee(''.route('home').'', false);
+ $response->assertSee(''.route('blog.show', $post->slug).'', false);
+});