Add ratings, reviews, and publisher reputation system
- Add database schema for reviews, issues, and stats caching - Add API endpoints for reviews (CRUD, voting, flagging) - Add API endpoints for issues (report, list, resolve) - Add publisher stats and badge system - Add trust score calculation (0-100 scale) - Update tool detail page with ratings, reviews, issues sections - Update publisher profile with stats, badges, trust score - Add dedicated reviews and issues pages with filtering - Update dashboard tools table with rating/issues columns - Update tool cards with inline rating display Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
<p class="mt-4 text-gray-600">{{ publisher.bio }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-4 flex items-center gap-4 text-sm text-gray-500">
|
||||
<div class="mt-4 flex flex-wrap items-center gap-4 text-sm text-gray-500">
|
||||
{% if publisher.website %}
|
||||
<a href="{{ publisher.website }}"
|
||||
target="_blank"
|
||||
@@ -56,7 +56,92 @@
|
||||
</svg>
|
||||
Joined {{ publisher.created_at|date_format }}
|
||||
</span>
|
||||
{% if stats.total_downloads %}
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||
</svg>
|
||||
{{ stats.total_downloads_formatted or stats.total_downloads }} downloads
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if stats.average_rating and stats.total_reviews %}
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
||||
</svg>
|
||||
{{ "%.1f"|format(stats.average_rating) }} ({{ stats.total_reviews }} review{{ 's' if stats.total_reviews != 1 else '' }})
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Badges -->
|
||||
{% if stats.badges %}
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
{% for badge in stats.badges %}
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium
|
||||
{{ 'bg-blue-100 text-blue-800' if badge.color == 'blue' else
|
||||
'bg-yellow-100 text-yellow-800' if badge.color == 'gold' or badge.color == 'yellow' else
|
||||
'bg-purple-100 text-purple-800' if badge.color == 'purple' else
|
||||
'bg-green-100 text-green-800' if badge.color == 'green' else
|
||||
'bg-cyan-100 text-cyan-800' if badge.color == 'cyan' else
|
||||
'bg-gray-100 text-gray-800' }}"
|
||||
title="{{ badge.description }}">
|
||||
{% if badge.icon == 'shield-check' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
||||
</svg>
|
||||
{% elif badge.icon == 'award' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/>
|
||||
</svg>
|
||||
{% elif badge.icon == 'layers' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
||||
</svg>
|
||||
{% elif badge.icon == 'clock' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
{% elif badge.icon == 'trending-up' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
|
||||
</svg>
|
||||
{% elif badge.icon == 'message-circle' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
{% elif badge.icon == 'star' %}
|
||||
<svg class="w-3.5 h-3.5 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
{{ badge.name }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Trust Score -->
|
||||
{% if stats.trust_score and stats.trust_score > 0 %}
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-500">Trust Score:</span>
|
||||
<div class="flex-1 max-w-xs h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div class="h-full rounded-full
|
||||
{{ 'bg-green-500' if stats.trust_score >= 80 else
|
||||
'bg-yellow-500' if stats.trust_score >= 50 else
|
||||
'bg-red-500' }}"
|
||||
style="width: {{ stats.trust_score }}%"></div>
|
||||
</div>
|
||||
<span class="text-sm font-medium
|
||||
{{ 'text-green-600' if stats.trust_score >= 80 else
|
||||
'text-yellow-600' if stats.trust_score >= 50 else
|
||||
'text-red-600' }}">
|
||||
{{ stats.trust_score|int }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user