implement marketplace feature wip
This commit is contained in:
@@ -91,6 +91,7 @@
|
||||
<li><a class="dropdown-item" href="/tickets/new">New Ticket</a></li>
|
||||
<li><a class="dropdown-item" href="/my-tickets">My Tickets</a></li>
|
||||
<li><a class="dropdown-item" href="/assets/my">My Assets</a></li>
|
||||
<li><a class="dropdown-item" href="/marketplace/my">My Listings</a></li>
|
||||
<li><a class="dropdown-item" href="/governance/my-votes">My Votes</a></li>
|
||||
{% if user.role == "Admin" %}
|
||||
<li><a class="dropdown-item" href="/admin">Admin Panel</a></li>
|
||||
@@ -149,6 +150,11 @@
|
||||
<i class="bi bi-coin me-2"></i> Digital Assets
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'marketplace' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/marketplace">
|
||||
<i class="bi bi-shop me-2"></i> Marketplace
|
||||
</a>
|
||||
</li>
|
||||
<!-- Markdown Editor link hidden
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'editor' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/editor">
|
||||
|
236
actix_mvc_app/src/views/marketplace/create_listing.html
Normal file
236
actix_mvc_app/src/views/marketplace/create_listing.html
Normal file
@@ -0,0 +1,236 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Create Marketplace Listing{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Create New Listing</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item active">Create Listing</li>
|
||||
</ol>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
Listing Details
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="/marketplace/create" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Listing Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
<div class="form-text">A clear, descriptive title for your listing.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="asset_id" class="form-label">Select Asset</label>
|
||||
<select class="form-select" id="asset_id" name="asset_id" required>
|
||||
<option value="" selected disabled>Choose an asset to list</option>
|
||||
{% for asset in assets %}
|
||||
<option value="{{ asset.id }}" data-type="{{ asset.asset_type.as_str() }}" data-image="{{ asset.image_url }}">
|
||||
{{ asset.name }} ({{ asset.asset_type.as_str() }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text">Select one of your assets to list on the marketplace.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="4" required></textarea>
|
||||
<div class="form-text">Provide a detailed description of what you're selling.</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="price" class="form-label">Price</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" class="form-control" id="price" name="price" step="0.01" min="0.01" required>
|
||||
</div>
|
||||
<div class="form-text">Set a fair price for your asset.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="currency" class="form-label">Currency</label>
|
||||
<select class="form-select" id="currency" name="currency" required>
|
||||
<option value="USD" selected>USD</option>
|
||||
<option value="EUR">EUR</option>
|
||||
<option value="BTC">BTC</option>
|
||||
<option value="ETH">ETH</option>
|
||||
<option value="TFT">TFT</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="listing_type" class="form-label">Listing Type</label>
|
||||
<select class="form-select" id="listing_type" name="listing_type" required>
|
||||
{% for type in listing_types %}
|
||||
<option value="{{ type }}">{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text">Choose how you want to sell your asset.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="duration_days" class="form-label">Duration (Days)</label>
|
||||
<input type="number" class="form-control" id="duration_days" name="duration_days" min="1" max="90" value="30">
|
||||
<div class="form-text">How long should this listing be active? (1-90 days)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tags" class="form-label">Tags</label>
|
||||
<input type="text" class="form-control" id="tags" name="tags" placeholder="digital, rare, collectible">
|
||||
<div class="form-text">Comma-separated tags to help buyers find your listing.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="terms" required>
|
||||
<label class="form-check-label" for="terms">
|
||||
I agree to the <a href="#" target="_blank">marketplace terms and conditions</a>.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="/marketplace" class="btn btn-secondary me-md-2">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Create Listing</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<!-- Asset Preview -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-eye"></i>
|
||||
Asset Preview
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div id="asset-preview-container">
|
||||
<div class="bg-light d-flex align-items-center justify-content-center rounded mb-3" style="height: 200px;">
|
||||
<i class="bi bi-image text-secondary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<p class="text-muted">Select an asset to preview</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Listing Tips -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-lightbulb"></i>
|
||||
Listing Tips
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-2"></i>
|
||||
Use a clear, descriptive title
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-2"></i>
|
||||
Include detailed information about your asset
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-2"></i>
|
||||
Set a competitive price
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-2"></i>
|
||||
Add relevant tags to improve discoverability
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-2"></i>
|
||||
Choose the right listing type for your asset
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const assetSelect = document.getElementById('asset_id');
|
||||
const previewContainer = document.getElementById('asset-preview-container');
|
||||
const listingTypeSelect = document.getElementById('listing_type');
|
||||
|
||||
// Update preview when asset is selected
|
||||
assetSelect.addEventListener('change', function() {
|
||||
const selectedOption = assetSelect.options[assetSelect.selectedIndex];
|
||||
const assetType = selectedOption.getAttribute('data-type');
|
||||
const imageUrl = selectedOption.getAttribute('data-image');
|
||||
const assetName = selectedOption.text;
|
||||
|
||||
let previewHtml = '';
|
||||
|
||||
if (imageUrl) {
|
||||
previewHtml = `
|
||||
<img src="${imageUrl}" class="img-fluid rounded mb-3" alt="${assetName}" style="max-height: 200px;">
|
||||
`;
|
||||
} else {
|
||||
previewHtml = `
|
||||
<div class="bg-light d-flex align-items-center justify-content-center rounded mb-3" style="height: 200px;">
|
||||
<i class="bi bi-collection text-secondary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
previewHtml += `
|
||||
<h5>${assetName}</h5>
|
||||
<span class="badge bg-primary mb-2">${assetType}</span>
|
||||
<p class="text-muted">This is how your asset will appear to buyers.</p>
|
||||
`;
|
||||
|
||||
previewContainer.innerHTML = previewHtml;
|
||||
|
||||
// Suggest listing type based on asset type
|
||||
if (assetType === 'NFT') {
|
||||
listingTypeSelect.value = 'Auction';
|
||||
} else if (assetType === 'Token') {
|
||||
listingTypeSelect.value = 'Fixed Price';
|
||||
} else if (assetType === 'RealEstate') {
|
||||
listingTypeSelect.value = 'Fixed Price';
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide duration field based on listing type
|
||||
listingTypeSelect.addEventListener('change', function() {
|
||||
const durationField = document.getElementById('duration_days');
|
||||
const durationFieldParent = durationField.parentElement;
|
||||
|
||||
if (listingTypeSelect.value === 'Auction') {
|
||||
durationFieldParent.style.display = 'block';
|
||||
durationField.required = true;
|
||||
if (!durationField.value) {
|
||||
durationField.value = 7; // Default auction duration
|
||||
}
|
||||
} else if (listingTypeSelect.value === 'Exchange') {
|
||||
durationFieldParent.style.display = 'block';
|
||||
durationField.required = true;
|
||||
if (!durationField.value) {
|
||||
durationField.value = 30; // Default exchange duration
|
||||
}
|
||||
} else {
|
||||
// For fixed price, duration is optional
|
||||
durationFieldParent.style.display = 'block';
|
||||
durationField.required = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
293
actix_mvc_app/src/views/marketplace/index.html
Normal file
293
actix_mvc_app/src/views/marketplace/index.html
Normal file
@@ -0,0 +1,293 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Digital Assets Marketplace{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Digital Assets Marketplace</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item active">Marketplace</li>
|
||||
</ol>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-primary text-white mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="display-4">{{ stats.active_listings }}</h2>
|
||||
<p class="mb-0">Active Listings</p>
|
||||
</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<a class="small text-white stretched-link" href="/marketplace/listings">View Details</a>
|
||||
<div class="small text-white"><i class="bi bi-arrow-right"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-success text-white mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="display-4">${{ stats.total_value }}</h2>
|
||||
<p class="mb-0">Total Market Value</p>
|
||||
</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<a class="small text-white stretched-link" href="/marketplace/listings">View Details</a>
|
||||
<div class="small text-white"><i class="bi bi-arrow-right"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-warning text-white mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="display-4">{{ stats.total_listings }}</h2>
|
||||
<p class="mb-0">Total Listings</p>
|
||||
</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<a class="small text-white stretched-link" href="/marketplace/listings">View Details</a>
|
||||
<div class="small text-white"><i class="bi bi-arrow-right"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card bg-info text-white mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="display-4">${{ stats.total_sales }}</h2>
|
||||
<p class="mb-0">Total Sales</p>
|
||||
</div>
|
||||
<div class="card-footer d-flex align-items-center justify-content-between">
|
||||
<a class="small text-white stretched-link" href="/marketplace/listings">View Details</a>
|
||||
<div class="small text-white"><i class="bi bi-arrow-right"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-lightning-charge"></i>
|
||||
Quick Actions
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-around">
|
||||
<a href="/marketplace/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> List New Asset
|
||||
</a>
|
||||
<a href="/marketplace/listings" class="btn btn-success">
|
||||
<i class="bi bi-search"></i> Browse Listings
|
||||
</a>
|
||||
<a href="/marketplace/my" class="btn btn-info">
|
||||
<i class="bi bi-person"></i> My Listings
|
||||
</a>
|
||||
<a href="/assets/my" class="btn btn-secondary">
|
||||
<i class="bi bi-collection"></i> My Assets
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Featured Listings -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-star"></i>
|
||||
Featured Listings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% if featured_listings|length > 0 %}
|
||||
{% for listing in featured_listings %}
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="badge bg-warning text-dark position-absolute" style="top: 0.5rem; right: 0.5rem">Featured</div>
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" class="card-img-top" alt="{{ listing.title }}" style="height: 180px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="card-img-top bg-light d-flex align-items-center justify-content-center" style="height: 180px;">
|
||||
<i class="bi bi-image text-secondary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ listing.title }}</h5>
|
||||
<p class="card-text text-truncate">{{ listing.description }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="badge bg-primary">{{ listing.listing_type.as_str() }}</span>
|
||||
<span class="badge bg-secondary">{{ listing.asset_type.as_str() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>${{ listing.price }}</strong>
|
||||
<a href="/marketplace/{{ listing.id }}" class="btn btn-sm btn-outline-primary">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<p class="text-center">No featured listings available at this time.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Listings and Sales -->
|
||||
<div class="row">
|
||||
<!-- Recent Listings -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-clock"></i>
|
||||
Recent Listings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Asset</th>
|
||||
<th>Type</th>
|
||||
<th>Price</th>
|
||||
<th>Listing Type</th>
|
||||
<th>Seller</th>
|
||||
<th>Listed</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if recent_listings|length > 0 %}
|
||||
{% for listing in recent_listings %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" alt="{{ listing.asset_name }}" class="me-2" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<i class="bi bi-collection me-2"></i>
|
||||
{% endif %}
|
||||
{{ listing.asset_name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if listing.asset_type.as_str() == "Token" %}
|
||||
<span class="badge bg-primary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "NFT" %}
|
||||
<span class="badge bg-info">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "RealEstate" %}
|
||||
<span class="badge bg-success">Real Estate</span>
|
||||
{% elif listing.asset_type.as_str() == "IntellectualProperty" %}
|
||||
<span class="badge bg-warning text-dark">IP</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>${{ listing.price }}</td>
|
||||
<td>{{ listing.listing_type.as_str() }}</td>
|
||||
<td>{{ listing.seller_name }}</td>
|
||||
<td>{{ listing.created_at|date }}</td>
|
||||
<td>
|
||||
<a href="/marketplace/{{ listing.id }}" class="btn btn-sm btn-outline-primary">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">No recent listings available.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/marketplace/listings" class="btn btn-sm btn-primary">View All Listings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Sales -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-bag-check"></i>
|
||||
Recent Sales
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Asset</th>
|
||||
<th>Price</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if recent_sales|length > 0 %}
|
||||
{% for listing in recent_sales %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" alt="{{ listing.asset_name }}" class="me-2" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<i class="bi bi-collection me-2"></i>
|
||||
{% endif %}
|
||||
{{ listing.asset_name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>${{ listing.sale_price }}</td>
|
||||
<td>{{ listing.sold_at|date }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center">No recent sales available.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Listing Types Distribution -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-pie-chart"></i>
|
||||
Listing Types
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for type, count in stats.listings_by_type %}
|
||||
<tr>
|
||||
<td>{{ type }}</td>
|
||||
<td>{{ count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
350
actix_mvc_app/src/views/marketplace/listing_detail.html
Normal file
350
actix_mvc_app/src/views/marketplace/listing_detail.html
Normal file
@@ -0,0 +1,350 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ listing.title }} | Marketplace{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Listing Details</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace/listings">Listings</a></li>
|
||||
<li class="breadcrumb-item active">{{ listing.title }}</li>
|
||||
</ol>
|
||||
|
||||
<!-- Listing Details -->
|
||||
<div class="row">
|
||||
<!-- Left Column: Image and Actions -->
|
||||
<div class="col-md-5">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body text-center">
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" alt="{{ listing.title }}" class="img-fluid rounded mb-3" style="max-height: 350px; object-fit: contain;">
|
||||
{% else %}
|
||||
<div class="bg-light d-flex align-items-center justify-content-center rounded mb-3" style="height: 350px;">
|
||||
<i class="bi bi-image text-secondary" style="font-size: 5rem;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
{% if listing.listing_type.as_str() == "Fixed Price" %}
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#purchaseModal">
|
||||
<i class="bi bi-cart"></i> Purchase Now
|
||||
</button>
|
||||
{% elif listing.listing_type.as_str() == "Auction" %}
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#bidModal">
|
||||
<i class="bi bi-hammer"></i> Place Bid
|
||||
</button>
|
||||
{% elif listing.listing_type.as_str() == "Exchange" %}
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#offerModal">
|
||||
<i class="bi bi-arrow-left-right"></i> Make Exchange Offer
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if listing.seller_id == user_id %}
|
||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#cancelModal">
|
||||
<i class="bi bi-x-circle"></i> Cancel Listing
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Asset Information -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Asset Information
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Asset Name:</strong> {{ listing.asset_name }}</p>
|
||||
<p><strong>Asset Type:</strong>
|
||||
{% if listing.asset_type.as_str() == "Token" %}
|
||||
<span class="badge bg-primary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "NFT" %}
|
||||
<span class="badge bg-info">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "RealEstate" %}
|
||||
<span class="badge bg-success">Real Estate</span>
|
||||
{% elif listing.asset_type.as_str() == "IntellectualProperty" %}
|
||||
<span class="badge bg-warning text-dark">Intellectual Property</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p><strong>Asset ID:</strong> <code>{{ listing.asset_id }}</code></p>
|
||||
<a href="/assets/{{ listing.asset_id }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i> View Asset Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Details and Bids -->
|
||||
<div class="col-md-7">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<i class="bi bi-tag"></i>
|
||||
Listing Details
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge bg-{{ listing.status.as_str() == 'Active' ? 'success' : 'secondary' }}">
|
||||
{{ listing.status.as_str() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-3">{{ listing.title }}</h2>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<span class="badge bg-primary">{{ listing.listing_type.as_str() }}</span>
|
||||
{% if listing.featured %}
|
||||
<span class="badge bg-warning text-dark">Featured</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-primary mb-0">${{ listing.price }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="card-text">{{ listing.description }}</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<p><strong>Seller:</strong> {{ listing.seller_name }}</p>
|
||||
<p><strong>Listed:</strong> {{ listing.created_at|date }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p><strong>Currency:</strong> {{ listing.currency }}</p>
|
||||
<p><strong>Expires:</strong>
|
||||
{% if listing.expires_at %}
|
||||
{{ listing.expires_at|date }}
|
||||
{% else %}
|
||||
<span class="text-muted">No expiration</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if listing.tags|length > 0 %}
|
||||
<div class="mb-3">
|
||||
<strong>Tags:</strong>
|
||||
{% for tag in listing.tags %}
|
||||
<span class="badge bg-secondary me-1">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bids Section (for Auctions) -->
|
||||
{% if listing.listing_type.as_str() == "Auction" %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-list-ol"></i>
|
||||
Bids
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if listing.bids|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bidder</th>
|
||||
<th>Amount</th>
|
||||
<th>Time</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bid in listing.bids %}
|
||||
<tr>
|
||||
<td>{{ bid.bidder_name }}</td>
|
||||
<td>${{ bid.amount }}</td>
|
||||
<td>{{ bid.created_at|date }}</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ bid.status.as_str() == 'Active' ? 'success' : 'secondary' }}">
|
||||
{{ bid.status.as_str() }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-2"><strong>Current Highest Bid:</strong> ${{ listing.highest_bid().amount }}</p>
|
||||
{% else %}
|
||||
<p>No bids yet. Be the first to bid!</p>
|
||||
<p><strong>Starting Price:</strong> ${{ listing.price }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Similar Listings -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-grid"></i>
|
||||
Similar Listings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% if similar_listings|length > 0 %}
|
||||
{% for similar in similar_listings %}
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100">
|
||||
{% if similar.image_url %}
|
||||
<img src="{{ similar.image_url }}" class="card-img-top" alt="{{ similar.title }}" style="height: 150px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="card-img-top bg-light d-flex align-items-center justify-content-center" style="height: 150px;">
|
||||
<i class="bi bi-image text-secondary" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ similar.title }}</h5>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="badge bg-primary">{{ similar.listing_type.as_str() }}</span>
|
||||
<span class="badge bg-secondary">{{ similar.asset_type.as_str() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>${{ similar.price }}</strong>
|
||||
<a href="/marketplace/{{ similar.id }}" class="btn btn-sm btn-outline-primary">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<p class="text-center">No similar listings found.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Purchase Modal -->
|
||||
<div class="modal fade" id="purchaseModal" tabindex="-1" aria-labelledby="purchaseModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="purchaseModalLabel">Purchase Asset</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="/marketplace/{{ listing.id }}/purchase" method="post">
|
||||
<div class="modal-body">
|
||||
<p>You are about to purchase <strong>{{ listing.asset_name }}</strong> for <strong>${{ listing.price }}</strong>.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h6>Purchase Details:</h6>
|
||||
<ul>
|
||||
<li>Asset: {{ listing.asset_name }}</li>
|
||||
<li>Price: ${{ listing.price }} {{ listing.currency }}</li>
|
||||
<li>Seller: {{ listing.seller_name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="agree-terms" name="agree_to_terms" required>
|
||||
<label class="form-check-label" for="agree-terms">
|
||||
I agree to the <a href="#" target="_blank">terms and conditions</a> of this purchase.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Confirm Purchase</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bid Modal -->
|
||||
<div class="modal fade" id="bidModal" tabindex="-1" aria-labelledby="bidModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="bidModalLabel">Place Bid</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="/marketplace/{{ listing.id }}/bid" method="post">
|
||||
<div class="modal-body">
|
||||
<p>You are placing a bid on <strong>{{ listing.asset_name }}</strong>.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h6>Auction Details:</h6>
|
||||
<ul>
|
||||
<li>Asset: {{ listing.asset_name }}</li>
|
||||
<li>Starting Price: ${{ listing.price }} {{ listing.currency }}</li>
|
||||
{% if listing.highest_bid() %}
|
||||
<li>Current Highest Bid: ${{ listing.highest_bid().amount }} {{ listing.currency }}</li>
|
||||
<li>Minimum Bid: ${{ listing.highest_bid().amount + 1 }} {{ listing.currency }}</li>
|
||||
{% else %}
|
||||
<li>Minimum Bid: ${{ listing.price + 1 }} {{ listing.currency }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="bid-amount" class="form-label">Your Bid Amount ({{ listing.currency }})</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" class="form-control" id="bid-amount" name="amount" step="0.01" min="{{ listing.highest_bid() ? listing.highest_bid().amount + 1 : listing.price + 1 }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="currency" value="{{ listing.currency }}">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Place Bid</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cancel Modal -->
|
||||
<div class="modal fade" id="cancelModal" tabindex="-1" aria-labelledby="cancelModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="cancelModalLabel">Cancel Listing</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="/marketplace/{{ listing.id }}/cancel" method="post">
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to cancel this listing for <strong>{{ listing.asset_name }}</strong>?</p>
|
||||
<div class="alert alert-warning">
|
||||
<p>This action cannot be undone. The listing will be marked as cancelled and removed from the marketplace.</p>
|
||||
{% if listing.bids|length > 0 %}
|
||||
<p><strong>Note:</strong> This listing has active bids. Cancelling will notify all bidders.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No, Keep Listing</button>
|
||||
<button type="submit" class="btn btn-danger">Yes, Cancel Listing</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
294
actix_mvc_app/src/views/marketplace/listings.html
Normal file
294
actix_mvc_app/src/views/marketplace/listings.html
Normal file
@@ -0,0 +1,294 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Marketplace Listings{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Marketplace Listings</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item active">Listings</li>
|
||||
</ol>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-funnel"></i>
|
||||
Filter Listings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="filter-form" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="asset-type" class="form-label">Asset Type</label>
|
||||
<select id="asset-type" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
{% for type in asset_types %}
|
||||
<option value="{{ type }}">{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="listing-type" class="form-label">Listing Type</label>
|
||||
<select id="listing-type" class="form-select">
|
||||
<option value="">All Listings</option>
|
||||
{% for type in listing_types %}
|
||||
<option value="{{ type }}">{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="price-min" class="form-label">Min Price</label>
|
||||
<input type="number" class="form-control" id="price-min" placeholder="Min $">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="price-max" class="form-label">Max Price</label>
|
||||
<input type="number" class="form-control" id="price-max" placeholder="Max $">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="search" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="search" placeholder="Search by name, description, or tags">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
||||
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="mb-3">
|
||||
<div class="btn-group" role="group" aria-label="View Toggle">
|
||||
<button type="button" class="btn btn-outline-primary active" id="grid-view-btn">
|
||||
<i class="bi bi-grid"></i> Grid View
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="list-view-btn">
|
||||
<i class="bi bi-list"></i> List View
|
||||
</button>
|
||||
</div>
|
||||
<a href="/marketplace/create" class="btn btn-success float-end">
|
||||
<i class="bi bi-plus-circle"></i> List New Asset
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Grid View -->
|
||||
<div id="grid-view">
|
||||
<div class="row">
|
||||
{% if listings|length > 0 %}
|
||||
{% for listing in listings %}
|
||||
<div class="col-xl-3 col-lg-4 col-md-6 mb-4 listing-item"
|
||||
data-asset-type="{{ listing.asset_type.as_str() }}"
|
||||
data-listing-type="{{ listing.listing_type.as_str() }}"
|
||||
data-price="{{ listing.price }}">
|
||||
<div class="card h-100">
|
||||
{% if listing.featured %}
|
||||
<div class="badge bg-warning text-dark position-absolute" style="top: 0.5rem; right: 0.5rem">Featured</div>
|
||||
{% endif %}
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" class="card-img-top" alt="{{ listing.title }}" style="height: 180px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="card-img-top bg-light d-flex align-items-center justify-content-center" style="height: 180px;">
|
||||
<i class="bi bi-image text-secondary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ listing.title }}</h5>
|
||||
<p class="card-text text-truncate">{{ listing.description }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="badge bg-primary">{{ listing.listing_type.as_str() }}</span>
|
||||
{% if listing.asset_type.as_str() == "Token" %}
|
||||
<span class="badge bg-primary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "NFT" %}
|
||||
<span class="badge bg-info">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "RealEstate" %}
|
||||
<span class="badge bg-success">Real Estate</span>
|
||||
{% elif listing.asset_type.as_str() == "IntellectualProperty" %}
|
||||
<span class="badge bg-warning text-dark">IP</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">Listed by {{ listing.seller_name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>${{ listing.price }}</strong>
|
||||
<a href="/marketplace/{{ listing.id }}" class="btn btn-sm btn-outline-primary">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
No listings found. <a href="/marketplace/create">Create a new listing</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List View -->
|
||||
<div id="list-view" style="display: none;">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
All Listings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Asset</th>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>Price</th>
|
||||
<th>Listing Type</th>
|
||||
<th>Seller</th>
|
||||
<th>Listed</th>
|
||||
<th>Expires</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if listings|length > 0 %}
|
||||
{% for listing in listings %}
|
||||
<tr class="listing-item"
|
||||
data-asset-type="{{ listing.asset_type.as_str() }}"
|
||||
data-listing-type="{{ listing.listing_type.as_str() }}"
|
||||
data-price="{{ listing.price }}">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" alt="{{ listing.asset_name }}" class="me-2" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<i class="bi bi-collection me-2"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ listing.title }}</td>
|
||||
<td>
|
||||
{% if listing.asset_type.as_str() == "Token" %}
|
||||
<span class="badge bg-primary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "NFT" %}
|
||||
<span class="badge bg-info">{{ listing.asset_type.as_str() }}</span>
|
||||
{% elif listing.asset_type.as_str() == "RealEstate" %}
|
||||
<span class="badge bg-success">Real Estate</span>
|
||||
{% elif listing.asset_type.as_str() == "IntellectualProperty" %}
|
||||
<span class="badge bg-warning text-dark">IP</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ listing.asset_type.as_str() }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>${{ listing.price }}</td>
|
||||
<td>{{ listing.listing_type.as_str() }}</td>
|
||||
<td>{{ listing.seller_name }}</td>
|
||||
<td>{{ listing.created_at|date }}</td>
|
||||
<td>
|
||||
{% if listing.expires_at %}
|
||||
{{ listing.expires_at|date }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/marketplace/{{ listing.id }}" class="btn btn-sm btn-outline-primary">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="9" class="text-center">No listings available.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// View toggle
|
||||
const gridViewBtn = document.getElementById('grid-view-btn');
|
||||
const listViewBtn = document.getElementById('list-view-btn');
|
||||
const gridView = document.getElementById('grid-view');
|
||||
const listView = document.getElementById('list-view');
|
||||
|
||||
gridViewBtn.addEventListener('click', function() {
|
||||
gridView.style.display = 'block';
|
||||
listView.style.display = 'none';
|
||||
gridViewBtn.classList.add('active');
|
||||
listViewBtn.classList.remove('active');
|
||||
});
|
||||
|
||||
listViewBtn.addEventListener('click', function() {
|
||||
gridView.style.display = 'none';
|
||||
listView.style.display = 'block';
|
||||
listViewBtn.classList.add('active');
|
||||
gridViewBtn.classList.remove('active');
|
||||
});
|
||||
|
||||
// Filtering
|
||||
const filterForm = document.getElementById('filter-form');
|
||||
const assetTypeSelect = document.getElementById('asset-type');
|
||||
const listingTypeSelect = document.getElementById('listing-type');
|
||||
const priceMinInput = document.getElementById('price-min');
|
||||
const priceMaxInput = document.getElementById('price-max');
|
||||
const searchInput = document.getElementById('search');
|
||||
const listingItems = document.querySelectorAll('.listing-item');
|
||||
|
||||
filterForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
filterForm.addEventListener('reset', function() {
|
||||
setTimeout(function() {
|
||||
applyFilters();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
function applyFilters() {
|
||||
const assetType = assetTypeSelect.value;
|
||||
const listingType = listingTypeSelect.value;
|
||||
const priceMin = priceMinInput.value ? parseFloat(priceMinInput.value) : 0;
|
||||
const priceMax = priceMaxInput.value ? parseFloat(priceMaxInput.value) : Infinity;
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
|
||||
listingItems.forEach(function(item) {
|
||||
const itemAssetType = item.getAttribute('data-asset-type');
|
||||
const itemListingType = item.getAttribute('data-listing-type');
|
||||
const itemPrice = parseFloat(item.getAttribute('data-price'));
|
||||
const itemTitle = item.querySelector('.card-title') ?
|
||||
item.querySelector('.card-title').textContent.toLowerCase() : '';
|
||||
const itemDescription = item.querySelector('.card-text') ?
|
||||
item.querySelector('.card-text').textContent.toLowerCase() : '';
|
||||
|
||||
const assetTypeMatch = !assetType || itemAssetType === assetType;
|
||||
const listingTypeMatch = !listingType || itemListingType === listingType;
|
||||
const priceMatch = itemPrice >= priceMin && itemPrice <= priceMax;
|
||||
const searchMatch = !searchTerm ||
|
||||
itemTitle.includes(searchTerm) ||
|
||||
itemDescription.includes(searchTerm);
|
||||
|
||||
if (assetTypeMatch && listingTypeMatch && priceMatch && searchMatch) {
|
||||
item.style.display = '';
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
238
actix_mvc_app/src/views/marketplace/my_listings.html
Normal file
238
actix_mvc_app/src/views/marketplace/my_listings.html
Normal file
@@ -0,0 +1,238 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}My Marketplace Listings{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">My Listings</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item active">My Listings</li>
|
||||
</ol>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<a href="/marketplace/create" class="btn btn-success">
|
||||
<i class="bi bi-plus-circle"></i> Create New Listing
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Listings Table -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
My Listings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Asset</th>
|
||||
<th>Title</th>
|
||||
<th>Price</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Expires</th>
|
||||
<th>Views</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if listings|length > 0 %}
|
||||
{% for listing in listings %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if listing.image_url %}
|
||||
<img src="{{ listing.image_url }}" alt="{{ listing.asset_name }}" class="me-2" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<i class="bi bi-collection me-2"></i>
|
||||
{% endif %}
|
||||
{{ listing.asset_name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ listing.title }}</td>
|
||||
<td>${{ listing.price }}</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">{{ listing.listing_type.as_str() }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if listing.status.as_str() == "Active" %}
|
||||
<span class="badge bg-success">{{ listing.status.as_str() }}</span>
|
||||
{% elif listing.status.as_str() == "Sold" %}
|
||||
<span class="badge bg-info">{{ listing.status.as_str() }}</span>
|
||||
{% elif listing.status.as_str() == "Cancelled" %}
|
||||
<span class="badge bg-danger">{{ listing.status.as_str() }}</span>
|
||||
{% elif listing.status.as_str() == "Expired" %}
|
||||
<span class="badge bg-warning text-dark">{{ listing.status.as_str() }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ listing.created_at|date }}</td>
|
||||
<td>
|
||||
{% if listing.expires_at %}
|
||||
{{ listing.expires_at|date }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ listing.views }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/marketplace/{{ listing.id }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
{% if listing.status.as_str() == "Active" %}
|
||||
<form action="/marketplace/{{ listing.id }}/cancel" method="post" class="d-inline">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to cancel this listing?')">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="9" class="text-center">
|
||||
You don't have any listings yet.
|
||||
<a href="/marketplace/create">Create your first listing</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Listing Statistics -->
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-bar-chart"></i>
|
||||
Listings by Status
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="statusChart" width="100%" height="50"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-pie-chart"></i>
|
||||
Listings by Type
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="typeChart" width="100%" height="50"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Count listings by status
|
||||
const listingsData = JSON.parse('{{ listings|tojson|safe }}');
|
||||
|
||||
const statusCounts = {
|
||||
'Active': 0,
|
||||
'Sold': 0,
|
||||
'Cancelled': 0,
|
||||
'Expired': 0
|
||||
};
|
||||
|
||||
const typeCounts = {
|
||||
'Fixed Price': 0,
|
||||
'Auction': 0,
|
||||
'Exchange': 0
|
||||
};
|
||||
|
||||
listingsData.forEach(listing => {
|
||||
statusCounts[listing.status] += 1;
|
||||
typeCounts[listing.listing_type] += 1;
|
||||
});
|
||||
|
||||
// Status Chart
|
||||
const statusCtx = document.getElementById('statusChart').getContext('2d');
|
||||
new Chart(statusCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: Object.keys(statusCounts),
|
||||
datasets: [{
|
||||
label: 'Number of Listings',
|
||||
data: Object.values(statusCounts),
|
||||
backgroundColor: [
|
||||
'rgba(40, 167, 69, 0.7)', // Active - green
|
||||
'rgba(23, 162, 184, 0.7)', // Sold - cyan
|
||||
'rgba(220, 53, 69, 0.7)', // Cancelled - red
|
||||
'rgba(255, 193, 7, 0.7)' // Expired - yellow
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(40, 167, 69, 1)',
|
||||
'rgba(23, 162, 184, 1)',
|
||||
'rgba(220, 53, 69, 1)',
|
||||
'rgba(255, 193, 7, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
precision: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Type Chart
|
||||
const typeCtx = document.getElementById('typeChart').getContext('2d');
|
||||
new Chart(typeCtx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: Object.keys(typeCounts),
|
||||
datasets: [{
|
||||
data: Object.values(typeCounts),
|
||||
backgroundColor: [
|
||||
'rgba(0, 123, 255, 0.7)', // Fixed Price - blue
|
||||
'rgba(111, 66, 193, 0.7)', // Auction - purple
|
||||
'rgba(23, 162, 184, 0.7)' // Exchange - cyan
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(0, 123, 255, 1)',
|
||||
'rgba(111, 66, 193, 1)',
|
||||
'rgba(23, 162, 184, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user