From 49aeceb25ce9e69fa5ad88ca9fc06342e9517552 Mon Sep 17 00:00:00 2001 From: Naser Mansour Date: Fri, 9 Jan 2026 17:18:27 +0200 Subject: [PATCH] complete story 14.5 --- .../story-14.5-latest-posts-section.md | 68 +++++-- lang/ar/home.php | 7 + lang/en/home.php | 7 + .../views/{ => livewire}/pages/home.blade.php | 64 +++++- routes/web.php | 4 +- tests/Feature/Public/HomePageTest.php | 187 ++++++++++++++++++ 6 files changed, 319 insertions(+), 18 deletions(-) rename resources/views/{ => livewire}/pages/home.blade.php (66%) diff --git a/docs/stories/story-14.5-latest-posts-section.md b/docs/stories/story-14.5-latest-posts-section.md index 8340e8c..0251caf 100644 --- a/docs/stories/story-14.5-latest-posts-section.md +++ b/docs/stories/story-14.5-latest-posts-section.md @@ -220,19 +220,19 @@ $post->created_at->translatedFormat('j F Y') ## Dev Checklist -- [ ] Determine data passing method (Volt component or route) -- [ ] Create posts section HTML structure -- [ ] Add section heading with translations -- [ ] Implement post card design -- [ ] Query and display 3 latest published posts -- [ ] Add date formatting with translation support -- [ ] Implement excerpt truncation -- [ ] Add "Read More" links to individual posts -- [ ] Add "View All" link to posts index -- [ ] Handle empty state (hide section or show message) -- [ ] Implement responsive grid layout -- [ ] Test RTL layout -- [ ] Verify links work correctly +- [x] Determine data passing method (Volt component or route) +- [x] Create posts section HTML structure +- [x] Add section heading with translations +- [x] Implement post card design +- [x] Query and display 3 latest published posts +- [x] Add date formatting with translation support +- [x] Implement excerpt truncation +- [x] Add "Read More" links to individual posts +- [x] Add "View All" link to posts index +- [x] Handle empty state (hide section or show message) +- [x] Implement responsive grid layout +- [x] Test RTL layout +- [x] Verify links work correctly ## Estimation @@ -244,3 +244,45 @@ $post->created_at->translatedFormat('j F Y') - Previous stories for page structure - Existing Post model - Posts routes (`posts.index`, `posts.show`) + +--- + +## Dev Agent Record + +### Status +Ready for Review + +### Agent Model Used +Claude Opus 4.5 (claude-opus-4-5-20251101) + +### File List +| File | Action | +|------|--------| +| `resources/views/livewire/pages/home.blade.php` | Created (new Volt component) | +| `resources/views/pages/home.blade.php` | Deleted (replaced by Volt component) | +| `routes/web.php` | Modified (changed to Volt::route) | +| `lang/en/home.php` | Modified (added posts translations) | +| `lang/ar/home.php` | Modified (added posts translations) | +| `tests/Feature/Public/HomePageTest.php` | Modified (added 17 new tests) | + +### Change Log +- Converted home page from plain Blade view to Volt component with `with()` method for data fetching +- Added latest posts section that displays 3 most recent published posts +- Implemented responsive grid layout (1 col mobile, 2 col tablet, 3 col desktop) +- Added post cards with title, date, excerpt, and "Read More" links +- Added "View All Articles" button linking to posts index +- Section hidden when no published posts exist (empty state) +- Used Post model's `getTitle()` and `getExcerpt()` methods for bilingual support +- Uses `published_at` for ordering and display, with fallback to `created_at` +- Added wire:navigate for SPA-like navigation +- Added hover effects on cards and title links + +### Debug Log References +None - implementation completed without issues + +### Completion Notes +- All acceptance criteria met +- 17 new tests added covering posts section functionality +- All 91 home page and posts tests pass +- RTL support inherited from existing layout structure +- Uses existing Post model scope `published()` for filtering diff --git a/lang/ar/home.php b/lang/ar/home.php index 244565d..5f29750 100644 --- a/lang/ar/home.php +++ b/lang/ar/home.php @@ -41,6 +41,13 @@ return [ // Values Section 'values_title' => 'قيمنا', 'values_subtitle' => 'تبدأ من الميدان وتعود إلى الناس', + + // Latest Posts Section + 'posts_title' => 'أحدث المقالات', + 'read_more' => 'اقرأ المزيد', + 'view_all_posts' => 'عرض جميع المقالات', + 'posts_empty' => 'المقالات قريباً', + 'values' => [ 'integrity' => [ 'icon' => 'shield-check', diff --git a/lang/en/home.php b/lang/en/home.php index 61c90d7..7e87c6c 100644 --- a/lang/en/home.php +++ b/lang/en/home.php @@ -41,6 +41,13 @@ return [ // Values Section 'values_title' => 'Our Values', 'values_subtitle' => 'These start in the field and return to the people', + + // Latest Posts Section + 'posts_title' => 'Latest Articles', + 'read_more' => 'Read More', + 'view_all_posts' => 'View All Articles', + 'posts_empty' => 'Articles coming soon', + 'values' => [ 'integrity' => [ 'icon' => 'shield-check', diff --git a/resources/views/pages/home.blade.php b/resources/views/livewire/pages/home.blade.php similarity index 66% rename from resources/views/pages/home.blade.php rename to resources/views/livewire/pages/home.blade.php index 720a5ee..303e53f 100644 --- a/resources/views/pages/home.blade.php +++ b/resources/views/livewire/pages/home.blade.php @@ -1,4 +1,23 @@ - + Post::published() + ->latest('published_at') + ->take(3) + ->get(), + ]; + } +}; ?> + +
{{-- Hero Section --}}
@@ -113,4 +132,45 @@
-
+ + {{-- Latest Posts Section --}} + @if($latestPosts->count() > 0) +
+
+
+

+ {{ __('home.posts_title') }} +

+
+ +
+ @foreach($latestPosts as $post) + + @endforeach +
+ + +
+
+ @endif + diff --git a/routes/web.php b/routes/web.php index e2bc9dc..79bec36 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,9 +7,7 @@ use Illuminate\Support\Facades\Route; use Laravel\Fortify\Features; use Livewire\Volt\Volt; -Route::get('/', function () { - return view('pages.home'); -})->name('home'); +Volt::route('/', 'pages.home')->name('home'); Volt::route('/booking', 'pages.booking')->name('booking'); Volt::route('/booking/success', 'pages.booking-success')->name('booking.success'); diff --git a/tests/Feature/Public/HomePageTest.php b/tests/Feature/Public/HomePageTest.php index ec1c851..9e4b7fe 100644 --- a/tests/Feature/Public/HomePageTest.php +++ b/tests/Feature/Public/HomePageTest.php @@ -1,5 +1,7 @@ get('/') ->assertOk(); @@ -369,3 +371,188 @@ test('home page displays social innovation value in Arabic', function () { ->assertOk() ->assertSee('الابتكار الاجتماعي'); }); + +// Latest Posts Section Tests + +test('home page does not display posts section when no published posts exist', function () { + // No posts created + $this->get('/') + ->assertOk() + ->assertDontSee('id="posts"', false); +}); + +test('home page displays posts section when published posts exist', function () { + Post::factory()->published()->count(3)->create(); + + $this->get('/') + ->assertOk() + ->assertSee('id="posts"', false); +}); + +test('home page displays posts section title in English', function () { + Post::factory()->published()->create(); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('Latest Articles'); +}); + +test('home page displays posts section title in Arabic', function () { + Post::factory()->published()->create(); + + $this->withSession(['locale' => 'ar']) + ->get('/') + ->assertOk() + ->assertSee('أحدث المقالات'); +}); + +test('home page displays maximum 3 latest posts', function () { + // Create 5 published posts + Post::factory()->published()->count(5)->create(); + + $response = $this->get('/'); + $response->assertOk(); + + // The section should exist + $response->assertSee('id="posts"', false); + + // Count the number of post cards (articles with specific class) + $content = $response->getContent(); + $postCardCount = substr_count($content, 'class="bg-card p-6 rounded-lg shadow-card hover:shadow-card-hover transition-shadow"'); + expect($postCardCount)->toBe(3); +}); + +test('home page displays post titles', function () { + $post = Post::factory()->published()->create([ + 'title' => ['en' => 'Test English Title', 'ar' => 'عنوان اختبار'], + ]); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('Test English Title'); +}); + +test('home page displays post titles in Arabic', function () { + $post = Post::factory()->published()->create([ + 'title' => ['en' => 'Test English Title', 'ar' => 'عنوان اختبار عربي'], + ]); + + $this->withSession(['locale' => 'ar']) + ->get('/') + ->assertOk() + ->assertSee('عنوان اختبار عربي'); +}); + +test('home page displays post excerpt', function () { + $post = Post::factory()->published()->create([ + 'body' => ['en' => 'This is a test body content for the post that should be truncated.', 'ar' => 'هذا محتوى اختبار'], + ]); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('This is a test body content'); +}); + +test('home page displays read more link in English', function () { + Post::factory()->published()->create(); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('Read More'); +}); + +test('home page displays read more link in Arabic', function () { + Post::factory()->published()->create(); + + $this->withSession(['locale' => 'ar']) + ->get('/') + ->assertOk() + ->assertSee('اقرأ المزيد'); +}); + +test('home page displays view all posts link in English', function () { + Post::factory()->published()->create(); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('View All Articles'); +}); + +test('home page displays view all posts link in Arabic', function () { + Post::factory()->published()->create(); + + $this->withSession(['locale' => 'ar']) + ->get('/') + ->assertOk() + ->assertSee('عرض جميع المقالات'); +}); + +test('home page view all posts links to posts index', function () { + Post::factory()->published()->create(); + + $this->get('/') + ->assertOk() + ->assertSee('href="'.route('posts.index').'"', false); +}); + +test('home page post title links to post show page', function () { + $post = Post::factory()->published()->create(); + + $this->get('/') + ->assertOk() + ->assertSee('href="'.route('posts.show', $post).'"', false); +}); + +test('home page only shows published posts not drafts', function () { + $publishedPost = Post::factory()->published()->create([ + 'title' => ['en' => 'Published Post Title', 'ar' => 'منشور'], + ]); + + $draftPost = Post::factory()->draft()->create([ + 'title' => ['en' => 'Draft Post Title', 'ar' => 'مسودة'], + ]); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('Published Post Title') + ->assertDontSee('Draft Post Title'); +}); + +test('home page displays posts in order of latest published first', function () { + $oldPost = Post::factory()->published()->create([ + 'title' => ['en' => 'Old Post', 'ar' => 'قديم'], + 'published_at' => now()->subDays(10), + ]); + + $newPost = Post::factory()->published()->create([ + 'title' => ['en' => 'New Post', 'ar' => 'جديد'], + 'published_at' => now()->subDay(), + ]); + + $response = $this->withSession(['locale' => 'en'])->get('/'); + $response->assertOk(); + + $content = $response->getContent(); + $newPostPosition = strpos($content, 'New Post'); + $oldPostPosition = strpos($content, 'Old Post'); + + // New post should appear before old post + expect($newPostPosition)->toBeLessThan($oldPostPosition); +}); + +test('home page displays post publication date', function () { + $post = Post::factory()->published()->create([ + 'published_at' => now()->setDate(2026, 1, 15), + ]); + + $this->withSession(['locale' => 'en']) + ->get('/') + ->assertOk() + ->assertSee('15 January 2026'); +});