AITOOL / templates /marketplace.html
NandanData's picture
Upload 54 files
ab3b796 verified
{% extends "base.html" %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="max-w-7xl mx-auto">
<!-- Header -->
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Prompt Marketplace</h1>
<p class="mt-2 text-gray-600 dark:text-gray-300">Discover and purchase high-quality prompts for various AI tools</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-blue-600">{{ user_credits }}</div>
<div class="text-sm text-gray-500">credits available</div>
</div>
</div>
<!-- Filters and Search -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<!-- Search -->
<div class="flex-1">
<div class="relative">
<input type="text" id="search-input"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 pl-10 pr-4 py-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Search prompts...">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clip-rule="evenodd"/>
</svg>
</div>
</div>
</div>
<!-- Category Filter -->
<div class="flex-shrink-0">
<select id="category-filter"
class="rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:ring-blue-500 focus:border-blue-500">
<option value="">All Categories</option>
<option value="text" {% if category == 'text' %}selected{% endif %}>Text Generation</option>
<option value="image" {% if category == 'image' %}selected{% endif %}>Image Generation</option>
<option value="code" {% if category == 'code' %}selected{% endif %}>Code Generation</option>
</select>
</div>
<!-- Sort By -->
<div class="flex-shrink-0">
<select id="sort-by"
class="rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:ring-blue-500 focus:border-blue-500">
<option value="popular" {% if sort_by == 'popular' %}selected{% endif %}>Most Popular</option>
<option value="newest" {% if sort_by == 'newest' %}selected{% endif %}>Newest First</option>
<option value="rating" {% if sort_by == 'rating' %}selected{% endif %}>Highest Rated</option>
</select>
</div>
</div>
</div>
<!-- Prompts Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for prompt in prompts %}
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden prompt-card"
data-category="{{ prompt.tool_id }}"
data-title="{{ prompt.title|lower }}"
data-rating="{{ prompt.avg_rating }}"
data-usage="{{ prompt.usage_count }}"
data-date="{{ prompt.created_at }}">
<!-- Prompt Header -->
<div class="p-6">
<div class="flex items-start justify-between">
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ prompt.title }}</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ prompt.description }}</p>
</div>
<div class="flex items-center">
<span class="text-yellow-500"></span>
<span class="ml-1 text-sm text-gray-600 dark:text-gray-300">{{ "%.1f"|format(prompt.avg_rating) }}</span>
</div>
</div>
<!-- Tags -->
<div class="mt-4 flex flex-wrap gap-2">
{% for tag in prompt.tags %}
<span class="px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 rounded-full">
{{ tag }}
</span>
{% endfor %}
</div>
<!-- Usage Stats -->
<div class="mt-4 flex items-center text-sm text-gray-500 dark:text-gray-400">
<svg class="h-4 w-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
{{ prompt.usage_count }} uses
</div>
</div>
<!-- Preview -->
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-700">
<div class="text-sm text-gray-600 dark:text-gray-300 line-clamp-3">
{{ prompt.content }}
</div>
</div>
<!-- Footer -->
<div class="px-6 py-4 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-500 dark:text-gray-400">
Created by {{ prompt.creator_id }}
</div>
<button onclick="purchasePrompt('{{ prompt.id }}')"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Purchase
</button>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Empty State -->
<div id="empty-state" class="hidden text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No prompts found</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Try adjusting your search or filter criteria
</p>
</div>
</div>
</div>
<!-- Purchase Confirmation Modal -->
<div id="purchase-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center">
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-lg w-full mx-4">
<h2 class="text-xl font-semibold mb-4">Confirm Purchase</h2>
<p class="text-gray-600 dark:text-gray-300 mb-4">
Are you sure you want to purchase this prompt? This will cost 5 credits.
</p>
<div class="flex justify-end space-x-4">
<button id="cancel-purchase"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600">
Cancel
</button>
<button id="confirm-purchase"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">
Confirm Purchase
</button>
</div>
</div>
</div>
<script>
let selectedPromptId = null;
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
setupEventListeners();
setupFilters();
});
// Set up event listeners
function setupEventListeners() {
// Purchase modal
document.getElementById('cancel-purchase').addEventListener('click', hidePurchaseModal);
document.getElementById('confirm-purchase').addEventListener('click', handlePurchase);
}
// Set up filters
function setupFilters() {
const searchInput = document.getElementById('search-input');
const categoryFilter = document.getElementById('category-filter');
const sortBy = document.getElementById('sort-by');
function filterPrompts() {
const searchTerm = searchInput.value.toLowerCase();
const category = categoryFilter.value;
const sortValue = sortBy.value;
const cards = document.querySelectorAll('.prompt-card');
let visibleCount = 0;
cards.forEach(card => {
const title = card.dataset.title;
const cardCategory = card.dataset.category;
const rating = parseFloat(card.dataset.rating);
const usage = parseInt(card.dataset.usage);
const date = new Date(card.dataset.date);
const matchesSearch = title.includes(searchTerm);
const matchesCategory = !category || cardCategory === category;
if (matchesSearch && matchesCategory) {
card.style.display = 'block';
visibleCount++;
} else {
card.style.display = 'none';
}
});
// Show/hide empty state
const emptyState = document.getElementById('empty-state');
emptyState.classList.toggle('hidden', visibleCount > 0);
// Sort visible cards
const container = document.querySelector('.grid');
const visibleCards = Array.from(cards).filter(card => card.style.display !== 'none');
visibleCards.sort((a, b) => {
switch (sortValue) {
case 'rating':
return parseFloat(b.dataset.rating) - parseFloat(a.dataset.rating);
case 'newest':
return new Date(b.dataset.date) - new Date(a.dataset.date);
case 'popular':
default:
return parseInt(b.dataset.usage) - parseInt(a.dataset.usage);
}
});
visibleCards.forEach(card => container.appendChild(card));
}
searchInput.addEventListener('input', filterPrompts);
categoryFilter.addEventListener('change', filterPrompts);
sortBy.addEventListener('change', filterPrompts);
}
// Purchase prompt
function purchasePrompt(promptId) {
selectedPromptId = promptId;
showPurchaseModal();
}
// Show purchase modal
function showPurchaseModal() {
document.getElementById('purchase-modal').classList.remove('hidden');
document.getElementById('purchase-modal').classList.add('flex');
}
// Hide purchase modal
function hidePurchaseModal() {
document.getElementById('purchase-modal').classList.add('hidden');
document.getElementById('purchase-modal').classList.remove('flex');
selectedPromptId = null;
}
// Handle purchase
async function handlePurchase() {
if (!selectedPromptId) return;
try {
const response = await fetch('/api/marketplace/purchase', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt_id: selectedPromptId
})
});
const data = await response.json();
if (data.success) {
showToast('Prompt purchased successfully!', 'success');
hidePurchaseModal();
// Refresh the page to update credits
window.location.reload();
} else {
showToast(data.error || 'Failed to purchase prompt', 'error');
}
} catch (error) {
console.error('Error purchasing prompt:', error);
showToast('Failed to purchase prompt', 'error');
}
}
</script>
{% endblock %}