libra/docs/stories/story-14.5-latest-posts-sec...

247 lines
6.7 KiB
Markdown

# Story 14.5: Latest Posts Section
## Story
**As a** website visitor
**I want to** see recent legal articles and updates on the home page
**So that** I can access valuable legal content and see that the firm is active
## Acceptance Criteria
### AC1: Section Container
**Given** a visitor scrolls to the latest posts section
**When** viewing the section
**Then** display with:
- Warm Cream (`#F4F1EA`) background
- Section ID `id="posts"` for anchor linking
- Adequate padding (64px vertical on desktop)
### AC2: Section Heading
**Given** the latest posts section
**When** displayed
**Then** show a section heading:
- English: "Latest Articles" or "Legal Insights"
- Arabic: "أحدث المقالات" or "رؤى قانونية"
- Typography: H2, SemiBold, Forest Green (`#2D322A`)
- Centered alignment
### AC3: Display 3 Latest Posts
**Given** the posts section
**When** posts exist in the database
**Then** display the 3 most recent published posts showing:
- Post title
- Publication date
- Brief excerpt (first 100-150 characters of body)
- "Read More" link
### AC4: Post Card Design
**Given** each post card
**When** displayed
**Then** include:
- Card background: White (`#FFFFFF`)
- Title: Bold, Forest Green (`#2D322A`), clickable link
- Date: Small text, muted color
- Excerpt: Body text, truncated with ellipsis
- "Read More" link: Warm Gold (`#A68966`)
- Subtle shadow and border-radius
- Hover effect on card or title
### AC5: Empty State
**Given** the posts section
**When** no published posts exist
**Then** either:
- Hide the entire section, OR
- Show a message: "Articles coming soon" / "المقالات قريباً"
### AC6: View All Link
**Given** the posts section
**When** posts are displayed
**Then** show a "View All Articles" / "عرض جميع المقالات" link:
- Links to `/posts`
- Styled as secondary button or text link with arrow
- Positioned below the post cards, centered
### AC7: Grid Layout
**Given** different screen sizes
**When** viewing the posts section:
- **Desktop (≥992px):** 3 columns (1 row of 3 cards)
- **Tablet (576-991px):** 2 columns + 1 below, or 3 columns
- **Mobile (<576px):** 1 column (3 rows, stacked)
### AC8: RTL Support
**Given** Arabic language is selected
**When** viewing the posts section
**Then** cards and text align right-to-left correctly
### AC9: Dynamic Data
**Given** the posts section
**When** rendered
**Then** query posts from database:
```php
Post::where('status', 'published')
->latest()
->take(3)
->get()
```
## Technical Notes
### Files to Modify
- `resources/views/pages/home.blade.php` - Add posts section with data query
- `lang/en/home.php` - Add translations
- `lang/ar/home.php` - Add translations
### Controller/Page Component
The home page needs to pass posts data. Options:
1. Use a Livewire/Volt component to fetch posts
2. Pass posts via route closure or controller
**Option 1: Volt Component (Recommended)**
Convert `home.blade.php` to a Volt component:
```php
<?php
use App\Models\Post;
use Livewire\Volt\Component;
new class extends Component {
public function with(): array
{
return [
'latestPosts' => Post::where('status', 'published')
->latest()
->take(3)
->get(),
];
}
}; ?>
```
**Option 2: Route with data**
```php
// routes/web.php
Route::get('/', function () {
return view('pages.home', [
'latestPosts' => Post::where('status', 'published')
->latest()
->take(3)
->get(),
]);
})->name('home');
```
### HTML Structure
```blade
@if($latestPosts->count() > 0)
<section id="posts" class="py-16 lg:py-20 bg-background">
<div class="container mx-auto px-4">
<div class="text-center mb-12">
<h2 class="text-2xl lg:text-3xl font-semibold text-text mb-4">
{{ __('home.posts_title') }}
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
@foreach($latestPosts as $post)
<article class="bg-card p-6 rounded-lg shadow-card">
<time class="text-xs text-body/70 mb-2 block">
{{ $post->created_at->translatedFormat('j F Y') }}
</time>
<h3 class="text-lg font-bold text-text mb-3">
<a href="{{ route('posts.show', $post) }}" class="hover:text-cta transition-colors">
{{ $post->title }}
</a>
</h3>
<p class="text-body text-sm mb-4 line-clamp-3">
{{ Str::limit(strip_tags($post->body), 150) }}
</p>
<a href="{{ route('posts.show', $post) }}" class="text-cta font-medium text-sm hover:text-cta-hover">
{{ __('home.read_more') }} →
</a>
</article>
@endforeach
</div>
<div class="text-center">
<flux:button variant="ghost" href="{{ route('posts.index') }}">
{{ __('home.view_all_posts') }}
</flux:button>
</div>
</div>
</section>
@endif
```
### Translation Keys
```php
// lang/en/home.php (additions)
'posts_title' => 'Latest Articles',
'read_more' => 'Read More',
'view_all_posts' => 'View All Articles',
'posts_empty' => 'Articles coming soon',
// lang/ar/home.php (additions)
'posts_title' => 'أحدث المقالات',
'read_more' => 'اقرأ المزيد',
'view_all_posts' => 'عرض جميع المقالات',
'posts_empty' => 'المقالات قريباً',
```
### Post Model Reference
Existing Post model fields:
- `title` - Post title (bilingual stored via JSON or separate fields)
- `body` - Post content
- `status` - 'draft' or 'published'
- `created_at` - Timestamp
### Date Formatting
Use Laravel's `translatedFormat()` for bilingual dates:
```php
$post->created_at->translatedFormat('j F Y')
// English: "15 January 2026"
// Arabic: "15 يناير 2026"
```
## 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
## Estimation
**Complexity:** Medium (requires data fetching)
**Risk:** Low - Uses existing Post model and routes
## Dependencies
- Previous stories for page structure
- Existing Post model
- Posts routes (`posts.index`, `posts.show`)