25 KiB
Reviews Listing Page Implementation Prompt
Django Parity Reference
Django Implementation: reviews/views.py - ReviewListView (similar patterns to other listing views)
Django Template: reviews/templates/reviews/review_list.html
Django Features: Social interaction display, sentiment analysis, review verification, context-aware filtering, real-time engagement metrics
Core Implementation Requirements
Laravel/Livewire Architecture
Generate the reviews listing system using ThrillWiki's custom generators:
# Generate main reviews listing with social interaction support
php artisan make:thrillwiki-livewire ReviewsListing --paginated --cached --with-tests
# Generate social interaction components
php artisan make:thrillwiki-livewire ReviewSocialInteractions --reusable --with-tests
# Generate sentiment analysis display
php artisan make:thrillwiki-livewire ReviewSentimentAnalysis --reusable --cached
# Generate review verification system
php artisan make:thrillwiki-livewire ReviewVerificationBadges --reusable --with-tests
# Generate context-aware filters
php artisan make:thrillwiki-livewire ReviewsContextFilters --reusable --cached
# Generate real-time engagement metrics
php artisan make:thrillwiki-livewire ReviewEngagementMetrics --reusable --with-tests
# Generate review quality indicators
php artisan make:thrillwiki-livewire ReviewQualityIndicators --reusable --cached
# Generate user credibility system
php artisan make:thrillwiki-livewire UserCredibilityBadges --reusable --with-tests
Django Parity Features
1. Social Review Search Functionality
Django Implementation: Multi-faceted search across:
- Review content (
content__icontains) - Reviewer username (
user__username__icontains) - Reviewable entity (
reviewable__name__icontains) - Review tags (
tags__name__icontains) - Experience context (
experience_context__icontains) - Visit verification status (
verified_visit)
Laravel Implementation:
public function socialReviewSearch($query, $context = 'all')
{
return Review::query()
->when($query, function ($q) use ($query) {
$terms = explode(' ', $query);
foreach ($terms as $term) {
$q->where(function ($subQuery) use ($term) {
$subQuery->where('content', 'ilike', "%{$term}%")
->orWhere('title', 'ilike', "%{$term}%")
->orWhere('experience_context', 'ilike', "%{$term}%")
->orWhereHas('user', function($userQuery) use ($term) {
$userQuery->where('username', 'ilike', "%{$term}%")
->orWhere('display_name', 'ilike', "%{$term}%");
})
->orWhereHas('reviewable', function($entityQuery) use ($term) {
$entityQuery->where('name', 'ilike', "%{$term}%");
})
->orWhereHas('tags', function($tagQuery) use ($term) {
$tagQuery->where('name', 'ilike', "%{$term}%");
});
});
}
})
->when($context !== 'all', function ($q) use ($context) {
$q->where('reviewable_type', $this->getModelClass($context));
})
->with([
'user' => fn($q) => $q->with(['profile', 'credibilityBadges']),
'reviewable',
'likes' => fn($q) => $q->with('user:id,username'),
'comments' => fn($q) => $q->with('user:id,username')->limit(3),
'tags',
'verificationBadges'
])
->withCount(['likes', 'dislikes', 'comments', 'shares'])
->addSelect([
'engagement_score' => DB::raw('(likes_count * 2 + comments_count * 3 + shares_count * 4)')
]);
}
2. Advanced Social Filtering
Django Filters:
- Review rating (1-5 stars)
- Verification status (verified, unverified, disputed)
- Sentiment analysis (positive, neutral, negative)
- Social engagement level (high, medium, low)
- Review recency (last_day, last_week, last_month, last_year)
- User credibility level (expert, trusted, verified, new)
- Review context (solo_visit, group_visit, family_visit, enthusiast_visit)
- Review completeness (photos, detailed, brief)
Laravel Filters Implementation:
public function applySocialFilters($query, $filters)
{
return $query
->when($filters['rating_range'] ?? null, function ($q, $range) {
[$min, $max] = explode('-', $range);
$q->whereBetween('rating', [$min, $max]);
})
->when($filters['verification_status'] ?? null, function ($q, $status) {
switch ($status) {
case 'verified':
$q->where('verified_visit', true);
break;
case 'unverified':
$q->where('verified_visit', false);
break;
case 'disputed':
$q->where('verification_disputed', true);
break;
}
})
->when($filters['sentiment'] ?? null, function ($q, $sentiment) {
$sentimentRanges = [
'positive' => [0.6, 1.0],
'neutral' => [0.4, 0.6],
'negative' => [0.0, 0.4]
];
if (isset($sentimentRanges[$sentiment])) {
$q->whereBetween('sentiment_score', $sentimentRanges[$sentiment]);
}
})
->when($filters['engagement_level'] ?? null, function ($q, $level) {
$engagementThresholds = [
'high' => 20,
'medium' => 5,
'low' => 0
];
if (isset($engagementThresholds[$level])) {
$q->havingRaw('(likes_count + comments_count + shares_count) >= ?',
[$engagementThresholds[$level]]);
}
})
->when($filters['recency'] ?? null, function ($q, $recency) {
$timeRanges = [
'last_day' => now()->subDay(),
'last_week' => now()->subWeek(),
'last_month' => now()->subMonth(),
'last_year' => now()->subYear()
];
if (isset($timeRanges[$recency])) {
$q->where('created_at', '>=', $timeRanges[$recency]);
}
})
->when($filters['user_credibility'] ?? null, function ($q, $credibility) {
$q->whereHas('user', function ($userQuery) use ($credibility) {
switch ($credibility) {
case 'expert':
$userQuery->whereHas('credibilityBadges', fn($badge) =>
$badge->where('type', 'expert'));
break;
case 'trusted':
$userQuery->where('trust_score', '>=', 80);
break;
case 'verified':
$userQuery->whereNotNull('email_verified_at');
break;
case 'new':
$userQuery->where('created_at', '>=', now()->subMonths(3));
break;
}
});
})
->when($filters['review_context'] ?? null, function ($q, $context) {
$q->where('visit_context', $context);
})
->when($filters['completeness'] ?? null, function ($q, $completeness) {
switch ($completeness) {
case 'photos':
$q->whereHas('photos');
break;
case 'detailed':
$q->whereRaw('LENGTH(content) > 500');
break;
case 'brief':
$q->whereRaw('LENGTH(content) <= 200');
break;
}
});
}
3. Real-Time Social Engagement Display
Social Metrics:
- Like/dislike counts with user attribution
- Comment threads with nested replies
- Share counts across platforms
- User credibility and verification badges
- Sentiment analysis visualization
- Engagement trend tracking
Screen-Agnostic Design Implementation
Mobile Layout (320px - 767px)
- Social Review Cards: Compact cards with engagement metrics
- Touch Interactions: Swipe-to-like, pull-to-refresh, tap interactions
- Social Actions: Prominent like/comment/share buttons
- User Attribution: Clear reviewer identification with badges
Mobile Component Structure:
<div class="reviews-mobile-layout">
<!-- Social Search Bar -->
<div class="sticky top-0 bg-white dark:bg-gray-900 z-20 p-4">
<livewire:reviews-social-search />
<div class="flex items-center mt-2 space-x-2">
<button wire:click="filterByContext('park')"
class="flex items-center space-x-1 px-3 py-1 {{ $activeContext === 'park' ? 'bg-blue-500 text-white' : 'bg-blue-100 dark:bg-blue-900' }} rounded-full">
<span class="text-sm">Parks</span>
</button>
<button wire:click="filterByContext('ride')"
class="flex items-center space-x-1 px-3 py-1 {{ $activeContext === 'ride' ? 'bg-green-500 text-white' : 'bg-green-100 dark:bg-green-900' }} rounded-full">
<span class="text-sm">Rides</span>
</button>
<button wire:click="toggleVerifiedOnly"
class="flex items-center space-x-1 px-2 py-1 {{ $verifiedOnly ? 'bg-orange-500 text-white' : 'bg-orange-100 dark:bg-orange-900' }} rounded-full">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="text-xs">Verified</span>
</button>
</div>
</div>
<!-- Community Engagement Banner -->
<div class="bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white p-4 m-4 rounded-lg">
<livewire:reviews-community-stats :compact="true" />
</div>
<!-- Quick Filters -->
<div class="horizontal-scroll p-4 pb-2">
<livewire:reviews-quick-filters />
</div>
<!-- Review Cards -->
<div class="space-y-4 p-4">
@foreach($reviews as $review)
<livewire:review-mobile-card :review="$review" :show-social="true" :key="$review->id" />
@endforeach
</div>
<!-- Mobile Pagination -->
<div class="sticky bottom-0 bg-white dark:bg-gray-900 p-4">
{{ $reviews->links('pagination.mobile') }}
</div>
</div>
Tablet Layout (768px - 1023px)
- Social Stream Layout: Two-column review stream with engagement sidebar
- Interactive Comments: Expandable comment threads
- Multi-Touch Gestures: Pinch-to-zoom on photos, swipe between reviews
- Social Activity Feed: Real-time updates on review interactions
Tablet Component Structure:
<div class="reviews-tablet-layout flex h-screen">
<!-- Social Filter Sidebar -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
<div class="p-6">
<livewire:reviews-social-search :advanced="true" />
<div class="mt-6">
<livewire:reviews-context-filters :expanded="true" />
</div>
<div class="mt-6">
<livewire:reviews-social-filters :show-engagement="true" />
</div>
<div class="mt-6">
<livewire:reviews-community-stats :detailed="true" />
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="flex-1 flex flex-col">
<!-- Social Header -->
<div class="bg-white dark:bg-gray-900 p-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<h2 class="text-xl font-semibold">{{ $reviews->total() }} Community Reviews</h2>
<livewire:reviews-engagement-overview />
</div>
<div class="flex items-center space-x-2">
<livewire:reviews-sort-selector />
<livewire:reviews-view-toggle />
</div>
</div>
</div>
<!-- Content Stream -->
<div class="flex-1 overflow-y-auto p-6">
@if($view === 'stream')
<div class="space-y-6">
@foreach($reviews as $review)
<livewire:review-tablet-card :review="$review" :interactive="true" :key="$review->id" />
@endforeach
</div>
@elseif($view === 'sentiment')
<livewire:reviews-sentiment-analysis :reviews="$reviews" />
@else
<livewire:reviews-engagement-dashboard :reviews="$reviews" />
@endif
<div class="mt-6">
{{ $reviews->links() }}
</div>
</div>
</div>
</div>
Desktop Layout (1024px - 1919px)
- Three-Pane Social Layout: Filters + reviews + activity feed
- Advanced Social Features: Real-time notifications, user following
- Rich Interaction: Hover states, contextual menus, drag-and-drop
- Community Moderation: Flagging, reporting, and moderation tools
Desktop Component Structure:
<div class="reviews-desktop-layout flex h-screen">
<!-- Advanced Social Filters -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
<div class="p-6">
<livewire:reviews-social-search :advanced="true" :autocomplete="true" />
<div class="mt-6">
<livewire:reviews-context-filters :advanced="true" :show-statistics="true" />
</div>
<div class="mt-6">
<livewire:reviews-social-filters :advanced="true" :show-engagement="true" />
</div>
</div>
</div>
<!-- Main Content -->
<div class="flex-1 flex flex-col">
<!-- Social Dashboard Header -->
<div class="bg-white dark:bg-gray-900 p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-6">
<h1 class="text-2xl font-bold">{{ $reviews->total() }} Community Reviews</h1>
<livewire:reviews-social-summary />
</div>
<div class="flex items-center space-x-4">
<livewire:reviews-sort-selector :advanced="true" />
<livewire:reviews-view-selector />
<livewire:reviews-moderation-tools />
</div>
</div>
<livewire:reviews-advanced-search />
</div>
<!-- Content Area -->
<div class="flex-1 overflow-y-auto">
@if($view === 'feed')
<div class="p-6 space-y-6">
@foreach($reviews as $review)
<livewire:review-desktop-card :review="$review" :comprehensive="true" :key="$review->id" />
@endforeach
<div class="mt-8">
{{ $reviews->links('pagination.desktop') }}
</div>
</div>
@elseif($view === 'sentiment')
<div class="p-6">
<livewire:reviews-sentiment-dashboard :reviews="$reviews" :interactive="true" />
</div>
@elseif($view === 'moderation')
<div class="p-6">
<livewire:reviews-moderation-dashboard :reviews="$reviews" />
</div>
@else
<div class="p-6">
<livewire:reviews-social-analytics :reviews="$reviews" />
</div>
@endif
</div>
</div>
<!-- Social Activity Panel -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
<div class="p-6">
<livewire:reviews-social-activity />
<div class="mt-6">
<livewire:reviews-trending-topics />
</div>
<div class="mt-6">
<livewire:reviews-featured-reviewers />
</div>
</div>
</div>
</div>
Large Screen Layout (1920px+)
- Dashboard-Style Social Interface: Comprehensive community analytics
- Multi-Panel Views: Simultaneous review streams and analytics
- Advanced Visualizations: Sentiment analysis charts and engagement networks
- Community Management: Advanced moderation and user management tools
Performance Optimization Strategy
Social Engagement Caching
public function mount()
{
$this->socialStats = Cache::remember(
'reviews.social.stats',
now()->addMinutes(15),
fn() => $this->calculateSocialStatistics()
);
$this->trendingTopics = Cache::remember(
'reviews.trending.topics',
now()->addHours(1),
fn() => $this->loadTrendingTopics()
);
}
public function getReviewsProperty()
{
$cacheKey = "reviews.listing." . md5(serialize([
'search' => $this->search,
'filters' => $this->filters,
'context_filter' => $this->contextFilter,
'sort' => $this->sort,
'page' => $this->page,
'user_id' => auth()->id() // For personalized content
]));
return Cache::remember($cacheKey, now()->addMinutes(10), function() {
return $this->socialReviewSearch($this->search, $this->contextFilter)
->applySocialFilters($this->filters)
->orderBy($this->sort['column'], $this->sort['direction'])
->paginate(12);
});
}
Real-Time Social Features
// Optimized query for social engagement data
public function optimizedSocialQuery()
{
return Review::select([
'reviews.*',
DB::raw('COALESCE(likes_count.count, 0) as likes_count'),
DB::raw('COALESCE(comments_count.count, 0) as comments_count'),
DB::raw('COALESCE(shares_count.count, 0) as shares_count'),
DB::raw('(COALESCE(likes_count.count, 0) * 2 +
COALESCE(comments_count.count, 0) * 3 +
COALESCE(shares_count.count, 0) * 4) as engagement_score'),
DB::raw('CASE
WHEN sentiment_score >= 0.6 THEN "positive"
WHEN sentiment_score >= 0.4 THEN "neutral"
ELSE "negative"
END as sentiment_category')
])
->leftJoin(DB::raw('(SELECT review_id, COUNT(*) as count FROM review_likes GROUP BY review_id) as likes_count'),
'reviews.id', '=', 'likes_count.review_id')
->leftJoin(DB::raw('(SELECT review_id, COUNT(*) as count FROM review_comments GROUP BY review_id) as comments_count'),
'reviews.id', '=', 'comments_count.review_id')
->leftJoin(DB::raw('(SELECT review_id, COUNT(*) as count FROM review_shares GROUP BY review_id) as shares_count'),
'reviews.id', '=', 'shares_count.review_id')
->with([
'user:id,username,display_name,avatar_url',
'user.credibilityBadges:id,user_id,type,title',
'reviewable:id,name,type',
'verificationBadges:id,review_id,type,verified_at',
'recentLikes' => fn($q) => $q->with('user:id,username')->limit(5),
'topComments' => fn($q) => $q->with('user:id,username')->orderBy('likes_count', 'desc')->limit(3)
]);
}
Component Reuse Strategy
Shared Components
ReviewSocialInteractions: Like/comment/share functionality across all review contextsReviewVerificationBadges: Trust and verification indicators for authentic reviewsReviewEngagementMetrics: Real-time engagement tracking and displayUserCredibilityBadges: User reputation and expertise indicators
Context Variations
ParkReviewsListing: Park-specific reviews with location contextRideReviewsListing: Ride-specific reviews with experience contextUserReviewsListing: User profile reviews with credibility focusFeaturedReviewsListing: High-engagement reviews with community highlights
Testing Requirements
Feature Tests
/** @test */
public function can_filter_reviews_by_social_engagement()
{
$highEngagement = Review::factory()->create(['content' => 'Amazing experience!']);
$highEngagement->likes()->createMany(15, ['user_id' => User::factory()]);
$highEngagement->comments()->createMany(8, ['user_id' => User::factory()]);
$lowEngagement = Review::factory()->create(['content' => 'Okay ride']);
$lowEngagement->likes()->create(['user_id' => User::factory()]);
Livewire::test(ReviewsListing::class)
->set('filters.engagement_level', 'high')
->assertSee($highEngagement->content)
->assertDontSee($lowEngagement->content);
}
/** @test */
public function displays_user_credibility_correctly()
{
$expertUser = User::factory()->create(['username' => 'expert_reviewer']);
$expertUser->credibilityBadges()->create(['type' => 'expert', 'title' => 'Theme Park Expert']);
$expertReview = Review::factory()->create([
'user_id' => $expertUser->id,
'content' => 'Professional analysis'
]);
Livewire::test(ReviewsListing::class)
->assertSee('Theme Park Expert')
->assertSee($expertReview->content);
}
/** @test */
public function maintains_django_parity_performance_with_social_data()
{
Review::factory()->count(30)->create();
$start = microtime(true);
Livewire::test(ReviewsListing::class);
$end = microtime(true);
$this->assertLessThan(0.5, $end - $start); // < 500ms with social data
}
Social Interaction Tests
/** @test */
public function calculates_engagement_scores_accurately()
{
$review = Review::factory()->create();
$review->likes()->createMany(10, ['user_id' => User::factory()]);
$review->comments()->createMany(5, ['user_id' => User::factory()]);
$review->shares()->createMany(2, ['user_id' => User::factory()]);
$component = Livewire::test(ReviewsListing::class);
$reviewData = $component->get('reviews')->first();
// Engagement score = (likes * 2) + (comments * 3) + (shares * 4)
$expectedScore = (10 * 2) + (5 * 3) + (2 * 4); // 43
$this->assertEquals($expectedScore, $reviewData->engagement_score);
}
/** @test */
public function handles_real_time_social_updates()
{
$review = Review::factory()->create();
$component = Livewire::test(ReviewsListing::class);
// Simulate real-time like
$review->likes()->create(['user_id' => User::factory()->create()]);
$component->call('refreshEngagement', $review->id)
->assertSee('1 like');
}
Performance Targets
Universal Performance Standards with Social Features
- Initial Load: < 500ms (including engagement metrics)
- Social Interaction Response: < 200ms for like/comment actions
- Real-time Updates: < 100ms for engagement refresh
- Sentiment Analysis: < 150ms for sentiment visualization
- Community Statistics: < 100ms (cached)
Social Content Caching Strategy
- Engagement Metrics: 10 minutes (frequently changing)
- Trending Topics: 1 hour (community trends)
- User Credibility: 6 hours (reputation changes slowly)
- Social Statistics: 15 minutes (community activity)
Success Criteria Checklist
Django Parity Verification
- Social review search matches Django behavior exactly
- Engagement metrics calculated identically to Django
- Verification systems work like Django implementation
- Sentiment analysis provides same results as Django
- Community features match Django social functionality
Screen-Agnostic Compliance
- Mobile layout optimized for social interaction
- Tablet layout provides effective community browsing
- Desktop layout maximizes social engagement features
- Large screen layout provides comprehensive community management
- All layouts handle real-time social updates gracefully
Performance Benchmarks
- Initial load under 500ms including social data
- Social interactions under 200ms response time
- Real-time updates under 100ms
- Community statistics under 100ms (cached)
- Social caching reduces server load by 70%
Social Feature Completeness
- Engagement metrics display accurately across all contexts
- User credibility systems provide meaningful trust indicators
- Verification badges work for authentic experience validation
- Community moderation tools function effectively
- Real-time social updates work seamlessly across devices
This prompt ensures complete Django parity while providing comprehensive social review capabilities that foster authentic community engagement while maintaining ThrillWiki's screen-agnostic design principles.