init projectmycelium
This commit is contained in:
303
src/views/marketplace/applications.html
Normal file
303
src/views/marketplace/applications.html
Normal file
@@ -0,0 +1,303 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Application Solutions{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>Application Solutions</h1>
|
||||
<p class="lead">Discover self-healing applications that maintain sovereignty while offering convenience.</p>
|
||||
|
||||
<!-- Application Solutions Introduction -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<div class="d-flex">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-info-circle-fill fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="alert-heading">How Application Solutions Work</h5>
|
||||
<p>ThreeFold Application Solutions use a unique model: You provide the compute resources (Slices), while solution providers manage the applications. This ensures you maintain sovereignty over your infrastructure while benefiting from professional management.</p>
|
||||
<hr>
|
||||
<p class="mb-0">When you deploy an application, you'll be guided through the process of allocating the necessary resources if you don't already have them.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter and Search Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Filter Applications</h5>
|
||||
<form class="row g-3" method="GET">
|
||||
<div class="col-md-3">
|
||||
<label for="appTypeFilter" class="form-label">App Type</label>
|
||||
<select name="app_type" id="appTypeFilter" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
<option value="Web">Web Applications</option>
|
||||
<option value="Productivity">Productivity</option>
|
||||
<option value="Development">Development</option>
|
||||
<option value="Database">Database</option>
|
||||
<option value="Communication">Communication</option>
|
||||
<option value="CMS">Content Management</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="deploymentFilter" class="form-label">Deployment Type</label>
|
||||
<select name="deployment_type" id="deploymentFilter" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="Container">Container</option>
|
||||
<option value="VM">Virtual Machine</option>
|
||||
<option value="Kubernetes">Kubernetes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="minPriceFilter" class="form-label">Min Price ($)</label>
|
||||
<input type="number" name="min_price" id="minPriceFilter" class="form-control"
|
||||
value="" placeholder="0" min="0" step="1">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="maxPriceFilter" class="form-label">Max Price ($)</label>
|
||||
<input type="number" name="max_price" id="maxPriceFilter" class="form-control"
|
||||
value="" placeholder="50" min="0" step="1">
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary me-2">Apply Filters</button>
|
||||
<a href="/marketplace/applications" class="btn btn-outline-secondary">Clear</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications Grid -->
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-5">
|
||||
{% if application_products and application_products | length > 0 %}
|
||||
{% for product_data in application_products %}
|
||||
<div class="col">
|
||||
<div class="card h-100">
|
||||
<div class="position-relative p-4 bg-light text-center">
|
||||
<i class="bi bi-app fs-1 text-primary"></i>
|
||||
<span class="position-absolute top-0 end-0 badge bg-primary m-2">{{ product_data.product.category_id | title }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h5 class="card-title">
|
||||
<a href="/products/{{ product_data.product.id }}" class="text-decoration-none text-dark">
|
||||
{{ product_data.product.name }}
|
||||
</a>
|
||||
</h5>
|
||||
{% if product_data.product.metadata.featured %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% elif product_data.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product_data.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="card-text">{{ product_data.product.description | truncate(length=100) }}</p>
|
||||
|
||||
<!-- Features (from tags) -->
|
||||
{% if product_data.product.metadata.tags and product_data.product.metadata.tags | length > 0 %}
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">Features:</h6>
|
||||
<div class="d-flex flex-wrap">
|
||||
{% for tag in product_data.product.metadata.tags %}
|
||||
<span class="badge bg-light text-dark me-1 mb-1">{{ tag | title }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Resource Requirements -->
|
||||
{% if product_data.product.attributes %}
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">Resource Requirements:</h6>
|
||||
<div class="d-flex">
|
||||
{% if product_data.product.attributes.cpu_cores %}
|
||||
<div class="me-3"><i class="bi bi-cpu me-1"></i> {{ product_data.product.attributes.cpu_cores.value }} cores</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.memory_gb %}
|
||||
<div class="me-3"><i class="bi bi-memory me-1"></i> {{ product_data.product.attributes.memory_gb.value }} GB</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.storage_gb %}
|
||||
<div><i class="bi bi-hdd me-1"></i> {{ product_data.product.attributes.storage_gb.value }} GB</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Provider Info -->
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-building me-1"></i>{{ product_data.product.provider_name }}
|
||||
{% if product_data.product.metadata.location %}
|
||||
<span class="ms-2">
|
||||
<i class="bi bi-geo-alt me-1"></i>{{ product_data.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="price-info">
|
||||
<span class="text-muted small">Management fee:</span>
|
||||
<div class="fw-bold text-primary">{{ product_data.formatted_price }}</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success btn-sm buy-now-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}"
|
||||
data-category="applications">
|
||||
<i class="bi bi-lightning-fill me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
<a href="/products/{{ product_data.product.id }}" class="btn btn-outline-secondary btn-sm">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-app display-1 text-muted"></i>
|
||||
<h4 class="mt-3">No Applications Available</h4>
|
||||
<p class="text-muted">Check back later for new application solutions.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.total_pages > 1 %}
|
||||
<nav aria-label="Application pages" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Previous Page -->
|
||||
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
||||
{% if pagination.has_previous %}
|
||||
<a class="page-link" href="?page={{ pagination.previous_page }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 1 -->
|
||||
<li class="page-item {% if pagination.current_page == 0 %}active{% endif %}">
|
||||
{% if pagination.current_page == 0 %}
|
||||
<span class="page-link">1</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=0">1</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 2 (if exists) -->
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<li class="page-item {% if pagination.current_page == 1 %}active{% endif %}">
|
||||
{% if pagination.current_page == 1 %}
|
||||
<span class="page-link">2</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=1">2</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 3 (if exists) -->
|
||||
{% if pagination.total_pages > 2 %}
|
||||
<li class="page-item {% if pagination.current_page == 2 %}active{% endif %}">
|
||||
{% if pagination.current_page == 2 %}
|
||||
<span class="page-link">3</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=2">3</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 4 (if exists) -->
|
||||
{% if pagination.total_pages > 3 %}
|
||||
<li class="page-item {% if pagination.current_page == 3 %}active{% endif %}">
|
||||
{% if pagination.current_page == 3 %}
|
||||
<span class="page-link">4</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=3">4</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 5 (if exists) -->
|
||||
{% if pagination.total_pages > 4 %}
|
||||
<li class="page-item {% if pagination.current_page == 4 %}active{% endif %}">
|
||||
{% if pagination.current_page == 4 %}
|
||||
<span class="page-link">5</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=4">5</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Next Page -->
|
||||
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
||||
{% if pagination.has_next %}
|
||||
<a class="page-link" href="?page={{ pagination.next_page }}">Next</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Next</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="text-center text-muted mt-2">
|
||||
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
|
||||
({{ pagination.total_count }} total applications)
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- How It Works Section -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">How Application Deployment Works</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>1. Choose Your Application</h5>
|
||||
<p>Browse our catalog of self-healing applications and select the one that meets your needs.</p>
|
||||
|
||||
<h5>2. Resource Allocation</h5>
|
||||
<p>We'll guide you through allocating the necessary compute resources (Slices) for your application.</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>3. Deployment & Management</h5>
|
||||
<p>The solution provider handles deployment, updates, and maintenance while you retain full sovereignty.</p>
|
||||
|
||||
<h5>4. Access & Control</h5>
|
||||
<p>Access your application through secure channels while maintaining complete control over your data and infrastructure.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success mt-3">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
<strong>Your Sovereignty Guaranteed:</strong> You own the infrastructure, we provide the expertise. Your data never leaves your control.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.price-info {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
border-top: 1px solid rgba(0,0,0,.125);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/static/js/marketplace-category.js"></script>
|
||||
|
||||
{% endblock %}
|
410
src/views/marketplace/cart.html
Normal file
410
src/views/marketplace/cart.html
Normal file
@@ -0,0 +1,410 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
{% block title %}Shopping Cart - Project Mycelium{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.hover-shadow:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.add-recommended-btn {
|
||||
transition: all 0.2s ease;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.add-recommended-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2"><i class="bi bi-cart3 me-2"></i>Shopping Cart</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<a href="/marketplace" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Continue Shopping
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if cart.is_empty %}
|
||||
<!-- Empty Cart State -->
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 text-center">
|
||||
<div class="card border-0">
|
||||
<div class="card-body py-5">
|
||||
<i class="bi bi-cart-x display-1 text-muted mb-4"></i>
|
||||
<h3 class="text-muted mb-3">Your cart is empty</h3>
|
||||
<p class="text-muted mb-4">Looks like you haven't added any items to your cart yet. Browse our marketplace to find the perfect ThreeFold resources for your needs.</p>
|
||||
<a href="/marketplace" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-shop me-2"></i>Browse Marketplace
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Cart with Items -->
|
||||
<div class="row">
|
||||
<!-- Cart Items -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Cart Items ({{ cart.item_count }})</h5>
|
||||
<button class="btn btn-sm btn-outline-danger" id="clearCartBtn">
|
||||
<i class="bi bi-trash me-1"></i>Clear Cart
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% for item in cart.items %}
|
||||
<div class="cart-item border-bottom p-4" data-product-id="{{ item.product_id }}">
|
||||
<div class="row align-items-center">
|
||||
<!-- Product Info -->
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0 me-3">
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||
{% if item.product_category == "compute" %}
|
||||
<i class="bi bi-cpu fs-4 text-primary"></i>
|
||||
{% elif item.product_category == "hardware" %}
|
||||
<i class="bi bi-hdd-rack fs-4 text-success"></i>
|
||||
{% elif item.product_category == "gateways" %}
|
||||
<i class="bi bi-globe fs-4 text-info"></i>
|
||||
{% elif item.product_category == "applications" %}
|
||||
<i class="bi bi-app fs-4 text-warning"></i>
|
||||
{% elif item.product_category == "services" %}
|
||||
<i class="bi bi-person-workspace fs-4 text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box fs-4 text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ item.product_name }}</h6>
|
||||
<p class="text-muted mb-1 small">{{ item.provider_name }}</p>
|
||||
<span class="badge bg-light text-dark">{{ item.product_category|title }}</span>
|
||||
|
||||
{% if item.specifications %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Specifications:</small>
|
||||
<div class="mt-1">
|
||||
{% for key, value in item.specifications %}
|
||||
<span class="badge bg-secondary me-1">{{ key }}: {{ value }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quantity Controls -->
|
||||
<div class="col-md-3">
|
||||
<div class="d-flex align-items-center justify-content-center">
|
||||
<button class="btn btn-sm btn-outline-secondary" data-product-id="{{ item.product_id }}" data-action="decrease">
|
||||
<i class="bi bi-dash"></i>
|
||||
</button>
|
||||
<span class="mx-3 fw-bold">{{ item.quantity }}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-product-id="{{ item.product_id }}" data-action="increase">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Price and Actions -->
|
||||
<div class="col-md-3">
|
||||
<div class="text-end">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Unit: {{ item.unit_price }}</small>
|
||||
</div>
|
||||
<div class="fw-bold text-primary mb-2">{{ item.total_price }}</div>
|
||||
<button class="btn btn-sm btn-outline-danger" data-product-id="{{ item.product_id }}" data-action="remove">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cart Summary -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card sticky-top" style="top: 100px;">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Order Summary</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Currency Selector -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted">Display Currency</label>
|
||||
<select class="form-select form-select-sm" id="currencySelector">
|
||||
{% for currency in currencies %}
|
||||
<option value="{{ currency.code }}" {% if currency.code == user_currency %}selected{% endif %}>
|
||||
{{ currency.symbol }} {{ currency.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Price Breakdown -->
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal ({{ cart.item_count }} items)</span>
|
||||
<span>{{ cart.subtotal }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Estimated fees</span>
|
||||
<span class="text-muted">Calculated at checkout</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="fw-bold">Total</span>
|
||||
<span class="fw-bold text-primary fs-5">{{ cart.total }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Guest User Notice -->
|
||||
{% if not user_json %}
|
||||
<div class="alert alert-info mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<div>
|
||||
<strong>Almost ready to checkout!</strong><br>
|
||||
<small>You'll need to log in or create an account to complete your purchase. Don't worry - your cart items will be saved!</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Checkout Button -->
|
||||
<div class="d-grid">
|
||||
{% if user_json %}
|
||||
<a href="/checkout" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-credit-card me-2"></i>Proceed to Checkout
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#guestCheckoutModal">
|
||||
<i class="bi bi-credit-card me-2"></i>Proceed to Checkout
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Security Notice -->
|
||||
<div class="mt-3 p-3 bg-light rounded">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-shield-check text-success me-2"></i>
|
||||
<small class="text-muted">Secure checkout with 256-bit SSL encryption</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Products -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-stars me-2"></i>Recommended for you</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
{% for product_data in recommended_products %}
|
||||
<div class="col-12">
|
||||
<div class="d-flex align-items-center p-3 border rounded hover-shadow" style="transition: all 0.2s ease;">
|
||||
<i class="bi bi-cpu text-primary me-3 fs-4"></i>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold">{{ product_data.product.name }}</div>
|
||||
<small class="text-muted">{{ product_data.product.description }}</small>
|
||||
<div class="text-primary fw-bold mt-1">{{ product_data.formatted_price }}</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary add-recommended-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-product-price="{{ product_data.price.display_amount }}"
|
||||
data-product-category="{{ product_data.product.category_id }}">
|
||||
<i class="bi bi-plus me-1"></i>Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/marketplace" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-shop me-1"></i>Browse All Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Clear Cart Confirmation Modal -->
|
||||
<div class="modal fade" id="clearCartModal" tabindex="-1" aria-labelledby="clearCartModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="clearCartModalLabel">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Clear Entire Cart
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-cart-x text-danger me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<p class="mb-1 fw-bold">Are you sure you want to clear your entire cart?</p>
|
||||
<p class="mb-0 text-muted small">This action will remove all items from your cart and cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<small>All {{ cart.item_count }} items will be permanently removed from your cart.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmClearCartBtn">
|
||||
<i class="bi bi-trash me-1"></i>Clear Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remove Item Confirmation Modal -->
|
||||
<div class="modal fade" id="removeItemModal" tabindex="-1" aria-labelledby="removeItemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning text-dark">
|
||||
<h5 class="modal-title" id="removeItemModalLabel">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Remove Item
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-trash text-warning me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<p class="mb-1 fw-bold">Remove this item from your cart?</p>
|
||||
<p class="mb-0 text-muted small">This action cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<small>You can always add this item back later from the marketplace.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning" id="confirmRemoveItemBtn">
|
||||
<i class="bi bi-trash me-1"></i>Remove Item
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loadingOverlay" class="position-fixed top-0 start-0 w-100 h-100 d-none" style="background: rgba(0,0,0,0.5); z-index: 9999;">
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<div class="spinner-border text-light" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="/static/js/cart-marketplace.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
<!-- Guest Checkout Modal -->
|
||||
<div class="modal fade" id="guestCheckoutModal" tabindex="-1" aria-labelledby="guestCheckoutModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="guestCheckoutModalLabel">
|
||||
<i class="bi bi-person-check me-2"></i>Complete Your Purchase
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-cart-check text-primary" style="font-size: 3rem;"></i>
|
||||
<h4 class="mt-3 mb-2">Almost there!</h4>
|
||||
<p class="text-muted">You're just one step away from completing your purchase.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info d-flex align-items-center mb-4">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
<div>
|
||||
<strong>Your cart is safe!</strong><br>
|
||||
<small>All items in your cart will be automatically saved when you log in or create an account.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<h6 class="mb-3">Choose an option to continue:</h6>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-primary">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-person-fill text-primary mb-2" style="font-size: 2rem;"></i>
|
||||
<h6>Already have an account?</h6>
|
||||
<p class="text-muted small mb-3">Sign in to access your saved information and complete checkout quickly.</p>
|
||||
<button type="button" class="btn btn-primary w-100" id="guestLoginBtn">
|
||||
<i class="bi bi-box-arrow-in-right me-2"></i>Log In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-person-plus-fill text-success mb-2" style="font-size: 2rem;"></i>
|
||||
<h6>New to ThreeFold?</h6>
|
||||
<p class="text-muted small mb-3">Create a free account to manage your orders and access exclusive features.</p>
|
||||
<button type="button" class="btn btn-success w-100" id="guestRegisterBtn">
|
||||
<i class="bi bi-person-plus me-2"></i>Create Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 p-3 bg-light rounded">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<i class="bi bi-lightning-charge text-warning" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h6 class="mb-1">Quick & Secure</h6>
|
||||
<small class="text-muted">Registration takes less than 30 seconds. Your payment information is protected with enterprise-grade security.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-arrow-left me-2"></i>Continue Shopping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
499
src/views/marketplace/cart_full.html
Normal file
499
src/views/marketplace/cart_full.html
Normal file
@@ -0,0 +1,499 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Shopping Cart - Project Mycelium{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<!-- Add Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css">
|
||||
|
||||
<style>
|
||||
.cart-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cart-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.quantity-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.quantity-controls button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cart-summary {
|
||||
position: sticky;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.security-notice {
|
||||
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);
|
||||
border: 1px solid #b6d4da;
|
||||
}
|
||||
|
||||
.empty-cart-icon {
|
||||
font-size: 4rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.toast {
|
||||
z-index: 10000;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Shopping Cart</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2"><i class="bi bi-cart3 me-2 text-primary"></i>Shopping Cart</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<a href="/marketplace" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Continue Shopping
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if cart.is_empty %}
|
||||
<!-- Empty Cart State -->
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 text-center">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body py-5">
|
||||
<i class="bi bi-cart-x empty-cart-icon mb-4"></i>
|
||||
<h3 class="text-muted mb-3">Your cart is empty</h3>
|
||||
<p class="text-muted mb-4">Looks like you haven't added any items to your cart yet. Browse our marketplace to find the perfect ThreeFold resources for your needs.</p>
|
||||
<a href="/marketplace" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-shop me-2"></i>Browse Marketplace
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Cart with Items -->
|
||||
<div class="row">
|
||||
<!-- Cart Items -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-bag me-2"></i>Cart Items ({{ cart.item_count }})</h5>
|
||||
<button class="btn btn-sm btn-outline-danger" id="clearCartBtn">
|
||||
<i class="bi bi-trash me-1"></i>Clear Cart
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% for item in cart.items %}
|
||||
<div class="cart-item border-bottom p-4" data-product-id="{{ item.product_id }}">
|
||||
<div class="row align-items-center">
|
||||
<!-- Product Info -->
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0 me-3">
|
||||
<div class="product-icon">
|
||||
{% if item.product_category == "compute" %}
|
||||
<i class="bi bi-cpu fs-4 text-primary"></i>
|
||||
{% elif item.product_category == "hardware" %}
|
||||
<i class="bi bi-hdd-rack fs-4 text-success"></i>
|
||||
{% elif item.product_category == "gateways" %}
|
||||
<i class="bi bi-globe fs-4 text-info"></i>
|
||||
{% elif item.product_category == "applications" %}
|
||||
<i class="bi bi-app fs-4 text-warning"></i>
|
||||
{% elif item.product_category == "services" %}
|
||||
<i class="bi bi-person-workspace fs-4 text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box fs-4 text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1 fw-bold">{{ item.product_name }}</h6>
|
||||
<p class="text-muted mb-1 small">
|
||||
<i class="bi bi-building me-1"></i>{{ item.provider_name }}
|
||||
</p>
|
||||
<span class="badge bg-light text-dark border">
|
||||
<i class="bi bi-tag me-1"></i>{{ item.product_category|title }}
|
||||
</span>
|
||||
|
||||
{% if item.specifications %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted fw-bold">Specifications:</small>
|
||||
<div class="mt-1">
|
||||
{% for key, value in item.specifications %}
|
||||
<span class="badge bg-secondary me-1 small">{{ key }}: {{ value }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quantity Controls -->
|
||||
<div class="col-md-3">
|
||||
<div class="quantity-controls justify-content-center">
|
||||
<button class="btn btn-sm btn-outline-secondary" data-product-id="{{ item.product_id }}" data-action="decrease">
|
||||
<i class="bi bi-dash"></i>
|
||||
</button>
|
||||
<span class="mx-3 fw-bold fs-5">{{ item.quantity }}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-product-id="{{ item.product_id }}" data-action="increase">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<small class="text-muted">Quantity</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Price and Actions -->
|
||||
<div class="col-md-3">
|
||||
<div class="text-end">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Unit: {{ item.unit_price }}</small>
|
||||
</div>
|
||||
<div class="fw-bold text-primary mb-3 fs-5">{{ item.total_price }}</div>
|
||||
<button class="btn btn-sm btn-outline-danger" data-product-id="{{ item.product_id }}" data-action="remove" title="Remove item">
|
||||
<i class="bi bi-trash"></i> Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cart Summary -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm cart-summary">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-receipt me-2"></i>Order Summary</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Currency Selector -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted fw-bold">
|
||||
<i class="bi bi-currency-exchange me-1"></i>Display Currency
|
||||
</label>
|
||||
<select class="form-select form-select-sm" id="currencySelector">
|
||||
{% for currency in currencies %}
|
||||
<option value="{{ currency.code }}" {% if currency.code == user_currency %}selected{% endif %}>
|
||||
{{ currency.symbol }} {{ currency.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Price Breakdown -->
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span><i class="bi bi-bag me-1"></i>Subtotal ({{ cart.item_count }} items)</span>
|
||||
<span class="fw-bold">{{ cart.subtotal }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted"><i class="bi bi-calculator me-1"></i>Estimated fees</span>
|
||||
<span class="text-muted">Calculated at checkout</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted"><i class="bi bi-truck me-1"></i>Deployment</span>
|
||||
<span class="text-success">Free</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="fw-bold fs-5">Total</span>
|
||||
<span class="fw-bold text-primary fs-4">{{ cart.total }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Checkout Button -->
|
||||
<div class="d-grid mb-3">
|
||||
{% if user_json %}
|
||||
<a href="/checkout" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-credit-card me-2"></i>Proceed to Checkout
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#guestCheckoutModal">
|
||||
<i class="bi bi-credit-card me-2"></i>Proceed to Checkout
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Additional Actions -->
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-secondary btn-sm" data-action="save-for-later">
|
||||
<i class="bi bi-bookmark me-1"></i>Save for Later
|
||||
</button>
|
||||
<button class="btn btn-outline-info btn-sm" data-action="share-cart">
|
||||
<i class="bi bi-share me-1"></i>Share Cart
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Security Notice -->
|
||||
<div class="mt-3 p-3 security-notice rounded">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-shield-check text-success me-2 fs-5"></i>
|
||||
<div>
|
||||
<small class="fw-bold">Secure Checkout</small>
|
||||
<div class="small text-muted">256-bit SSL encryption</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Products -->
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0"><i class="bi bi-lightbulb me-2"></i>You might also like</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-12">
|
||||
<div class="d-flex align-items-center p-3 border rounded hover-shadow">
|
||||
<i class="bi bi-cpu text-primary me-3 fs-4"></i>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold small">High-Performance Compute</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">Starting at $5/month</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="d-flex align-items-center p-3 border rounded hover-shadow">
|
||||
<i class="bi bi-hdd-rack text-success me-3 fs-4"></i>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold small">Storage Solutions</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">Starting at $2.50/month</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="d-flex align-items-center p-3 border rounded hover-shadow">
|
||||
<i class="bi bi-globe text-info me-3 fs-4"></i>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold small">Gateway Services</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">Starting at $1.50/month</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Clear Cart Confirmation Modal -->
|
||||
<div class="modal fade" id="clearCartModal" tabindex="-1" aria-labelledby="clearCartModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="clearCartModalLabel">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Clear Entire Cart
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-cart-x text-danger me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<p class="mb-1 fw-bold">Are you sure you want to clear your entire cart?</p>
|
||||
<p class="mb-0 text-muted small">This action will remove all items from your cart and cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<small>All {{ cart.item_count }} items will be permanently removed from your cart.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmClearCartBtn">
|
||||
<i class="bi bi-trash me-1"></i>Clear Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remove Item Confirmation Modal -->
|
||||
<div class="modal fade" id="removeItemModal" tabindex="-1" aria-labelledby="removeItemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning text-dark">
|
||||
<h5 class="modal-title" id="removeItemModalLabel">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Remove Item
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-trash text-warning me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<p class="mb-1 fw-bold">Remove this item from your cart?</p>
|
||||
<p class="mb-0 text-muted small">This action cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<small>You can always add this item back later from the marketplace.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning" id="confirmRemoveItemBtn">
|
||||
<i class="bi bi-trash me-1"></i>Remove Item
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loadingOverlay" class="position-fixed top-0 start-0 w-100 h-100 d-none loading-overlay">
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<div class="spinner-border text-light" role="status" style="width: 3rem; height: 3rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="application/json" id="cart-hydration">
|
||||
{
|
||||
"item_count": {{ cart.item_count }},
|
||||
"redirect_login_url": "/login?checkout=true",
|
||||
"redirect_register_url": "/register?checkout=true",
|
||||
"redirect_after_auth": "/cart"
|
||||
}
|
||||
</script>
|
||||
<script src="/static/js/cart.js"></script>
|
||||
|
||||
<!-- Guest Checkout Modal -->
|
||||
<div class="modal fade" id="guestCheckoutModal" tabindex="-1" aria-labelledby="guestCheckoutModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="guestCheckoutModalLabel">
|
||||
<i class="bi bi-person-check me-2"></i>Complete Your Purchase
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-cart-check text-primary" style="font-size: 3rem;"></i>
|
||||
<h4 class="mt-3 mb-2">Almost there!</h4>
|
||||
<p class="text-muted">You're just one step away from completing your purchase.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info d-flex align-items-center mb-4">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
<div>
|
||||
<strong>Your cart is safe!</strong><br>
|
||||
<small>All items in your cart will be automatically saved when you log in or create an account.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<h6 class="mb-3">Choose an option to continue:</h6>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-primary">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-person-fill text-primary mb-2" style="font-size: 2rem;"></i>
|
||||
<h6>Already have an account?</h6>
|
||||
<p class="text-muted small mb-3">Sign in to access your saved information and complete checkout quickly.</p>
|
||||
<button type="button" class="btn btn-primary w-100" id="guestLoginBtn">
|
||||
<i class="bi bi-box-arrow-in-right me-2"></i>Log In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-person-plus-fill text-success mb-2" style="font-size: 2rem;"></i>
|
||||
<h6>New to ThreeFold?</h6>
|
||||
<p class="text-muted small mb-3">Create a free account to manage your orders and access exclusive features.</p>
|
||||
<button type="button" class="btn btn-success w-100" id="guestRegisterBtn">
|
||||
<i class="bi bi-person-plus me-2"></i>Create Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 p-3 bg-light rounded">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<i class="bi bi-lightning-charge text-warning" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h6 class="mb-1">Quick & Secure</h6>
|
||||
<small class="text-muted">Registration takes less than 30 seconds. Your payment information is protected with enterprise-grade security.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-arrow-left me-2"></i>Continue Shopping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
18
src/views/marketplace/cart_simple.html
Normal file
18
src/views/marketplace/cart_simple.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Shopping Cart - Project Mycelium{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="container-fluid py-4">
|
||||
<h1>Shopping Cart</h1>
|
||||
<p>Cart page is working!</p>
|
||||
|
||||
{% if cart %}
|
||||
<p>Cart data is available</p>
|
||||
<p>Items: {{ cart.item_count }}</p>
|
||||
<p>Currency: {{ cart.currency }}</p>
|
||||
{% else %}
|
||||
<p>No cart data available</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
573
src/views/marketplace/cart_standalone.html
Normal file
573
src/views/marketplace/cart_standalone.html
Normal file
@@ -0,0 +1,573 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Shopping Cart - Project Mycelium</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
transition: all 0.3s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.cart-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 12px;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
|
||||
.quantity-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.quantity-controls button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cart-summary {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.security-notice {
|
||||
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);
|
||||
border: 1px solid #b6d4da;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.empty-cart-icon {
|
||||
font-size: 4rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0b5ed7 0%, #0a58ca 100%);
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.badge {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.hover-shadow:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.add-recommended-btn {
|
||||
transition: all 0.2s ease;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.add-recommended-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hover-shadow:hover {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
transform: translateY(-1px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/images/logo_dark.png" alt="ThreeFold Logo" class="me-2">
|
||||
<span>Project Mycelium</span>
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/marketplace">
|
||||
<i class="bi bi-shop me-1"></i>Marketplace
|
||||
</a>
|
||||
<a class="nav-link active" href="/cart">
|
||||
<i class="bi bi-cart3 me-1"></i>Cart
|
||||
</a>
|
||||
<a class="nav-link" href="/orders" id="myOrdersLink" style="display: none;">
|
||||
<i class="bi bi-list-ul me-1"></i>My Orders
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container py-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/" class="text-decoration-none">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace" class="text-decoration-none">Marketplace</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Shopping Cart</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-4 border-bottom">
|
||||
<h1 class="h2 mb-0">
|
||||
<i class="bi bi-cart3 me-2 text-primary"></i>Shopping Cart
|
||||
</h1>
|
||||
<div class="btn-toolbar">
|
||||
<a href="/marketplace" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Continue Shopping
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if cart.is_empty %}
|
||||
<!-- Empty Cart State -->
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 text-center">
|
||||
<div class="card">
|
||||
<div class="card-body py-5">
|
||||
<i class="bi bi-cart-x empty-cart-icon mb-4"></i>
|
||||
<h3 class="text-muted mb-3">Your cart is empty</h3>
|
||||
<p class="text-muted mb-4">
|
||||
Discover amazing ThreeFold resources and services. From compute power to storage solutions,
|
||||
find everything you need to build on the decentralized internet.
|
||||
</p>
|
||||
<a href="/marketplace" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-shop me-2"></i>Explore Marketplace
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Cart with Items -->
|
||||
<div class="row">
|
||||
<!-- Cart Items -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-bag me-2"></i>Cart Items
|
||||
<span class="badge bg-primary">{{ cart.item_count }}</span>
|
||||
</h5>
|
||||
<button class="btn btn-sm btn-outline-danger" id="clearCartBtn">
|
||||
<i class="bi bi-trash me-1"></i>Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% for item in cart.items %}
|
||||
<div class="cart-item p-4 border-bottom" data-product-id="{{ item.product_id }}">
|
||||
<div class="row align-items-center">
|
||||
<!-- Product Info -->
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0 me-3">
|
||||
<div class="product-icon">
|
||||
{% if item.product_category == "compute" %}
|
||||
<i class="bi bi-cpu fs-4 text-primary"></i>
|
||||
{% elif item.product_category == "hardware" %}
|
||||
<i class="bi bi-hdd-rack fs-4 text-success"></i>
|
||||
{% elif item.product_category == "gateways" %}
|
||||
<i class="bi bi-globe fs-4 text-info"></i>
|
||||
{% elif item.product_category == "applications" %}
|
||||
<i class="bi bi-app fs-4 text-warning"></i>
|
||||
{% elif item.product_category == "services" %}
|
||||
<i class="bi bi-person-workspace fs-4 text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box fs-4 text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1 fw-bold">{{ item.product_name }}</h6>
|
||||
<p class="text-muted mb-2 small">
|
||||
<i class="bi bi-building me-1"></i>{{ item.provider_name }}
|
||||
</p>
|
||||
<span class="badge bg-light text-dark border">
|
||||
{{ item.product_category|title }}
|
||||
</span>
|
||||
|
||||
{% if item.specifications %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted fw-bold">Configuration:</small>
|
||||
<div class="mt-1">
|
||||
{% for key, value in item.specifications %}
|
||||
<span class="badge bg-secondary me-1 small">{{ key }}: {{ value }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quantity Controls -->
|
||||
<div class="col-md-3">
|
||||
<div class="quantity-controls justify-content-center">
|
||||
<button class="btn btn-sm btn-outline-secondary" data-product-id="{{ item.product_id }}" data-action="decrease">
|
||||
<i class="bi bi-dash"></i>
|
||||
</button>
|
||||
<span class="mx-3 fw-bold fs-5">{{ item.quantity }}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-product-id="{{ item.product_id }}" data-action="increase">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<small class="text-muted">Quantity</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Price and Actions -->
|
||||
<div class="col-md-3">
|
||||
<div class="text-end">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Unit: {{ item.unit_price }}</small>
|
||||
</div>
|
||||
<div class="fw-bold text-primary mb-3 fs-5">{{ item.total_price }}</div>
|
||||
<button class="btn btn-sm btn-outline-danger" data-product-id="{{ item.product_id }}" data-action="remove">
|
||||
<i class="bi bi-trash me-1"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cart Summary -->
|
||||
<div class="col-lg-4">
|
||||
<div class="cart-summary p-4">
|
||||
<h5 class="mb-3">
|
||||
<i class="bi bi-receipt me-2 text-primary"></i>Order Summary
|
||||
</h5>
|
||||
|
||||
<!-- Currency Selector -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted fw-bold">
|
||||
<i class="bi bi-currency-exchange me-1"></i>Display Currency
|
||||
</label>
|
||||
<select class="form-select form-select-sm" id="currencySelector">
|
||||
{% for currency in currencies %}
|
||||
<option value="{{ currency.code }}" {% if currency.code == user_currency %}selected{% endif %}>
|
||||
{{ currency.symbol }} {{ currency.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Price Breakdown -->
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span><i class="bi bi-bag me-1"></i>Subtotal ({{ cart.item_count }} items)</span>
|
||||
<span class="fw-bold">{{ cart.subtotal }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted"><i class="bi bi-calculator me-1"></i>Platform fees</span>
|
||||
<span class="text-muted">Calculated at checkout</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted"><i class="bi bi-truck me-1"></i>Deployment</span>
|
||||
<span class="text-success fw-bold">Free</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between mb-4">
|
||||
<span class="fw-bold fs-5">Total</span>
|
||||
<span class="fw-bold text-primary fs-4">{{ cart.total }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Guest User Notice -->
|
||||
{% if not user_json %}
|
||||
<div class="alert alert-info mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<div>
|
||||
<strong>Almost ready to checkout!</strong><br>
|
||||
<small>You'll need to log in or create an account to complete your purchase. Don't worry - your cart items will be saved!</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Checkout Button -->
|
||||
<div class="d-grid mb-3">
|
||||
{% if user_json %}
|
||||
<a href="/checkout" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-credit-card me-2"></i>Proceed to Checkout
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-lg" id="checkoutBtn">
|
||||
<i class="bi bi-credit-card me-2"></i>Proceed to Checkout
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Security Notice -->
|
||||
<div class="security-notice p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-shield-check text-success me-2 fs-5"></i>
|
||||
<div>
|
||||
<small class="fw-bold">Secure Checkout</small>
|
||||
<div class="small text-muted">256-bit SSL encryption</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Products -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-stars me-2"></i>Recommended for you
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for product_data in recommended_products %}
|
||||
<div class="d-flex align-items-center p-3 border rounded hover-shadow mb-3" style="transition: all 0.2s ease;">
|
||||
<i class="bi bi-cpu text-primary me-3 fs-4"></i>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold">{{ product_data.product.name }}</div>
|
||||
<small class="text-muted">{{ product_data.product.description }}</small>
|
||||
<div class="text-primary fw-bold mt-1">{{ product_data.formatted_price }}</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary add-recommended-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-product-price="{{ product_data.price.display_amount }}"
|
||||
data-product-category="{{ product_data.product.category_id }}">
|
||||
<i class="bi bi-plus me-1"></i>Add
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/marketplace" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-shop me-1"></i>Browse All Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Clear Cart Confirmation Modal -->
|
||||
<div class="modal fade" id="clearCartModal" tabindex="-1" aria-labelledby="clearCartModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="clearCartModalLabel">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Clear Entire Cart
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-cart-x text-danger me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<p class="mb-1 fw-bold">Are you sure you want to clear your entire cart?</p>
|
||||
<p class="mb-0 text-muted small">This action will remove all items from your cart and cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<small>All {{ cart.item_count }} items will be permanently removed from your cart.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmClearCartBtn">
|
||||
<i class="bi bi-trash me-1"></i>Clear Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remove Item Confirmation Modal -->
|
||||
<div class="modal fade" id="removeItemModal" tabindex="-1" aria-labelledby="removeItemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning text-dark">
|
||||
<h5 class="modal-title" id="removeItemModalLabel">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Remove Item
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-trash text-warning me-3" style="font-size: 2rem;"></i>
|
||||
<div>
|
||||
<p class="mb-1 fw-bold">Remove this item from your cart?</p>
|
||||
<p class="mb-0 text-muted small">This action cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info d-flex align-items-center">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<small>You can always add this item back later from the marketplace.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning" id="confirmRemoveItemBtn">
|
||||
<i class="bi bi-trash me-1"></i>Remove Item
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loadingOverlay" class="position-fixed top-0 start-0 w-100 h-100 d-none" style="background: rgba(0,0,0,0.5); z-index: 9999;">
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<div class="spinner-border text-light" role="status" style="width: 3rem; height: 3rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script type="application/json" id="cart-hydration">
|
||||
{
|
||||
"item_count": {{ cart.item_count }},
|
||||
"redirect_login_url": "/login?checkout=true",
|
||||
"redirect_register_url": "/register?checkout=true",
|
||||
"redirect_after_auth": "/cart"
|
||||
}
|
||||
</script>
|
||||
<script src="/static/js/cart.js"></script>
|
||||
|
||||
<!-- Guest Checkout Modal -->
|
||||
<div class="modal fade" id="guestCheckoutModal" tabindex="-1" aria-labelledby="guestCheckoutModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="guestCheckoutModalLabel">
|
||||
<i class="bi bi-person-check me-2"></i>Complete Your Purchase
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-cart-check text-primary" style="font-size: 3rem;"></i>
|
||||
<h4 class="mt-3 mb-2">Almost there!</h4>
|
||||
<p class="text-muted">You're just one step away from completing your purchase.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info d-flex align-items-center mb-4">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
<div>
|
||||
<strong>Your cart is safe!</strong><br>
|
||||
<small>All items in your cart will be automatically saved when you log in or create an account.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<h6 class="mb-3">Choose an option to continue:</h6>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-primary">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-person-fill text-primary mb-2" style="font-size: 2rem;"></i>
|
||||
<h6>Already have an account?</h6>
|
||||
<p class="text-muted small mb-3">Sign in to access your saved information and complete checkout quickly.</p>
|
||||
<button type="button" class="btn btn-primary w-100" id="guestLoginBtn">
|
||||
<i class="bi bi-box-arrow-in-right me-2"></i>Log In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-person-plus-fill text-success mb-2" style="font-size: 2rem;"></i>
|
||||
<h6>New to ThreeFold?</h6>
|
||||
<p class="text-muted small mb-3">Create a free account to manage your orders and access exclusive features.</p>
|
||||
<button type="button" class="btn btn-success w-100" id="guestRegisterBtn">
|
||||
<i class="bi bi-person-plus me-2"></i>Create Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 p-3 bg-light rounded">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<i class="bi bi-lightning-charge text-warning" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h6 class="mb-1">Quick & Secure</h6>
|
||||
<small class="text-muted">Registration takes less than 30 seconds. Your payment information is protected with enterprise-grade security.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-arrow-left me-2"></i>Continue Shopping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
342
src/views/marketplace/checkout.html
Normal file
342
src/views/marketplace/checkout.html
Normal file
@@ -0,0 +1,342 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Checkout - Project Mycelium</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.checkout-step {
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.checkout-step.active {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border: 2px solid #2196f3;
|
||||
}
|
||||
|
||||
.checkout-step.completed {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
|
||||
border: 2px solid #4caf50;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.step-number.active {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-number.completed {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-number.pending {
|
||||
background: #e0e0e0;
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.payment-method {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.payment-method:hover {
|
||||
border-color: #2196f3;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.payment-method.selected {
|
||||
border-color: #2196f3;
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.security-badge {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
|
||||
border: 1px solid #4caf50;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0b5ed7 0%, #0a58ca 100%);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 0.2rem rgba(33, 150, 243, 0.25);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/images/logo_dark.png" alt="ThreeFold Logo" class="me-2">
|
||||
<span>Project Mycelium</span>
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/marketplace">
|
||||
<i class="bi bi-shop me-1"></i>Marketplace
|
||||
</a>
|
||||
<a class="nav-link" href="/cart">
|
||||
<i class="bi bi-cart3 me-1"></i>Cart
|
||||
</a>
|
||||
<a class="nav-link active" href="/checkout">
|
||||
<i class="bi bi-credit-card me-1"></i>Checkout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container py-4">
|
||||
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-4 border-bottom">
|
||||
<h1 class="h2 mb-0">
|
||||
<i class="bi bi-credit-card me-2 text-primary"></i>Secure Checkout
|
||||
</h1>
|
||||
<div class="btn-toolbar">
|
||||
<a href="/cart" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i>Back to Cart
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Checkout Steps -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="checkout-step active flex-fill me-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="step-number active">1</div>
|
||||
<div>
|
||||
<div class="fw-bold">Review Order</div>
|
||||
<small class="text-muted">Verify your items</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkout-step pending flex-fill me-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="step-number pending">2</div>
|
||||
<div>
|
||||
<div class="fw-bold">Payment</div>
|
||||
<small class="text-muted">Choose payment method</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkout-step pending flex-fill">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="step-number pending">3</div>
|
||||
<div>
|
||||
<div class="fw-bold">Confirmation</div>
|
||||
<small class="text-muted">Order complete</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Checkout Form -->
|
||||
<div class="col-lg-8">
|
||||
<!-- Step 1: Order Review -->
|
||||
<div id="step1" class="checkout-section">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-bag-check me-2"></i>Order Review
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if cart_details and cart_details.items %}
|
||||
{% for item in cart_details.items %}
|
||||
<div class="d-flex align-items-center p-3 border-bottom">
|
||||
<div class="me-3">
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
{% if item.product.category_id == "compute" %}
|
||||
<i class="bi bi-cpu text-primary"></i>
|
||||
{% elif item.product.category_id == "hardware" %}
|
||||
<i class="bi bi-hdd-rack text-success"></i>
|
||||
{% elif item.product.category_id == "gateways" %}
|
||||
<i class="bi bi-globe text-info"></i>
|
||||
{% elif item.product.category_id == "applications" %}
|
||||
<i class="bi bi-app text-warning"></i>
|
||||
{% elif item.product.category_id == "services" %}
|
||||
<i class="bi bi-person-workspace text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ item.product.name }}</h6>
|
||||
<small class="text-muted">{{ item.product.provider_name }}</small>
|
||||
<div class="mt-1">
|
||||
<span class="badge bg-light text-dark">Qty: {{ item.cart_item.quantity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold text-primary">{{ item.unit_price.formatted_display }}</div>
|
||||
<small class="text-muted">per unit</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="bi bi-cart-x fs-1 text-muted mb-3"></i>
|
||||
<h5 class="text-muted">No items in cart</h5>
|
||||
<a href="/marketplace" class="btn btn-primary">Browse Marketplace</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<button class="btn btn-primary btn-lg" id="complete-order-btn">
|
||||
Complete Order <i class="bi bi-lock ms-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Summary -->
|
||||
<div class="col-lg-4">
|
||||
<div class="order-summary p-4">
|
||||
<h5 class="mb-3">
|
||||
<i class="bi bi-receipt me-2 text-primary"></i>Order Summary
|
||||
</h5>
|
||||
|
||||
{% if cart_details %}
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal ({{ cart_details.item_count }} items)</span>
|
||||
<span class="fw-bold" id="subtotal-display">{{ cart_details.subtotal }} {{ cart_details.currency }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Platform fee</span>
|
||||
<span class="text-muted">Free</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Deployment</span>
|
||||
<span class="text-success fw-bold">Free</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between mb-4">
|
||||
<span class="fw-bold fs-5">Total</span>
|
||||
<span class="fw-bold text-primary fs-4" id="total-display">{{ cart_details.total }} {{ cart_details.currency }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Security Badges -->
|
||||
<div class="security-badge p-3 mb-3">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="bi bi-shield-check text-success me-2 fs-5"></i>
|
||||
<div>
|
||||
<div class="fw-bold small">Secure Checkout</div>
|
||||
<div class="small text-muted">256-bit SSL encryption</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-lock text-success me-2 fs-5"></i>
|
||||
<div>
|
||||
<div class="fw-bold small">Privacy Protected</div>
|
||||
<div class="small text-muted">Your data is safe with us</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Support -->
|
||||
<div class="text-center">
|
||||
<small class="text-muted">
|
||||
Need help? <a href="https://threefoldfaq.crisp.help/en/" class="text-decoration-none" target="_blank">Contact Support</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loadingOverlay" class="position-fixed top-0 start-0 w-100 h-100 d-none" style="background: rgba(0,0,0,0.5); z-index: 9999;">
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<div class="spinner-border text-light" role="status" style="width: 3rem; height: 3rem;">
|
||||
<span class="visually-hidden">Processing...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Modal + Errors utilities (must load before checkout.js) -->
|
||||
<script src="/static/js/modal-system.js"></script>
|
||||
<script src="/static/js/utils/errors.js"></script>
|
||||
|
||||
<!-- JSON hydration for checkout (CSP-safe) -->
|
||||
<script type="application/json" id="checkout-hydration">
|
||||
{
|
||||
"user_currency": {{ user_currency | default(value='USD') | json_encode() | safe }},
|
||||
"cart_details": {% if cart_details is defined %}{{ cart_details | json_encode() | safe }}{% else %}null{% endif %}
|
||||
}
|
||||
</script>
|
||||
<!-- External JS (CSP-compliant) -->
|
||||
<script src="/static/js/base.js"></script>
|
||||
<script src="/static/js/checkout.js"></script>
|
||||
</body>
|
||||
</html>
|
445
src/views/marketplace/compute_resources.html
Normal file
445
src/views/marketplace/compute_resources.html
Normal file
@@ -0,0 +1,445 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Compute Resources{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>Compute Resources (Slices)</h1>
|
||||
<p class="lead">Find and acquire the compute resources you need for your workloads.</p>
|
||||
|
||||
<!-- Enhanced Filter Section -->
|
||||
<div class="filter-section p-4 mb-4">
|
||||
<h5 class="mb-3">Filter Resources</h5>
|
||||
<form id="filterForm" method="GET">
|
||||
<div class="enhanced-filters">
|
||||
<div>
|
||||
<label class="form-label">Location</label>
|
||||
<select name="location" class="form-select">
|
||||
<option value="">All Locations</option>
|
||||
<option value="Belgium">Belgium</option>
|
||||
<option value="Austria">Austria</option>
|
||||
<option value="Switzerland">Switzerland</option>
|
||||
<option value="Netherlands">Netherlands</option>
|
||||
<option value="Germany">Germany</option>
|
||||
<option value="France">France</option>
|
||||
<option value="UK">UK</option>
|
||||
<option value="Canada">Canada</option>
|
||||
<option value="USA">USA</option>
|
||||
<option value="Japan">Japan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Min vCores</label>
|
||||
<select name="min_cores" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="1">1 Core+</option>
|
||||
<option value="2">2 Cores+</option>
|
||||
<option value="4">4 Cores+</option>
|
||||
<option value="8">8 Cores+</option>
|
||||
<option value="16">16 Cores+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Min RAM (GB)</label>
|
||||
<select name="min_memory" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="4">4 GB+</option>
|
||||
<option value="8">8 GB+</option>
|
||||
<option value="16">16 GB+</option>
|
||||
<option value="32">32 GB+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Min Storage (GB)</label>
|
||||
<select name="min_storage" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="200">200 GB+</option>
|
||||
<option value="500">500 GB+</option>
|
||||
<option value="1000">1 TB+</option>
|
||||
<option value="2000">2 TB+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Min Uptime (%)</label>
|
||||
<select name="min_uptime" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="95">95%+</option>
|
||||
<option value="98">98%+</option>
|
||||
<option value="99">99%+</option>
|
||||
<option value="99.9">99.9%+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Min Bandwidth (Mbps)</label>
|
||||
<select name="min_bandwidth" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="100">100 Mbps+</option>
|
||||
<option value="500">500 Mbps+</option>
|
||||
<option value="1000">1 Gbps+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Price Range ($)</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" name="min_price" class="form-control" placeholder="Min" style="width: 80px;">
|
||||
<input type="number" name="max_price" class="form-control" placeholder="Max" style="width: 80px;">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
||||
<a href="/marketplace/compute" class="btn btn-outline-secondary ms-2">Clear</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Table Section -->
|
||||
{% if compute_products and compute_products | length > 0 %}
|
||||
<div class="compute-table">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 15%;">Type</th>
|
||||
<th style="width: 12%;">Provider</th>
|
||||
<th style="width: 10%;">Location</th>
|
||||
<th style="width: 8%;">vCores</th>
|
||||
<th style="width: 8%;">RAM</th>
|
||||
<th style="width: 10%;">Storage (SSD)</th>
|
||||
<th style="width: 10%;">Uptime SLA</th>
|
||||
<th style="width: 10%;">Bandwidth SLA</th>
|
||||
<th style="width: 8%;">Price/Hour</th>
|
||||
<th style="width: 9%;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for product_data in compute_products %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="slice-name">{{ product_data.product.name }}</div>
|
||||
<div class="slice-info">
|
||||
<small class="text-muted d-block">
|
||||
{% if product_data.product.attributes.cpu_cores %}
|
||||
{{ product_data.product.attributes.cpu_cores.value }}x Base Unit
|
||||
{% else %}
|
||||
Compute slice
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="provider-name">
|
||||
{% if product_data.product.attributes.farmer_email %}
|
||||
{{ product_data.product.attributes.farmer_email.value | truncate(length=15) }}
|
||||
{% else %}
|
||||
{% if product_data.product.provider %}{{ product_data.product.provider }}{% else %}Unknown{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if product_data.product.metadata.location %}
|
||||
<span class="location-badge node-location"
|
||||
data-location="{{ product_data.product.metadata.location }}">
|
||||
{{ product_data.product.metadata.location }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if product_data.product.attributes.cpu_cores %}
|
||||
<span class="spec-value">{{ product_data.product.attributes.cpu_cores.value }}</span>
|
||||
<span class="spec-unit">cores</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if product_data.product.attributes.memory_gb %}
|
||||
<span class="spec-value">{{ product_data.product.attributes.memory_gb.value }}</span>
|
||||
<span class="spec-unit">GB</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if product_data.product.attributes.storage_gb %}
|
||||
<span class="spec-value">{{ product_data.product.attributes.storage_gb.value }}</span>
|
||||
<span class="spec-unit">GB</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if product_data.product.attributes.uptime_percentage %}
|
||||
<div class="sla-indicator sla-good">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
<span>{{ product_data.product.attributes.uptime_percentage.value | format_decimal(precision=1) }}%</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if product_data.product.attributes.bandwidth_mbps %}
|
||||
<div class="sla-indicator sla-good">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
<span>{{ product_data.product.attributes.bandwidth_mbps.value }} Mbps</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="sla-indicator sla-good">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
<span>1000 Mbps</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="price-display">{{ product_data.formatted_price }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-success btn-sm-custom buy-now-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}"
|
||||
data-category="compute">
|
||||
<i class="bi bi-lightning-charge"></i> Buy Now
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm-custom add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus"></i> Add to Cart
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary btn-sm-custom" href="/products/{{ product_data.product.id }}">
|
||||
<i class="bi bi-eye"></i> View
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-inbox display-1 text-muted"></i>
|
||||
<h4 class="mt-3">No Compute Resources Available</h4>
|
||||
<p class="text-muted">Check back later for new compute resources.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.total_pages > 1 %}
|
||||
<nav aria-label="Resource pages" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Previous Page -->
|
||||
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
||||
{% if pagination.has_previous %}
|
||||
<a class="page-link" href="?page={{ pagination.previous_page }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 1 -->
|
||||
<li class="page-item {% if pagination.current_page == 0 %}active{% endif %}">
|
||||
{% if pagination.current_page == 0 %}
|
||||
<span class="page-link">1</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=0">1</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 2 (if exists) -->
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<li class="page-item {% if pagination.current_page == 1 %}active{% endif %}">
|
||||
{% if pagination.current_page == 1 %}
|
||||
<span class="page-link">2</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=1">2</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 3 (if exists) -->
|
||||
{% if pagination.total_pages > 2 %}
|
||||
<li class="page-item {% if pagination.current_page == 2 %}active{% endif %}">
|
||||
{% if pagination.current_page == 2 %}
|
||||
<span class="page-link">3</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=2">3</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 4 (if exists) -->
|
||||
{% if pagination.total_pages > 3 %}
|
||||
<li class="page-item {% if pagination.current_page == 3 %}active{% endif %}">
|
||||
{% if pagination.current_page == 3 %}
|
||||
<span class="page-link">4</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=3">4</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 5 (if exists) -->
|
||||
{% if pagination.total_pages > 4 %}
|
||||
<li class="page-item {% if pagination.current_page == 4 %}active{% endif %}">
|
||||
{% if pagination.current_page == 4 %}
|
||||
<span class="page-link">5</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=4">5</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Next Page -->
|
||||
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
||||
{% if pagination.has_next %}
|
||||
<a class="page-link" href="?page={{ pagination.next_page }}">Next</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Next</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="text-center text-muted mt-2">
|
||||
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
|
||||
({{ pagination.total_count }} total compute resources)
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.compute-table {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.compute-table th {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
padding: 12px 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.compute-table td {
|
||||
padding: 12px 8px;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
}
|
||||
|
||||
.compute-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.provider-name {
|
||||
font-weight: 600;
|
||||
color: #0d6efd;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.slice-info {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.slice-name {
|
||||
font-weight: 500;
|
||||
color: #212529;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.spec-value {
|
||||
font-weight: 500;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.spec-unit {
|
||||
color: #6c757d;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sla-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.sla-good { color: #28a745; }
|
||||
.sla-medium { color: #ffc107; }
|
||||
.sla-low { color: #dc3545; }
|
||||
|
||||
.price-display {
|
||||
font-weight: 600;
|
||||
color: #0d6efd;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-sm-custom {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.compute-table {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.compute-table th,
|
||||
.compute-table td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.enhanced-filters {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
align-items: end;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="application/json" id="compute-hydration">{}</script>
|
||||
<script src="/static/js/marketplace-compute.js"></script>
|
||||
{% endblock %}
|
376
src/views/marketplace/dashboard.html
Normal file
376
src/views/marketplace/dashboard.html
Normal file
@@ -0,0 +1,376 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Overview{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>Project Mycelium Overview</h1>
|
||||
<p class="lead">Explore the decentralized ecosystem of resources, applications, and services.</p>
|
||||
|
||||
<!-- Overview Stats -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-primary mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Compute Resources</h5>
|
||||
<p class="card-text">250+ available slices</p>
|
||||
<a href="/marketplace/compute" class="text-white">Browse Resources →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-success mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">3Nodes</h5>
|
||||
<p class="card-text">120+ certified nodes</p>
|
||||
<a href="/marketplace/3nodes" class="text-white">Browse 3Nodes →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-info mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Gateways</h5>
|
||||
<p class="card-text">45+ active gateways</p>
|
||||
<a href="/marketplace/gateways" class="text-white">Browse Gateways →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-warning mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Applications</h5>
|
||||
<p class="card-text">80+ self-healing apps</p>
|
||||
<a href="/marketplace/applications" class="text-white">Browse Applications →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Featured Items -->
|
||||
<h2 class="mt-5 mb-4">Featured Items</h2>
|
||||
<div class="row">
|
||||
{% if featured_products is defined and featured_products | length > 0 %}
|
||||
{% for item in featured_products %}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="marketplace-item">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
{% set cat = 'other' %}
|
||||
{% if item.product is defined and item.product.category_id is defined %}
|
||||
{% set cat = item.product.category_id %}
|
||||
{% endif %}
|
||||
<span class="badge bg-{% if cat == 'compute' %}primary{% elif cat == 'hardware' %}success{% elif cat == 'gateway' %}info{% elif cat == 'application' %}warning{% else %}secondary{% endif %} badge-category">
|
||||
{{ cat }}
|
||||
</span>
|
||||
{% if item.product is defined and item.product.metadata is defined and (item.product.metadata.featured | default(value=false)) %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% set prod_name = 'Unnamed' %}
|
||||
{% set prod_desc = '' %}
|
||||
{% set provider_name = '' %}
|
||||
{% set prod_id = '' %}
|
||||
{% set category_id = '' %}
|
||||
{% set base_price = 0 %}
|
||||
{% set provider_id = '' %}
|
||||
{% if item.product is defined %}
|
||||
{% if item.product.name is defined %}{% set prod_name = item.product.name %}{% endif %}
|
||||
{% if item.product.description is defined %}{% set prod_desc = item.product.description %}{% endif %}
|
||||
{% if item.product.provider_name is defined %}{% set provider_name = item.product.provider_name %}{% endif %}
|
||||
{% if item.product.id is defined %}{% set prod_id = item.product.id %}{% endif %}
|
||||
{% if item.product.category_id is defined %}{% set category_id = item.product.category_id %}{% endif %}
|
||||
{% if item.product.base_price is defined %}{% set base_price = item.product.base_price %}{% endif %}
|
||||
{% if item.product.provider_id is defined %}{% set provider_id = item.product.provider_id %}{% endif %}
|
||||
{% endif %}
|
||||
<h4>{{ prod_name }}</h4>
|
||||
<p>{{ prod_desc }}</p>
|
||||
|
||||
<!-- Product Specifications -->
|
||||
{% if item.product is defined and item.product.attributes is defined and item.product.attributes | length > 0 %}
|
||||
<div class="row mb-3">
|
||||
{% for attr_name, attr_value in item.product.attributes %}
|
||||
{% if attr_name == "cpu_cores" %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-cpu me-1"></i>
|
||||
<small>{{ attr_value.value | default(value='') }} Cores</small>
|
||||
</div>
|
||||
</div>
|
||||
{% elif attr_name == "memory_gb" %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-memory me-1"></i>
|
||||
<small>{{ attr_value.value | default(value='') }} GB RAM</small>
|
||||
</div>
|
||||
</div>
|
||||
{% elif attr_name == "storage_gb" %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-hdd me-1"></i>
|
||||
<small>{{ attr_value.value | default(value='') }} GB Storage</small>
|
||||
</div>
|
||||
</div>
|
||||
{% elif attr_name == "location" %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-geo-alt me-1"></i>
|
||||
<small>{{ attr_value.value | default(value='') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="text-primary fw-bold">{{ item.formatted_price | default(value="") }}</span>
|
||||
<br><small class="text-muted">by {{ provider_name }}</small>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-success buy-now-btn"
|
||||
data-product-id="{{ prod_id }}"
|
||||
data-product-name="{{ prod_name }}"
|
||||
data-category="{{ category_id }}"
|
||||
data-unit-price="{{ base_price }}"
|
||||
data-provider-id="{{ provider_id }}"
|
||||
data-provider-name="{{ provider_name }}"
|
||||
title="Buy instantly with your wallet balance">
|
||||
<i class="bi bi-lightning-fill me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary add-to-cart-btn"
|
||||
data-product-id="{{ prod_id }}"
|
||||
data-product-name="{{ prod_name }}"
|
||||
data-product-price="{{ item.formatted_price | default(value='') }}">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
<a href="/products/{{ prod_id }}" class="btn btn-sm btn-outline-primary">
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
No featured products available at the moment.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Popular Applications -->
|
||||
<h2 class="mt-5 mb-4">Popular Applications</h2>
|
||||
<div class="row">
|
||||
{% if popular_applications is defined and popular_applications | length > 0 %}
|
||||
{% for item in popular_applications %}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="marketplace-item">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<span class="badge bg-warning badge-category">Application</span>
|
||||
{% if item.product is defined and item.product.metadata is defined and (item.product.metadata.featured | default(value=false)) %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% set prod_name = 'Unnamed' %}
|
||||
{% set prod_desc = '' %}
|
||||
{% set provider_name = '' %}
|
||||
{% set prod_id = '' %}
|
||||
{% set category_id = '' %}
|
||||
{% set base_price = 0 %}
|
||||
{% set provider_id = '' %}
|
||||
{% set unit_price = 0 %}
|
||||
{% set currency = '' %}
|
||||
{% if item.product is defined %}
|
||||
{% if item.product.name is defined %}{% set prod_name = item.product.name %}{% endif %}
|
||||
{% if item.product.description is defined %}{% set prod_desc = item.product.description %}{% endif %}
|
||||
{% if item.product.provider_name is defined %}{% set provider_name = item.product.provider_name %}{% endif %}
|
||||
{% if item.product.id is defined %}{% set prod_id = item.product.id %}{% endif %}
|
||||
{% if item.product.category_id is defined %}{% set category_id = item.product.category_id %}{% endif %}
|
||||
{% if item.product.base_price is defined %}{% set base_price = item.product.base_price %}{% endif %}
|
||||
{% if item.product.provider_id is defined %}{% set provider_id = item.product.provider_id %}{% endif %}
|
||||
{% endif %}
|
||||
{% if item.price is defined %}
|
||||
{% if item.price.display_amount is defined %}{% set unit_price = item.price.display_amount %}{% endif %}
|
||||
{% if item.price.display_currency is defined %}{% set currency = item.price.display_currency %}{% endif %}
|
||||
{% endif %}
|
||||
<h4>{{ prod_name }}</h4>
|
||||
<p>{{ prod_desc | truncate(length=100) }}</p>
|
||||
|
||||
<!-- Application Specifications -->
|
||||
{% if item.product is defined and item.product.attributes is defined and item.product.attributes | length > 0 %}
|
||||
<div class="row mb-3">
|
||||
{% if item.product.attributes is defined and item.product.attributes.app_type is defined %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-app me-1"></i>
|
||||
<small>{{ item.product.attributes.app_type.value | default(value='') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if item.product.attributes is defined and item.product.attributes.deployment_type is defined %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-box me-1"></i>
|
||||
<small>{{ item.product.attributes.deployment_type.value | default(value='') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="text-primary fw-bold">{{ item.formatted_price | default(value="") }}</span>
|
||||
<br><small class="text-muted">by {{ provider_name }}</small>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-success buy-now-btn"
|
||||
data-product-id="{{ prod_id }}"
|
||||
data-product-name="{{ prod_name }}"
|
||||
data-unit-price="{{ unit_price }}"
|
||||
data-currency="{{ currency }}">
|
||||
<i class="bi bi-lightning-fill me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary add-to-cart-btn"
|
||||
data-product-id="{{ prod_id }}"
|
||||
data-product-name="{{ prod_name }}"
|
||||
data-category="{{ category_id }}"
|
||||
data-unit-price="{{ base_price }}"
|
||||
data-provider-id="{{ provider_id }}"
|
||||
data-provider-name="{{ provider_name }}"
|
||||
data-quantity="1"
|
||||
title="Buy instantly with your wallet balance">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
<a href="/products/{{ prod_id }}" class="btn btn-sm btn-outline-primary">
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
No popular applications available at the moment.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Available Services -->
|
||||
<h2 class="mt-5 mb-4">Available Services</h2>
|
||||
<div class="row">
|
||||
{% if available_services is defined and available_services | length > 0 %}
|
||||
{% for item in available_services %}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="marketplace-item">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<span class="badge bg-secondary badge-category">Service</span>
|
||||
{% if item.product is defined and item.product.metadata is defined and (item.product.metadata.featured | default(value=false)) %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% set prod_name = 'Unnamed' %}
|
||||
{% set prod_desc = '' %}
|
||||
{% set provider_name = '' %}
|
||||
{% set prod_id = '' %}
|
||||
{% set category_id = '' %}
|
||||
{% set base_price = 0 %}
|
||||
{% set provider_id = '' %}
|
||||
{% set unit_price = 0 %}
|
||||
{% set currency = '' %}
|
||||
{% if item.product is defined %}
|
||||
{% if item.product.name is defined %}{% set prod_name = item.product.name %}{% endif %}
|
||||
{% if item.product.description is defined %}{% set prod_desc = item.product.description %}{% endif %}
|
||||
{% if item.product.provider_name is defined %}{% set provider_name = item.product.provider_name %}{% endif %}
|
||||
{% if item.product.id is defined %}{% set prod_id = item.product.id %}{% endif %}
|
||||
{% if item.product.category_id is defined %}{% set category_id = item.product.category_id %}{% endif %}
|
||||
{% if item.product.base_price is defined %}{% set base_price = item.product.base_price %}{% endif %}
|
||||
{% if item.product.provider_id is defined %}{% set provider_id = item.product.provider_id %}{% endif %}
|
||||
{% endif %}
|
||||
{% if item.price is defined %}
|
||||
{% if item.price.display_amount is defined %}{% set unit_price = item.price.display_amount %}{% endif %}
|
||||
{% if item.price.display_currency is defined %}{% set currency = item.price.display_currency %}{% endif %}
|
||||
{% endif %}
|
||||
<h4>{{ prod_name }}</h4>
|
||||
<p>{{ prod_desc | truncate(length=100) }}</p>
|
||||
|
||||
<!-- Service Specifications -->
|
||||
{% if item.product is defined and item.product.attributes is defined and item.product.attributes | length > 0 %}
|
||||
<div class="row mb-3">
|
||||
{% if item.product.attributes is defined and item.product.attributes.service_type is defined %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-gear me-1"></i>
|
||||
<small>{{ item.product.attributes.service_type.value | default(value='') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if item.product.attributes is defined and item.product.attributes.expertise_level is defined %}
|
||||
<div class="col-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-star me-1"></i>
|
||||
<small>{{ item.product.attributes.expertise_level.value | default(value='') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<span class="text-primary fw-bold">{{ item.formatted_price | default(value="") }}</span>
|
||||
<br><small class="text-muted">by {{ provider_name }}</small>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-success buy-now-btn"
|
||||
data-product-id="{{ prod_id }}"
|
||||
data-product-name="{{ prod_name }}"
|
||||
data-unit-price="{{ unit_price }}"
|
||||
data-currency="{{ currency }}">
|
||||
<i class="bi bi-lightning-fill me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary add-to-cart-btn"
|
||||
data-product-id="{{ prod_id }}"
|
||||
data-product-name="{{ prod_name }}"
|
||||
data-category="{{ category_id }}"
|
||||
data-unit-price="{{ base_price }}"
|
||||
data-provider-id="{{ provider_id }}"
|
||||
data-provider-name="{{ provider_name }}"
|
||||
data-quantity="1"
|
||||
title="Buy instantly with your wallet balance">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
<a href="/products/{{ prod_id }}" class="btn btn-sm btn-outline-primary">
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
No services available at the moment.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script type="application/json" id="marketplace-dashboard-hydration">{}</script>
|
||||
<script src="/static/js/marketplace_dashboard.js"></script>
|
||||
{% endblock %}
|
320
src/views/marketplace/gateways.html
Normal file
320
src/views/marketplace/gateways.html
Normal file
@@ -0,0 +1,320 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Mycelium Gateways{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>Mycelium Gateways</h1>
|
||||
<p class="lead">Connect to the internet securely through the Mycelium network.</p>
|
||||
|
||||
<!-- Filter and Search Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Filter Gateways</h5>
|
||||
<form class="row g-3" id="filterForm" method="GET">
|
||||
<div class="col-md-3">
|
||||
<label for="locationFilter" class="form-label">Location</label>
|
||||
<select name="location" id="locationFilter" class="form-select">
|
||||
<option value="">All Locations</option>
|
||||
<option value="Belgium">Belgium</option>
|
||||
<option value="Austria">Austria</option>
|
||||
<option value="Switzerland">Switzerland</option>
|
||||
<option value="Netherlands">Netherlands</option>
|
||||
<option value="Germany">Germany</option>
|
||||
<option value="France">France</option>
|
||||
<option value="UK">UK</option>
|
||||
<option value="Canada">Canada</option>
|
||||
<option value="USA">USA</option>
|
||||
<option value="Japan">Japan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="speedFilter" class="form-label">Minimum Speed</label>
|
||||
<select name="bandwidth_mbps" id="speedFilter" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="100">100+ Mbps</option>
|
||||
<option value="500">500+ Mbps</option>
|
||||
<option value="1000">1+ Gbps</option>
|
||||
<option value="2500">2.5+ Gbps</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="minPriceFilter" class="form-label">Min Price ($)</label>
|
||||
<input type="number" name="min_price" id="minPriceFilter" class="form-control"
|
||||
value="" placeholder="0" min="0" step="1">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="maxPriceFilter" class="form-label">Max Price ($)</label>
|
||||
<input type="number" name="max_price" id="maxPriceFilter" class="form-control"
|
||||
value="" placeholder="300" min="0" step="1">
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary me-2">Apply Filters</button>
|
||||
<a href="/marketplace/gateways" class="btn btn-outline-secondary">Clear</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Available Gateways Section -->
|
||||
<div class="row">
|
||||
{% if gateway_products and gateway_products | length > 0 %}
|
||||
{% for product_data in gateway_products %}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="marketplace-item">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h4 class="mb-0">
|
||||
<a href="/products/{{ product_data.product.id }}" class="text-decoration-none text-dark">
|
||||
{{ product_data.product.name }}
|
||||
</a>
|
||||
</h4>
|
||||
{% if product_data.product.metadata.featured %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% elif product_data.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product_data.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Gateway Status -->
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="gateway-status-indicator bg-success me-2" title="Online"></div>
|
||||
<span>Online -
|
||||
{% if product_data.product.attributes.uptime_sla %}
|
||||
{{ product_data.product.attributes.uptime_sla.value }}
|
||||
{% else %}
|
||||
99.9%
|
||||
{% endif %}
|
||||
uptime in last 30 days
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Product Specifications -->
|
||||
<div class="row mb-3">
|
||||
{% if product_data.product.attributes.bandwidth_mbps %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-speedometer me-2"></i>
|
||||
<span>Speed: {{ product_data.product.attributes.bandwidth_mbps.value }} Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.uptime_sla %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
<span>SLA: {{ product_data.product.attributes.uptime_sla.value }} Uptime</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Additional Info -->
|
||||
<div class="row mb-3">
|
||||
{% if product_data.product.metadata.location %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-geo-alt me-2"></i>
|
||||
<span>Location: {{ product_data.product.metadata.location }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-clock-history me-2"></i>
|
||||
<span>Provider: {{ product_data.product.provider_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="mb-4">{{ product_data.product.description | truncate(length=120) }}</p>
|
||||
|
||||
<!-- Features (if available in tags) -->
|
||||
{% if product_data.product.metadata.tags and product_data.product.metadata.tags | length > 0 %}
|
||||
<div class="mb-3">
|
||||
<h6>Features:</h6>
|
||||
<ul class="small">
|
||||
{% for tag in product_data.product.metadata.tags %}
|
||||
<li>{{ tag | title }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Price and Actions -->
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="price-container">
|
||||
<span class="text-muted">Price:</span>
|
||||
<span class="text-primary fw-bold">{{ product_data.formatted_price }}</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success btn-sm buy-now-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}"
|
||||
data-category="gateways">
|
||||
<i class="bi bi-lightning-fill me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
<a href="/products/{{ product_data.product.id }}" class="btn btn-outline-secondary btn-sm">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-router display-1 text-muted"></i>
|
||||
<h4 class="mt-3">No Gateways Available</h4>
|
||||
<p class="text-muted">Check back later for new gateway services.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Gateway Configuration Guide -->
|
||||
<div class="card mt-5">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">How to Configure Your Gateway</h3>
|
||||
<p>Connecting your applications to a Mycelium Gateway is simple:</p>
|
||||
|
||||
<ol>
|
||||
<li>Choose a gateway from the options above</li>
|
||||
<li>Click the "Configure" button to start the setup process</li>
|
||||
<li>Register or select a <a href="/marketplace/names">Mycelium Name</a> for your gateway</li>
|
||||
<li>Configure domain routing and access controls</li>
|
||||
<li>Connect your application by updating the configuration with the provided Mycelium address</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-info mt-3">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Need help setting up your gateway? <a href="/marketplace/services">Our technical experts</a> can assist with configuration and optimization.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.total_pages > 1 %}
|
||||
<nav aria-label="Gateway pages" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Previous Page -->
|
||||
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
||||
{% if pagination.has_previous %}
|
||||
<a class="page-link" href="?page={{ pagination.previous_page }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 1 -->
|
||||
<li class="page-item {% if pagination.current_page == 0 %}active{% endif %}">
|
||||
{% if pagination.current_page == 0 %}
|
||||
<span class="page-link">1</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=0">1</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 2 (if exists) -->
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<li class="page-item {% if pagination.current_page == 1 %}active{% endif %}">
|
||||
{% if pagination.current_page == 1 %}
|
||||
<span class="page-link">2</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=1">2</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 3 (if exists) -->
|
||||
{% if pagination.total_pages > 2 %}
|
||||
<li class="page-item {% if pagination.current_page == 2 %}active{% endif %}">
|
||||
{% if pagination.current_page == 2 %}
|
||||
<span class="page-link">3</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=2">3</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 4 (if exists) -->
|
||||
{% if pagination.total_pages > 3 %}
|
||||
<li class="page-item {% if pagination.current_page == 3 %}active{% endif %}">
|
||||
{% if pagination.current_page == 3 %}
|
||||
<span class="page-link">4</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=3">4</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 5 (if exists) -->
|
||||
{% if pagination.total_pages > 4 %}
|
||||
<li class="page-item {% if pagination.current_page == 4 %}active{% endif %}">
|
||||
{% if pagination.current_page == 4 %}
|
||||
<span class="page-link">5</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=4">5</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Next Page -->
|
||||
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
||||
{% if pagination.has_next %}
|
||||
<a class="page-link" href="?page={{ pagination.next_page }}">Next</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Next</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="text-center text-muted mt-2">
|
||||
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
|
||||
({{ pagination.total_count }} total gateways)
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.gateway-status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.gateway-status-indicator.bg-success {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.gateway-status-indicator.bg-warning {
|
||||
background-color: #ffc107;
|
||||
}
|
||||
|
||||
.gateway-status-indicator.bg-danger {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.spec-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.price-container {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/static/js/marketplace-category.js"></script>
|
||||
{% endblock %}
|
160
src/views/marketplace/layout.html
Normal file
160
src/views/marketplace/layout.html
Normal file
@@ -0,0 +1,160 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<!-- Sidebar Toggle Button (mobile only) -->
|
||||
<button class="btn sidebar-toggle d-md-none" id="sidebarToggleBtn" aria-label="Toggle sidebar navigation" aria-expanded="false" aria-controls="sidebar">
|
||||
<i class="bi bi-list"></i> Menu
|
||||
</button>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" id="sidebar">
|
||||
<div class="position-sticky pt-3">
|
||||
<h5 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mb-1 text-muted">
|
||||
<span>Marketplace</span>
|
||||
</h5>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'dashboard' %}active{% endif %}" href="/marketplace">
|
||||
<i class="bi bi-speedometer2 me-1"></i>
|
||||
Overview
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'statistics' %}active{% endif %}" href="/marketplace/statistics">
|
||||
<i class="bi bi-graph-up me-1"></i>
|
||||
Statistics
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'compute_resources' %}active{% endif %}" href="/marketplace/compute">
|
||||
<i class="bi bi-cpu me-1"></i>
|
||||
Compute Resources
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'three_nodes' %}active{% endif %}" href="/marketplace/3nodes">
|
||||
<i class="bi bi-hdd-rack me-1"></i>
|
||||
3Nodes
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'gateways' %}active{% endif %}" href="/marketplace/gateways">
|
||||
<i class="bi bi-globe me-1"></i>
|
||||
Mycelium Gateways
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'applications' %}active{% endif %}" href="/marketplace/applications">
|
||||
<i class="bi bi-app me-1"></i>
|
||||
Application Solutions
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_section == 'services' %}active{% endif %}" href="/marketplace/services">
|
||||
<i class="bi bi-person-workspace me-1"></i>
|
||||
Human Energy Services
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content-wrapper position-relative">
|
||||
<!-- Sidebar Backdrop (mobile only) - positioned here so it doesn't cover the sidebar -->
|
||||
<div class="sidebar-backdrop d-md-none" id="sidebarBackdrop"></div>
|
||||
{% block marketplace_content %}
|
||||
<!-- Content will be injected here by child templates -->
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<!-- Add Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css">
|
||||
|
||||
<style>
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 56px; /* Navbar height */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
padding: 20px 0 0;
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
height: 100%; /* Full height */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Footer should only appear on the right side */
|
||||
@media (min-width: 768px) {
|
||||
.footer {
|
||||
margin-left: 25%; /* Matches the width of col-md-3 */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.footer {
|
||||
margin-left: 16.666667%; /* Matches the width of col-lg-2 */
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #2470dc;
|
||||
}
|
||||
|
||||
.dashboard-section {
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: #f8f9fa;
|
||||
box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075);
|
||||
}
|
||||
|
||||
.marketplace-item {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
background-color: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.marketplace-item:hover {
|
||||
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.badge-category {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Adjust for fixed navbar */
|
||||
main.py-4 {
|
||||
padding-top: 4.5rem !important;
|
||||
}
|
||||
|
||||
/* Removed conflicting media query */
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/static/js/marketplace_layout.js"></script>
|
||||
<!-- buy-now.js is now included in base.html -->
|
||||
{% endblock %}
|
442
src/views/marketplace/order_confirmation.html
Normal file
442
src/views/marketplace/order_confirmation.html
Normal file
@@ -0,0 +1,442 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Order Confirmation - Project Mycelium</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 4rem;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.confirmation-card {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
|
||||
border: 2px solid #28a745;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.order-details {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.next-steps {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border: 1px solid #2196f3;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0b5ed7 0%, #0a58ca 100%);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-confirmed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.order-tracking-timeline {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tracking-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tracking-step:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 32px;
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: #dee2e6;
|
||||
}
|
||||
|
||||
.tracking-step.completed::after {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.tracking-step.active::after {
|
||||
background: #007bff;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/images/logo_dark.png" alt="ThreeFold Logo" class="me-2">
|
||||
<span>Project Mycelium</span>
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/marketplace">
|
||||
<i class="bi bi-shop me-1"></i>Marketplace
|
||||
</a>
|
||||
<a class="nav-link" href="/orders">
|
||||
<i class="bi bi-list-ul me-1"></i>My Orders
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container py-4">
|
||||
|
||||
|
||||
<!-- Success Message -->
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-md-8">
|
||||
<div class="confirmation-card p-5 text-center">
|
||||
<i class="bi bi-check-circle success-icon mb-3"></i>
|
||||
<h1 class="h2 mb-3">Order Confirmed!</h1>
|
||||
<p class="lead mb-4">
|
||||
Thank you for your order. Your ThreeFold resources are being prepared for deployment.
|
||||
</p>
|
||||
{% if order %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-2">
|
||||
<strong>Order Number:</strong>
|
||||
</div>
|
||||
<div class="h5 text-primary">#{{ order.order_id }}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-2">
|
||||
<strong>Confirmation Code:</strong>
|
||||
</div>
|
||||
<div class="h5 text-success">{{ order.confirmation_number }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Order Details -->
|
||||
<div class="col-lg-8">
|
||||
{% if order %}
|
||||
<div class="order-details p-4 mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3><i class="bi bi-receipt me-2"></i>Order Details</h3>
|
||||
<span class="status-badge status-confirmed">
|
||||
<i class="bi bi-check-circle me-1"></i>{{ order.status }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<strong class="text-muted">Order Date:</strong>
|
||||
<div>{{ order.created_at }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong class="text-muted">Payment Method:</strong>
|
||||
<div>{{ order.payment_method }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<strong class="text-muted">Total Amount:</strong>
|
||||
<div class="h5 text-primary">{{ order.total }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong class="text-muted">Currency:</strong>
|
||||
<div>{{ order.currency }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="mb-3">Ordered Items</h5>
|
||||
{% for item in order.items %}
|
||||
<div class="d-flex align-items-center p-3 border rounded mb-3">
|
||||
<div class="me-3">
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
{% if item.product_category == "compute" %}
|
||||
<i class="bi bi-cpu text-primary"></i>
|
||||
{% elif item.product_category == "hardware" %}
|
||||
<i class="bi bi-hdd-rack text-success"></i>
|
||||
{% elif item.product_category == "gateways" %}
|
||||
<i class="bi bi-globe text-info"></i>
|
||||
{% elif item.product_category == "applications" %}
|
||||
<i class="bi bi-app text-warning"></i>
|
||||
{% elif item.product_category == "services" %}
|
||||
<i class="bi bi-person-workspace text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ item.product_name }}</h6>
|
||||
<small class="text-muted">{{ item.provider_name }}</small>
|
||||
<div class="mt-1">
|
||||
<span class="badge bg-light text-dark">Qty: {{ item.quantity }}</span>
|
||||
<span class="badge bg-secondary ms-1">{{ item.product_category|title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold">{{ item.unit_price }}</div>
|
||||
<small class="text-muted">per unit</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Next Steps -->
|
||||
<div class="next-steps p-4">
|
||||
<h5 class="mb-3">
|
||||
<i class="bi bi-arrow-right-circle me-2"></i>What's Next?
|
||||
</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-1-circle fs-4 text-primary"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">Processing</div>
|
||||
<small class="text-muted">Your order is being processed and resources are being allocated.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-2-circle fs-4 text-info"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">Deployment</div>
|
||||
<small class="text-muted">Resources will be deployed to the ThreeFold Grid within 24 hours.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-3-circle fs-4 text-success"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">Access</div>
|
||||
<small class="text-muted">You'll receive access credentials and connection details via email.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-4-circle fs-4 text-warning"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">Support</div>
|
||||
<small class="text-muted">Our support team is available 24/7 to help with any questions.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-gear me-2"></i>Quick Actions
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/orders" class="btn btn-primary">
|
||||
<i class="bi bi-list-ul me-2"></i>View All Orders
|
||||
</a>
|
||||
<a href="/marketplace" class="btn btn-outline-primary">
|
||||
<i class="bi bi-shop me-2"></i>Continue Shopping
|
||||
</a>
|
||||
<a href="/dashboard" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-speedometer2 me-2"></i>Go to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h6 class="mb-3">Need Help?</h6>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/docs" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-book me-1"></i>Docs
|
||||
</a>
|
||||
<a href="https://threefoldfaq.crisp.help/en/" class="btn btn-sm btn-outline-success" target="_blank">
|
||||
<i class="bi bi-chat-dots me-1"></i>Live Chat
|
||||
</a>
|
||||
<a href="mailto:support@threefold.io" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-envelope me-1"></i>Email Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Tracking -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-truck me-2"></i>Track Your Order
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="small text-muted mb-3">
|
||||
You can track the status of your order and deployment progress in real-time.
|
||||
</p>
|
||||
|
||||
<!-- Mock Order Tracking Timeline -->
|
||||
{% if order %}
|
||||
<div class="order-tracking-timeline mb-3">
|
||||
<div class="tracking-step completed">
|
||||
<div class="step-icon">
|
||||
<i class="bi bi-check-circle-fill text-success"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="fw-bold">Order Confirmed</div>
|
||||
<small class="text-muted">{{ order.created_at }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tracking-step active">
|
||||
<div class="step-icon">
|
||||
<i class="bi bi-gear-fill text-primary"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="fw-bold">Processing</div>
|
||||
<small class="text-muted">Resources being allocated</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tracking-step pending">
|
||||
<div class="step-icon">
|
||||
<i class="bi bi-cloud-arrow-up text-muted"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="fw-bold">Deploying</div>
|
||||
<small class="text-muted">Setting up your resources</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tracking-step pending">
|
||||
<div class="step-icon">
|
||||
<i class="bi bi-check-all text-muted"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="fw-bold">Ready</div>
|
||||
<small class="text-muted">Resources available</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<a href="/orders/{{ order.order_id }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-eye me-1"></i>View Full Order Details
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="d-grid">
|
||||
<a href="/orders" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-list-ul me-1"></i>View Orders
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Confirmation -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-envelope-check fs-1 text-success mb-2"></i>
|
||||
<h6>Email Confirmation Sent</h6>
|
||||
<p class="small text-muted">
|
||||
A detailed confirmation has been sent to your email address with all order information and next steps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Order confirmation page loaded');
|
||||
|
||||
// Auto-scroll to top
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
// Show success animation
|
||||
const successIcon = document.querySelector('.success-icon');
|
||||
if (successIcon) {
|
||||
successIcon.style.transform = 'scale(0)';
|
||||
setTimeout(() => {
|
||||
successIcon.style.transition = 'transform 0.5s ease-out';
|
||||
successIcon.style.transform = 'scale(1)';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
311
src/views/marketplace/order_detail.html
Normal file
311
src/views/marketplace/order_detail.html
Normal file
@@ -0,0 +1,311 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Order Details - Project Mycelium</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.order-header {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border: 2px solid #2196f3;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.order-details {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-confirmed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0b5ed7 0%, #0a58ca 100%);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/images/logo_dark.png" alt="ThreeFold Logo" class="me-2">
|
||||
<span>Project Mycelium</span>
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/marketplace">
|
||||
<i class="bi bi-shop me-1"></i>Marketplace
|
||||
</a>
|
||||
<a class="nav-link active" href="/orders">
|
||||
<i class="bi bi-list-ul me-1"></i>My Orders
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container py-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/" class="text-decoration-none">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace" class="text-decoration-none">Marketplace</a></li>
|
||||
<li class="breadcrumb-item"><a href="/orders" class="text-decoration-none">Orders</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Order Details</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{% if order %}
|
||||
<!-- Order Header -->
|
||||
<div class="order-header p-4 mb-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h1 class="h2 mb-2">
|
||||
<i class="bi bi-receipt me-2"></i>Order #{{ order.order_id }}
|
||||
</h1>
|
||||
<p class="mb-0 text-muted">
|
||||
<i class="bi bi-calendar me-1"></i>Placed on {{ order.created_at }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end">
|
||||
<span class="status-badge status-{{ order.status|lower }}">
|
||||
<i class="bi bi-check-circle me-1"></i>{{ order.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Order Details -->
|
||||
<div class="col-lg-8">
|
||||
<div class="order-details p-4 mb-4">
|
||||
<h3 class="mb-4">
|
||||
<i class="bi bi-box-seam me-2"></i>Order Items
|
||||
</h3>
|
||||
|
||||
{% for item in order.items %}
|
||||
<div class="d-flex align-items-center p-3 border rounded mb-3">
|
||||
<div class="me-3">
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
||||
{% if item.product_category == "compute" %}
|
||||
<i class="bi bi-cpu text-primary"></i>
|
||||
{% elif item.product_category == "hardware" %}
|
||||
<i class="bi bi-hdd-rack text-success"></i>
|
||||
{% elif item.product_category == "gateways" %}
|
||||
<i class="bi bi-globe text-info"></i>
|
||||
{% elif item.product_category == "applications" %}
|
||||
<i class="bi bi-app text-warning"></i>
|
||||
{% elif item.product_category == "services" %}
|
||||
<i class="bi bi-person-workspace text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ item.product_name }}</h6>
|
||||
<small class="text-muted">{{ item.provider_name }}</small>
|
||||
<div class="mt-1">
|
||||
<span class="badge bg-light text-dark">Qty: {{ item.quantity }}</span>
|
||||
<span class="badge bg-secondary ms-1">{{ item.product_category|title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold">{{ item.unit_price }}</div>
|
||||
<small class="text-muted">per unit</small>
|
||||
<div class="text-primary fw-bold">{{ item.total_price }}</div>
|
||||
<small class="text-muted">total</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Order Summary -->
|
||||
<div class="border-top pt-3 mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<strong class="text-muted">Payment Method:</strong>
|
||||
<div>{{ order.payment_method }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong class="text-muted">Currency:</strong>
|
||||
<div>{{ order.currency }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-md-end">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal:</span>
|
||||
<span>{{ order.subtotal }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Platform fee:</span>
|
||||
<span class="text-muted">Free</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-bold fs-5">Total:</span>
|
||||
<span class="fw-bold text-primary fs-4">{{ order.total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-gear me-2"></i>Order Actions
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/orders" class="btn btn-primary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Orders
|
||||
</a>
|
||||
<a href="/marketplace" class="btn btn-outline-primary">
|
||||
<i class="bi bi-shop me-2"></i>Continue Shopping
|
||||
</a>
|
||||
<button class="btn btn-outline-secondary js-print">
|
||||
<i class="bi bi-printer me-2"></i>Print Order
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h6 class="mb-3">Need Help?</h6>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/docs" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-book me-1"></i>Docs
|
||||
</a>
|
||||
<a href="https://threefoldfaq.crisp.help/en/" class="btn btn-sm btn-outline-success" target="_blank">
|
||||
<i class="bi bi-chat-dots me-1"></i>Live Chat
|
||||
</a>
|
||||
<a href="mailto:support@threefold.io" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-envelope me-1"></i>Email Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Status Timeline -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-clock-history me-2"></i>Order Timeline
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="timeline">
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-check-circle-fill text-success fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">Order Placed</div>
|
||||
<small class="text-muted">{{ order.created_at }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if order.status == "Confirmed" %}
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-check-circle-fill text-success fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">Payment Confirmed</div>
|
||||
<small class="text-muted">Payment processed successfully</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex mb-3">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-circle text-muted fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted">Deployment</div>
|
||||
<small class="text-muted">Resources being deployed</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
|
||||
<h3>Order Not Found</h3>
|
||||
<p class="text-muted">The requested order could not be found.</p>
|
||||
<a href="/orders" class="btn btn-primary">View All Orders</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script type="application/json" id="order-detail-data">{}</script>
|
||||
<script src="/static/js/print-utils.js"></script>
|
||||
</body>
|
||||
</html>
|
210
src/views/marketplace/order_invoice.html
Normal file
210
src/views/marketplace/order_invoice.html
Normal file
@@ -0,0 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Order Invoice - Project Mycelium</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f8f9fa; }
|
||||
.navbar-brand img { height: 30px; }
|
||||
.invoice-header {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border: 2px solid #2196f3;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.invoice-details {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.invoice-number { font-size: 1.5rem; font-weight: bold; color: #2196f3; }
|
||||
.card { border: none; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
||||
.btn-primary { background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%); border: none; }
|
||||
.btn-primary:hover { background: linear-gradient(135deg, #0b5ed7 0%, #0a58ca 100%); }
|
||||
.invoice-table th { background-color: #f8f9fa; border-top: none; }
|
||||
.total-amount { font-size: 2rem; font-weight: bold; color: #2196f3; }
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
body { background-color: white !important; }
|
||||
.card, .invoice-details { box-shadow: none !important; border: 1px solid #dee2e6 !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark no-print">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/images/logo_dark.png" alt="ThreeFold Logo" class="me-2">
|
||||
<span>Project Mycelium</span>
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/marketplace">
|
||||
<i class="bi bi-shop me-1"></i>Marketplace
|
||||
</a>
|
||||
<a class="nav-link" href="/orders">
|
||||
<i class="bi bi-list-ul me-1"></i>My Orders
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container py-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4 no-print">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/" class="text-decoration-none">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace" class="text-decoration-none">Marketplace</a></li>
|
||||
<li class="breadcrumb-item"><a href="/orders" class="text-decoration-none">Orders</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Order Invoice</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{% if order %}
|
||||
<!-- Invoice Header -->
|
||||
<div class="invoice-header p-4 mb-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h1 class="h2 mb-2">
|
||||
<i class="bi bi-receipt me-2"></i>THREEFOLD MARKETPLACE INVOICE
|
||||
</h1>
|
||||
<div class="invoice-number">INV-{{ order.order_id }}</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end">
|
||||
<div class="mb-2"><strong>Invoice Date:</strong> {{ invoice_date }}</div>
|
||||
<div><strong>Due Date:</strong> {{ due_date }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Invoice Details -->
|
||||
<div class="col-lg-8">
|
||||
<div class="invoice-details p-4 mb-4">
|
||||
<!-- Seller and Bill To -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">SOLD BY:</h6>
|
||||
<div class="fw-bold">Project Mycelium</div>
|
||||
<div>support@threefold.io</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">BILL TO:</h6>
|
||||
{% if user %}
|
||||
<div class="fw-bold">{{ user.email }}</div>
|
||||
{% if user.name %}<div>{{ user.name }}</div>{% endif %}
|
||||
{% if user.country %}<div>{{ user.country }}</div>{% endif %}
|
||||
{% else %}
|
||||
<div class="fw-bold">Account Holder</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Items Table -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-muted mb-3">ITEMS:</h6>
|
||||
<table class="table invoice-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th class="text-center">Qty</th>
|
||||
<th class="text-center">Unit Price</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in order.items %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold">{{ item.product_name }}</div>
|
||||
<small class="text-muted">{{ item.provider_name }} • {{ item.product_category|title }}</small>
|
||||
</td>
|
||||
<td class="text-center">{{ item.quantity }}</td>
|
||||
<td class="text-center">{{ item.unit_price }}</td>
|
||||
<td class="text-end">{{ item.total_price }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="border-top pt-4 mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-3">PAYMENT DETAILS:</h6>
|
||||
<p class="mb-2">Payment Method: <strong>{{ order.payment_method }}</strong></p>
|
||||
<p class="mb-2">Currency: <strong>{{ order.currency }}</strong></p>
|
||||
<p class="mb-0">Invoice generated: <small class="text-muted">{{ generated_date }}</small></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-md-end">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal:</span>
|
||||
<span>{{ order.subtotal }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Platform fee:</span>
|
||||
<span class="text-muted">Free</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-bold fs-5">TOTAL AMOUNT:</span>
|
||||
<span class="total-amount">{{ order.total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Sidebar -->
|
||||
<div class="col-lg-4 no-print">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-gear me-2"></i>Invoice Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/orders/{{ order.order_id }}" class="btn btn-primary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Order
|
||||
</a>
|
||||
<button class="btn btn-success js-print">
|
||||
<i class="bi bi-printer me-2"></i>Print Invoice
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<h6 class="mb-3">Need Help?</h6>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/docs" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-book me-1"></i>Documentation
|
||||
</a>
|
||||
<a href="https://threefoldfaq.crisp.help/en/" class="btn btn-sm btn-outline-success" target="_blank">
|
||||
<i class="bi bi-chat-dots me-1"></i>Live Chat
|
||||
</a>
|
||||
<a href="mailto:support@threefold.io" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-envelope me-1"></i>Email Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-exclamation-triangle fs-1 text-warning mb-3"></i>
|
||||
<h3>Order Not Found</h3>
|
||||
<p class="text-muted">The requested order could not be found.</p>
|
||||
<a href="/orders" class="btn btn-primary">View All Orders</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script type="application/json" id="invoice-data">{}</script>
|
||||
<script src="/static/js/print-utils.js"></script>
|
||||
</body>
|
||||
</html>
|
395
src/views/marketplace/orders.html
Normal file
395
src/views/marketplace/orders.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>My Orders - Project Mycelium</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.order-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.status-confirmed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background: #cce5ff;
|
||||
color: #004085;
|
||||
border: 1px solid #99d6ff;
|
||||
}
|
||||
|
||||
.status-deployed {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0b5ed7 0%, #0a58ca 100%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/images/logo_dark.png" alt="ThreeFold Logo" class="me-2">
|
||||
<span>Project Mycelium</span>
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/marketplace">
|
||||
<i class="bi bi-shop me-1"></i>Marketplace
|
||||
</a>
|
||||
<a class="nav-link" href="/cart">
|
||||
<i class="bi bi-cart3 me-1"></i>Cart
|
||||
</a>
|
||||
<a class="nav-link active" href="/orders">
|
||||
<i class="bi bi-list-ul me-1"></i>My Orders
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container py-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/" class="text-decoration-none">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/marketplace" class="text-decoration-none">Marketplace</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">My Orders</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-4 border-bottom">
|
||||
<h1 class="h2 mb-0">
|
||||
<i class="bi bi-list-ul me-2 text-primary"></i>My Orders
|
||||
</h1>
|
||||
<div class="btn-toolbar">
|
||||
<a href="/marketplace" class="btn btn-primary">
|
||||
<i class="bi bi-shop me-1"></i>Continue Shopping
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Orders List -->
|
||||
<div class="col-lg-8">
|
||||
{% if orders and orders|length > 0 %}
|
||||
{% for order in orders %}
|
||||
<div class="order-card card mb-4">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="mb-1">Order #{{ order.order_id }}</h5>
|
||||
<small class="text-muted">Placed on {{ order.created_at }}</small>
|
||||
</div>
|
||||
<span class="status-badge status-{{ order.status|lower }}">
|
||||
{% if order.status == "Pending" %}
|
||||
<i class="bi bi-clock me-1"></i>
|
||||
{% elif order.status == "Confirmed" %}
|
||||
<i class="bi bi-check-circle me-1"></i>
|
||||
{% elif order.status == "Processing" %}
|
||||
<i class="bi bi-gear me-1"></i>
|
||||
{% elif order.status == "Deployed" %}
|
||||
<i class="bi bi-cloud-check me-1"></i>
|
||||
{% elif order.status == "Completed" %}
|
||||
<i class="bi bi-check-all me-1"></i>
|
||||
{% elif order.status == "Cancelled" %}
|
||||
<i class="bi bi-x-circle me-1"></i>
|
||||
{% endif %}
|
||||
{{ order.status }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h6 class="mb-3">Items ({{ order.items|length }})</h6>
|
||||
{% for item in order.items %}
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="me-3">
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
{% if item.product_category == "compute" %}
|
||||
<i class="bi bi-cpu text-primary"></i>
|
||||
{% elif item.product_category == "hardware" %}
|
||||
<i class="bi bi-hdd-rack text-success"></i>
|
||||
{% elif item.product_category == "gateways" %}
|
||||
<i class="bi bi-globe text-info"></i>
|
||||
{% elif item.product_category == "applications" %}
|
||||
<i class="bi bi-app text-warning"></i>
|
||||
{% elif item.product_category == "services" %}
|
||||
<i class="bi bi-person-workspace text-secondary"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-box text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold">{{ item.product_name }}</div>
|
||||
<small class="text-muted">{{ item.provider_name }} • Qty: {{ item.quantity }}</small>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold">{{ item.total_price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-end">
|
||||
<div class="mb-2">
|
||||
<strong class="text-muted">Total Amount:</strong>
|
||||
</div>
|
||||
<div class="h4 text-primary mb-3">{{ order.total }}</div>
|
||||
<div class="mb-2">
|
||||
<strong class="text-muted">Payment:</strong>
|
||||
</div>
|
||||
<div class="mb-3">{{ order.payment_method }}</div>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/orders/{{ order.order_id }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-eye me-1"></i>View Details
|
||||
</a>
|
||||
{% if order.status == "Deployed" or order.status == "Completed" %}
|
||||
<a href="#" class="btn btn-outline-success btn-sm">
|
||||
<i class="bi bi-play-circle me-1"></i>Access Resources
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if order.confirmation_number %}
|
||||
<div class="card-footer bg-light">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-shield-check me-1"></i>
|
||||
Confirmation: <strong>{{ order.confirmation_number }}</strong>
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<!-- Empty State -->
|
||||
<div class="empty-state p-5 text-center">
|
||||
<i class="bi bi-bag-x display-1 text-muted mb-4"></i>
|
||||
<h3 class="text-muted mb-3">No orders yet</h3>
|
||||
<p class="text-muted mb-4">
|
||||
You haven't placed any orders yet. Explore our marketplace to find amazing ThreeFold resources and services.
|
||||
</p>
|
||||
<a href="/marketplace" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-shop me-2"></i>Browse Marketplace
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Filters & Info Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="filter-card p-4 mb-4">
|
||||
<h5 class="mb-3">
|
||||
<i class="bi bi-funnel me-2"></i>Filter Orders
|
||||
</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="statusFilter" class="form-label">Order Status</label>
|
||||
<select class="form-select" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="confirmed">Confirmed</option>
|
||||
<option value="processing">Processing</option>
|
||||
<option value="deployed">Deployed</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="dateRange" class="form-label">Date Range</label>
|
||||
<select class="form-select" id="dateRange">
|
||||
<option value="">All Time</option>
|
||||
<option value="7">Last 7 days</option>
|
||||
<option value="30">Last 30 days</option>
|
||||
<option value="90">Last 3 months</option>
|
||||
<option value="365">Last year</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Statistics -->
|
||||
<div class="filter-card p-4 mb-4">
|
||||
<h6 class="mb-3">
|
||||
<i class="bi bi-graph-up me-2"></i>Order Summary
|
||||
</h6>
|
||||
|
||||
{% if orders %}
|
||||
<div class="row text-center">
|
||||
<div class="col-6 mb-3">
|
||||
<div class="h4 text-primary">{{ orders|length }}</div>
|
||||
<small class="text-muted">Total Orders</small>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<div class="h4 text-success">
|
||||
{% set completed_count = 0 %}
|
||||
{% for order in orders %}
|
||||
{% if order.status == "Completed" %}
|
||||
{% set completed_count = completed_count + 1 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ completed_count }}
|
||||
</div>
|
||||
<small class="text-muted">Completed</small>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="h4 text-info">
|
||||
{% set active_count = 0 %}
|
||||
{% for order in orders %}
|
||||
{% if order.status in ["Pending", "Confirmed", "Processing", "Deployed"] %}
|
||||
{% set active_count = active_count + 1 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ active_count }}
|
||||
</div>
|
||||
<small class="text-muted">Active</small>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="h4 text-warning">
|
||||
{% set pending_count = 0 %}
|
||||
{% for order in orders %}
|
||||
{% if order.status == "Pending" %}
|
||||
{% set pending_count = pending_count + 1 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ pending_count }}
|
||||
</div>
|
||||
<small class="text-muted">Pending</small>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted">
|
||||
<i class="bi bi-graph-up fs-1 mb-2"></i>
|
||||
<div>No order data available</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="filter-card p-4">
|
||||
<h6 class="mb-3">
|
||||
<i class="bi bi-lightning me-2"></i>Quick Actions
|
||||
</h6>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/marketplace" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-shop me-1"></i>Browse Products
|
||||
</a>
|
||||
<a href="/cart" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-cart3 me-1"></i>View Cart
|
||||
</a>
|
||||
<a href="/dashboard" class="btn btn-outline-info btn-sm">
|
||||
<i class="bi bi-speedometer2 me-1"></i>Dashboard
|
||||
</a>
|
||||
<button class="btn btn-outline-success btn-sm" data-action="export-orders">
|
||||
<i class="bi bi-download me-1"></i>Export Orders
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h6 class="mb-2">Need Help?</h6>
|
||||
<div class="d-grid gap-1">
|
||||
<a href="/docs" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-book me-1"></i>Docs
|
||||
</a>
|
||||
<a href="https://threefoldfaq.crisp.help/en/" class="btn btn-sm btn-outline-success" target="_blank">
|
||||
<i class="bi bi-chat-dots me-1"></i>Contact Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script type="application/json" id="orders-data">{}</script>
|
||||
<script src="/static/js/orders.js"></script>
|
||||
</body>
|
||||
</html>
|
293
src/views/marketplace/product_detail.html
Normal file
293
src/views/marketplace/product_detail.html
Normal file
@@ -0,0 +1,293 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}{{ product.product.name }} - Project Mycelium{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="/marketplace/{{ product.product.category_id }}">
|
||||
{{ product.product.category_id | title }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ product.product.name }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<!-- Product Details -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<!-- Product Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h1 class="h2 mb-2">{{ product.product.name }}</h1>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-primary me-2">{{ product.product.category_id | title }}</span>
|
||||
{% if product.product.metadata.featured %}
|
||||
<span class="badge bg-warning me-2">Featured</span>
|
||||
{% endif %}
|
||||
{% if product.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-muted mb-0">
|
||||
<i class="bi bi-building me-1"></i>
|
||||
{{ product.product.provider_name }}
|
||||
{% if product.product.metadata.location %}
|
||||
<span class="ms-3">
|
||||
<i class="bi bi-geo-alt me-1"></i>
|
||||
{{ product.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="price-display">
|
||||
<div class="h3 text-primary mb-0">{{ product.formatted_price }}</div>
|
||||
{% if product.price.display_currency != product.price.base_currency %}
|
||||
<small class="text-muted">
|
||||
({{ product.price.base_amount }} {{ product.price.base_currency }})
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Description -->
|
||||
<div class="mb-4">
|
||||
<h5>Description</h5>
|
||||
<p>{{ product.product.description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Product Specifications -->
|
||||
{% if product.product.attributes %}
|
||||
<div class="mb-4">
|
||||
<h5>Specifications</h5>
|
||||
<div class="row">
|
||||
{% for key, attr in product.product.attributes %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="spec-item">
|
||||
{% if key == "cpu_cores" %}
|
||||
<i class="bi bi-cpu me-2 text-primary"></i>
|
||||
{% elif key == "memory_gb" %}
|
||||
<i class="bi bi-memory me-2 text-primary"></i>
|
||||
{% elif key == "storage_gb" %}
|
||||
<i class="bi bi-hdd me-2 text-primary"></i>
|
||||
{% elif key == "bandwidth_mbps" %}
|
||||
<i class="bi bi-speedometer2 me-2 text-primary"></i>
|
||||
{% elif key == "uptime_percentage" %}
|
||||
<i class="bi bi-check-circle me-2 text-success"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-info-circle me-2 text-info"></i>
|
||||
{% endif %}
|
||||
<strong>{{ key | replace(from="_", to=" ") | title }}:</strong>
|
||||
<span class="ms-2">
|
||||
{% if key == "memory_gb" or key == "storage_gb" %}
|
||||
{{ attr.value }} GB
|
||||
{% elif key == "bandwidth_mbps" %}
|
||||
{{ attr.value }} Mbps
|
||||
{% elif key == "uptime_percentage" %}
|
||||
{{ attr.value }}%
|
||||
{% else %}
|
||||
{{ attr.value }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Product Tags -->
|
||||
{% if product.product.metadata.tags %}
|
||||
<div class="mb-4">
|
||||
<h6>Tags</h6>
|
||||
{% for tag in product.product.metadata.tags %}
|
||||
<span class="badge bg-light text-dark me-1">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Add to Cart Card -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Purchase Options</h5>
|
||||
|
||||
<!-- Quantity Selector -->
|
||||
<div class="mb-3">
|
||||
<label for="quantity" class="form-label">Quantity</label>
|
||||
<div class="input-group">
|
||||
<button class="btn btn-outline-secondary" type="button" id="decreaseQty">-</button>
|
||||
<input type="number" class="form-control text-center" id="quantity" value="1" min="1" max="10">
|
||||
<button class="btn btn-outline-secondary" type="button" id="increaseQty">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Price Display -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Total:</span>
|
||||
<span class="fw-bold text-primary" id="totalPrice">{{ product.formatted_price }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buy Now Button -->
|
||||
<button class="btn btn-success w-100 mb-2 buy-now-btn"
|
||||
data-product-id="{{ product.product.id }}"
|
||||
data-product-name="{{ product.product.name }}"
|
||||
data-unit-price="{{ product.product.base_price }}"
|
||||
data-category="{{ product.product.category_id }}"
|
||||
data-provider-id="{{ product.product.provider_id }}"
|
||||
data-provider-name="{{ product.product.provider_name }}"
|
||||
title="Buy instantly with your wallet balance">
|
||||
<i class="bi bi-lightning-charge me-2"></i>Buy Now
|
||||
</button>
|
||||
|
||||
<!-- Add to Cart Button -->
|
||||
<button class="btn btn-primary w-100 mb-2" id="addToCartBtn"
|
||||
data-product-id="{{ product.product.id }}"
|
||||
data-product-name="{{ product.product.name }}"
|
||||
data-unit-price="{{ product.price.display_amount }}"
|
||||
data-currency="{{ product.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-2"></i>Add to Cart
|
||||
</button>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-heart me-1"></i>Add to Wishlist
|
||||
</button>
|
||||
<button class="btn btn-outline-info btn-sm">
|
||||
<i class="bi bi-share me-1"></i>Share Product
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Provider Information -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Provider Information</h6>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="provider-avatar me-3">
|
||||
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center"
|
||||
style="width: 40px; height: 40px;">
|
||||
{{ product.product.provider_name | slice(end=1) | upper }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{{ product.product.provider_name }}</div>
|
||||
{% if product.product.metadata.rating %}
|
||||
<div class="text-muted small">
|
||||
<i class="bi bi-star-fill text-warning"></i>
|
||||
<i class="bi bi-star-fill text-warning"></i>
|
||||
<i class="bi bi-star-fill text-warning"></i>
|
||||
<i class="bi bi-star-fill text-warning"></i>
|
||||
<i class="bi bi-star text-muted"></i>
|
||||
<span class="ms-1">{{ product.product.metadata.rating }}/5 ({{ product.product.metadata.review_count }} reviews)</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<p class="small text-muted mb-0">
|
||||
Trusted provider in the ThreeFold ecosystem with verified infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Currency Selector -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Currency</h6>
|
||||
<select class="form-select" id="currencySelector">
|
||||
{% for currency in currencies %}
|
||||
<option value="{{ currency.code }}"
|
||||
{% if currency.code == user_currency %}selected{% endif %}>
|
||||
{{ currency.symbol }} {{ currency.name }} ({{ currency.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommendations Section -->
|
||||
{% if recommendations and recommendations | length > 0 %}
|
||||
<div class="mt-5">
|
||||
<h4 class="mb-4">Recommended Products</h4>
|
||||
<div class="row">
|
||||
{% for rec in recommendations %}
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">
|
||||
<a href="/products/{{ rec.product.id }}" class="text-decoration-none">
|
||||
{{ rec.product.name }}
|
||||
</a>
|
||||
</h6>
|
||||
<p class="card-text small text-muted">{{ rec.product.description | truncate(length=80) }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-primary fw-bold">{{ rec.formatted_price }}</span>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success btn-sm buy-now-btn"
|
||||
data-product-id="{{ rec.product.id }}"
|
||||
data-product-name="{{ rec.product.name }}"
|
||||
data-unit-price="{{ rec.price.display_amount }}"
|
||||
data-currency="{{ rec.price.display_currency }}"
|
||||
data-category="{{ rec.product.category_id }}">
|
||||
<i class="bi bi-lightning-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ rec.product.id }}"
|
||||
data-product-name="{{ rec.product.name }}"
|
||||
data-unit-price="{{ rec.price.display_amount }}"
|
||||
data-currency="{{ rec.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.spec-item {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.spec-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.price-display {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.provider-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/static/js/product-detail.js"></script>
|
||||
{% endblock %}
|
74
src/views/marketplace/product_detail_step1.html
Normal file
74
src/views/marketplace/product_detail_step1.html
Normal file
@@ -0,0 +1,74 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}{{ product.product.name }} - Project Mycelium{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="/marketplace/{{ product.product.category_id }}">
|
||||
{{ product.product.category_id | title }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ product.product.name }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<!-- Product Details -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<!-- Product Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h1 class="h2 mb-2">{{ product.product.name }}</h1>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-primary me-2">{{ product.product.category_id | title }}</span>
|
||||
{% if product.product.metadata.featured %}
|
||||
<span class="badge bg-warning me-2">Featured</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-success">{{ product.product.availability }}</span>
|
||||
</div>
|
||||
<p class="text-muted mb-0">
|
||||
<i class="bi bi-building me-1"></i>
|
||||
{{ product.product.provider_name }}
|
||||
{% if product.product.metadata.location %}
|
||||
<span class="ms-3">
|
||||
<i class="bi bi-geo-alt me-1"></i>
|
||||
{{ product.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="price-display">
|
||||
<div class="h3 text-primary mb-0">{{ product.formatted_price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Description -->
|
||||
<div class="mb-4">
|
||||
<h5>Description</h5>
|
||||
<p>{{ product.product.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Purchase Options</h5>
|
||||
<button class="btn btn-primary w-100">Add to Cart</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
120
src/views/marketplace/product_detail_step2.html
Normal file
120
src/views/marketplace/product_detail_step2.html
Normal file
@@ -0,0 +1,120 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}{{ product.product.name }} - Project Mycelium{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/marketplace">Marketplace</a></li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="/marketplace/{{ product.product.category_id }}">
|
||||
{{ product.product.category_id | title }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ product.product.name }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<!-- Product Details -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<!-- Product Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h1 class="h2 mb-2">{{ product.product.name }}</h1>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-primary me-2">{{ product.product.category_id | title }}</span>
|
||||
{% if product.product.metadata.featured %}
|
||||
<span class="badge bg-warning me-2">Featured</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-success">{{ product.product.availability }}</span>
|
||||
</div>
|
||||
<p class="text-muted mb-0">
|
||||
<i class="bi bi-building me-1"></i>
|
||||
{{ product.product.provider_name }}
|
||||
{% if product.product.metadata.location %}
|
||||
<span class="ms-3">
|
||||
<i class="bi bi-geo-alt me-1"></i>
|
||||
{{ product.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="price-display">
|
||||
<div class="h3 text-primary mb-0">{{ product.formatted_price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Description -->
|
||||
<div class="mb-4">
|
||||
<h5>Description</h5>
|
||||
<p>{{ product.product.description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Product Specifications -->
|
||||
{% if product.product.attributes %}
|
||||
<div class="mb-4">
|
||||
<h5>Specifications</h5>
|
||||
<div class="row">
|
||||
{% for key, attr in product.product.attributes %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="spec-item">
|
||||
<strong>{{ key | replace(from="_", to=" ") | title }}:</strong>
|
||||
<span class="ms-2">{{ attr.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Add to Cart Card -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Purchase Options</h5>
|
||||
|
||||
<!-- Quantity Selector -->
|
||||
<div class="mb-3">
|
||||
<label for="quantity" class="form-label">Quantity</label>
|
||||
<div class="input-group">
|
||||
<button class="btn btn-outline-secondary" type="button" id="decreaseQty">-</button>
|
||||
<input type="number" class="form-control text-center" id="quantity" value="1" min="1" max="10">
|
||||
<button class="btn btn-outline-secondary" type="button" id="increaseQty">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Price Display -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Total:</span>
|
||||
<span class="fw-bold text-primary" id="totalPrice">{{ product.formatted_price }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add to Cart Button -->
|
||||
<button class="btn btn-primary w-100 mb-2" id="addToCartBtn"
|
||||
data-product-id="{{ product.product.id }}"
|
||||
data-product-name="{{ product.product.name }}"
|
||||
data-unit-price="{{ product.price.display_amount }}"
|
||||
data-currency="{{ product.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-2"></i>Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/product-detail-step2.js"></script>
|
||||
{% endblock %}
|
337
src/views/marketplace/products.html
Normal file
337
src/views/marketplace/products.html
Normal file
@@ -0,0 +1,337 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Products{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1>All Products</h1>
|
||||
<p class="lead mb-0">
|
||||
{% if search_query.q %}
|
||||
Search results for "{{ search_query.q }}"
|
||||
{% elif search_query.category %}
|
||||
{{ search_query.category | title }} Products
|
||||
{% else %}
|
||||
Browse all available products and services
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
{% if response.total_count > 0 %}
|
||||
Showing {{ response.products | length }} of {{ response.total_count }} products
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="row g-3">
|
||||
<!-- Search Query -->
|
||||
<div class="col-md-4">
|
||||
<label for="searchQuery" class="form-label">Search Products</label>
|
||||
<input type="text" class="form-control" id="searchQuery" name="q"
|
||||
value="{{ search_query.q or '' }}" placeholder="Search products...">
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="col-md-2">
|
||||
<label for="categoryFilter" class="form-label">Category</label>
|
||||
<select id="categoryFilter" name="category" class="form-select">
|
||||
<option value="">All Categories</option>
|
||||
{% for category in response.categories %}
|
||||
<option value="{{ category.id }}"
|
||||
{% if search_query.category == category.id %}selected{% endif %}>
|
||||
{{ category.display_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Price Range -->
|
||||
<div class="col-md-2">
|
||||
<label for="minPrice" class="form-label">Min Price</label>
|
||||
<input type="number" class="form-control" id="minPrice" name="min_price"
|
||||
value="{{ search_query.min_price or '' }}" placeholder="0">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="maxPrice" class="form-label">Max Price</label>
|
||||
<input type="number" class="form-control" id="maxPrice" name="max_price"
|
||||
value="{{ search_query.max_price or '' }}" placeholder="1000">
|
||||
</div>
|
||||
|
||||
<!-- Provider Filter -->
|
||||
<div class="col-md-2">
|
||||
<label for="providerFilter" class="form-label">Provider</label>
|
||||
<input type="text" class="form-control" id="providerFilter" name="provider"
|
||||
value="{{ search_query.provider or '' }}" placeholder="Provider name">
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<div class="col-md-12 d-flex justify-content-between align-items-end">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-search me-1"></i>Search
|
||||
</button>
|
||||
<a href="/products" class="btn btn-outline-secondary">Clear Filters</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
{% if response.products and response.products | length > 0 %}
|
||||
<!-- Sort Options -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="btn-group" role="group" aria-label="View options">
|
||||
<input type="radio" class="btn-check" name="view-mode" id="grid-view" checked>
|
||||
<label class="btn btn-outline-secondary" for="grid-view">
|
||||
<i class="bi bi-grid-3x3-gap"></i> Grid
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="view-mode" id="list-view">
|
||||
<label class="btn btn-outline-secondary" for="list-view">
|
||||
<i class="bi bi-list"></i> List
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
Sort by: Price (Low to High)
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">Price (Low to High)</a></li>
|
||||
<li><a class="dropdown-item" href="#">Price (High to Low)</a></li>
|
||||
<li><a class="dropdown-item" href="#">Name (A-Z)</a></li>
|
||||
<li><a class="dropdown-item" href="#">Provider</a></li>
|
||||
<li><a class="dropdown-item" href="#">Featured First</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Grid -->
|
||||
<div id="products-grid" class="row">
|
||||
{% for product_data in response.products %}
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card h-100 product-card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title">
|
||||
<a href="/products/{{ product_data.product.id }}" class="text-decoration-none text-dark">
|
||||
{{ product_data.product.name }}
|
||||
</a>
|
||||
</h5>
|
||||
{% if product_data.product.metadata.featured %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% elif product_data.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product_data.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<span class="badge bg-primary">{{ product_data.product.category_id | title }}</span>
|
||||
{% if product_data.product.metadata.location %}
|
||||
<span class="badge bg-light text-dark ms-1">{{ product_data.product.metadata.location }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted small">{{ product_data.product.description | truncate(length=100) }}</p>
|
||||
|
||||
<!-- Key Specifications -->
|
||||
{% if product_data.product.attributes %}
|
||||
<div class="mb-3">
|
||||
<div class="row text-center">
|
||||
{% if product_data.product.attributes.cpu_cores %}
|
||||
<div class="col-4">
|
||||
<div class="spec-badge">
|
||||
<i class="bi bi-cpu text-primary"></i>
|
||||
<div class="small">{{ product_data.product.attributes.cpu_cores.value }} cores</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.memory_gb %}
|
||||
<div class="col-4">
|
||||
<div class="spec-badge">
|
||||
<i class="bi bi-memory text-primary"></i>
|
||||
<div class="small">{{ product_data.product.attributes.memory_gb.value }} GB</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.storage_gb %}
|
||||
<div class="col-4">
|
||||
<div class="spec-badge">
|
||||
<i class="bi bi-hdd text-primary"></i>
|
||||
<div class="small">{{ product_data.product.attributes.storage_gb.value }} GB</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-muted small mb-3">
|
||||
<i class="bi bi-building me-1"></i>{{ product_data.product.provider_name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="price-info">
|
||||
<div class="fw-bold text-primary">{{ product_data.price.formatted_display }}</div>
|
||||
{% if product_data.price.display_currency != "USD" %}
|
||||
<div class="small text-muted">≈ ${{ product_data.product.base_price }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success btn-sm buy-now-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-category="{{ product_data.product.category_id }}"
|
||||
data-unit-price="{{ product_data.product.base_price }}"
|
||||
data-provider-id="{{ product_data.product.provider_id }}"
|
||||
data-provider-name="{{ product_data.product.provider_name }}"
|
||||
title="Buy instantly with your wallet balance">
|
||||
<i class="bi bi-lightning-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus"></i>
|
||||
</button>
|
||||
<a href="/products/{{ product_data.product.id }}" class="btn btn-outline-primary btn-sm">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Products List View (Hidden by default) -->
|
||||
<div id="products-list" class="d-none">
|
||||
{% for product_data in response.products %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="mb-1">
|
||||
<a href="/products/{{ product_data.product.id }}" class="text-decoration-none text-dark">
|
||||
{{ product_data.product.name }}
|
||||
</a>
|
||||
</h5>
|
||||
{% if product_data.product.metadata.featured %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% elif product_data.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product_data.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-muted mb-2">{{ product_data.product.description | truncate(length=150) }}</p>
|
||||
<div class="text-muted small">
|
||||
<span class="badge bg-primary me-2">{{ product_data.product.category_id | title }}</span>
|
||||
<i class="bi bi-building me-1"></i>{{ product_data.product.provider_name }}
|
||||
{% if product_data.product.metadata.location %}
|
||||
<span class="ms-2">
|
||||
<i class="bi bi-geo-alt me-1"></i>{{ product_data.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<div class="fw-bold text-primary fs-5 mb-2">{{ product_data.price.formatted_display }}</div>
|
||||
{% if product_data.price.display_currency != "USD" %}
|
||||
<div class="small text-muted">≈ ${{ product_data.product.base_price }}</div>
|
||||
{% endif %}
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
<a href="/products/{{ product_data.product.id }}" class="btn btn-outline-primary btn-sm">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if response.total_pages > 1 %}
|
||||
<nav aria-label="Product pages" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if response.page > 0 %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ response.page - 1 }}{% if search_query.q %}&q={{ search_query.q }}{% endif %}{% if search_query.category %}&category={{ search_query.category }}{% endif %}">Previous</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for page_num in range(end=response.total_pages) %}
|
||||
<li class="page-item {% if page_num == response.page %}active{% endif %}">
|
||||
<a class="page-link" href="?page={{ page_num }}{% if search_query.q %}&q={{ search_query.q }}{% endif %}{% if search_query.category %}&category={{ search_query.category }}{% endif %}">{{ page_num + 1 }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if response.page < response.total_pages - 1 %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ response.page + 1 }}{% if search_query.q %}&q={{ search_query.q }}{% endif %}{% if search_query.category %}&category={{ search_query.category }}{% endif %}">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- No Results -->
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-search display-1 text-muted"></i>
|
||||
<h4 class="mt-3">No Products Found</h4>
|
||||
{% if search_query.q or search_query.category %}
|
||||
<p class="text-muted">Try adjusting your search criteria or browse all products.</p>
|
||||
<a href="/products" class="btn btn-primary">Browse All Products</a>
|
||||
{% else %}
|
||||
<p class="text-muted">No products are currently available.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.product-card {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.spec-badge {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/static/js/products-page.js"></script>
|
||||
{% endblock %}
|
358
src/views/marketplace/services.html
Normal file
358
src/views/marketplace/services.html
Normal file
@@ -0,0 +1,358 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Professional Services{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>Professional Services</h1>
|
||||
<p class="lead">Expert services to help you succeed with ThreeFold technology and infrastructure.</p>
|
||||
|
||||
<!-- Services Introduction -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<div class="d-flex">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-people-fill fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="alert-heading">Professional Support & Services</h5>
|
||||
<p>Our certified experts and trusted community providers offer comprehensive services to help you deploy, manage, and optimize your ThreeFold infrastructure and applications.</p>
|
||||
<hr>
|
||||
<p class="mb-0">From initial consultation to ongoing support, we ensure your success with decentralized technology.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter and Search Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Filter Services</h5>
|
||||
<form class="row g-3" method="GET">
|
||||
<div class="col-md-3">
|
||||
<label for="serviceTypeFilter" class="form-label">Service Type</label>
|
||||
<select name="service_type" id="serviceTypeFilter" class="form-select">
|
||||
<option value="">All Services</option>
|
||||
<option value="Consulting">Consulting</option>
|
||||
<option value="Deployment">Deployment</option>
|
||||
<option value="Support">Support</option>
|
||||
<option value="Training">Training</option>
|
||||
<option value="Development">Development</option>
|
||||
<option value="Maintenance">Maintenance</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="expertiseFilter" class="form-label">Expertise Level</label>
|
||||
<select name="expertise_level" id="expertiseFilter" class="form-select">
|
||||
<option value="">Any</option>
|
||||
<option value="Basic">Basic</option>
|
||||
<option value="Intermediate">Intermediate</option>
|
||||
<option value="Advanced">Advanced</option>
|
||||
<option value="Expert">Expert</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="minPriceFilter" class="form-label">Min Price ($)</label>
|
||||
<input type="number" name="min_price" id="minPriceFilter" class="form-control"
|
||||
value="" placeholder="0" min="0" step="5">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="maxPriceFilter" class="form-label">Max Price ($)</label>
|
||||
<input type="number" name="max_price" id="maxPriceFilter" class="form-control"
|
||||
value="" placeholder="200" min="0" step="5">
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary me-2">Apply Filters</button>
|
||||
<a href="/marketplace/services" class="btn btn-outline-secondary">Clear</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Services Grid -->
|
||||
<div class="row" id="services-grid">
|
||||
{% if service_products and service_products | length > 0 %}
|
||||
{% for product_data in service_products %}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div class="service-icon me-3">
|
||||
<i class="bi bi-gear-fill fs-2 text-primary"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h5 class="card-title mb-1">
|
||||
<a href="/products/{{ product_data.product.id }}" class="text-decoration-none text-dark">
|
||||
{{ product_data.product.name }}
|
||||
</a>
|
||||
</h5>
|
||||
{% if product_data.product.metadata.featured %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% elif product_data.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product_data.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-muted small mb-2">
|
||||
<i class="bi bi-building me-1"></i>{{ product_data.product.provider_name }}
|
||||
{% if product_data.product.metadata.location %}
|
||||
<span class="ms-2">
|
||||
<i class="bi bi-geo-alt me-1"></i>{{ product_data.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="card-text">{{ product_data.product.description | truncate(length=150) }}</p>
|
||||
|
||||
<!-- Service Details -->
|
||||
{% if product_data.product.attributes %}
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">Service Details:</h6>
|
||||
<div class="row">
|
||||
{% if product_data.product.attributes.duration_hours %}
|
||||
<div class="col-md-6">
|
||||
<div class="service-detail">
|
||||
<i class="bi bi-clock me-2"></i>
|
||||
<span>Duration: {{ product_data.product.attributes.duration_hours.value }} hours</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.expertise_level %}
|
||||
<div class="col-md-6">
|
||||
<div class="service-detail">
|
||||
<i class="bi bi-star me-2"></i>
|
||||
<span>Level: {{ product_data.product.attributes.expertise_level.value | title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.response_time_hours %}
|
||||
<div class="col-md-6">
|
||||
<div class="service-detail">
|
||||
<i class="bi bi-reply me-2"></i>
|
||||
<span>Response: {{ product_data.product.attributes.response_time_hours.value }}h</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.support_type %}
|
||||
<div class="col-md-6">
|
||||
<div class="service-detail">
|
||||
<i class="bi bi-headset me-2"></i>
|
||||
<span>Type: {{ product_data.product.attributes.support_type.value | title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Service Features/Tags -->
|
||||
{% if product_data.product.metadata.tags and product_data.product.metadata.tags | length > 0 %}
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">Includes:</h6>
|
||||
<div class="d-flex flex-wrap">
|
||||
{% for tag in product_data.product.metadata.tags %}
|
||||
<span class="badge bg-light text-dark me-1 mb-1">{{ tag | title }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Provider Rating -->
|
||||
{% if product_data.product.metadata.rating %}
|
||||
<div class="mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2">Rating:</span>
|
||||
{% if product_data.product.metadata.rating >= 1 %}<i class="bi bi-star-fill text-warning"></i>{% else %}<i class="bi bi-star text-muted"></i>{% endif %}
|
||||
{% if product_data.product.metadata.rating >= 2 %}<i class="bi bi-star-fill text-warning"></i>{% else %}<i class="bi bi-star text-muted"></i>{% endif %}
|
||||
{% if product_data.product.metadata.rating >= 3 %}<i class="bi bi-star-fill text-warning"></i>{% else %}<i class="bi bi-star text-muted"></i>{% endif %}
|
||||
{% if product_data.product.metadata.rating >= 4 %}<i class="bi bi-star-fill text-warning"></i>{% else %}<i class="bi bi-star text-muted"></i>{% endif %}
|
||||
{% if product_data.product.metadata.rating >= 5 %}<i class="bi bi-star-fill text-warning"></i>{% else %}<i class="bi bi-star text-muted"></i>{% endif %}
|
||||
<span class="ms-2 text-muted small">({{ product_data.product.metadata.review_count }} reviews)</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="price-info">
|
||||
<div class="fw-bold text-primary fs-5">{{ product_data.formatted_price }}</div>
|
||||
<small class="text-muted">per engagement</small>
|
||||
</div>
|
||||
<div class="btn-group-vertical w-100">
|
||||
<div class="btn-group mb-2">
|
||||
<button class="btn btn-success btn-sm buy-now-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}"
|
||||
data-category="services">
|
||||
<i class="bi bi-lightning-charge me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
<a href="/products/{{ product_data.product.id }}" class="btn btn-outline-primary btn-sm">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-people display-1 text-muted"></i>
|
||||
<h4 class="mt-3">No Services Available</h4>
|
||||
<p class="text-muted">Check back later for new professional services.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.total_pages > 1 %}
|
||||
<nav aria-label="Service pages" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Previous Page -->
|
||||
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
||||
{% if pagination.has_previous %}
|
||||
<a class="page-link" href="?page={{ pagination.previous_page }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 1 -->
|
||||
<li class="page-item {% if pagination.current_page == 0 %}active{% endif %}">
|
||||
{% if pagination.current_page == 0 %}
|
||||
<span class="page-link">1</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=0">1</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 2 (if exists) -->
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<li class="page-item {% if pagination.current_page == 1 %}active{% endif %}">
|
||||
{% if pagination.current_page == 1 %}
|
||||
<span class="page-link">2</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=1">2</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 3 (if exists) -->
|
||||
{% if pagination.total_pages > 2 %}
|
||||
<li class="page-item {% if pagination.current_page == 2 %}active{% endif %}">
|
||||
{% if pagination.current_page == 2 %}
|
||||
<span class="page-link">3</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=2">3</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 4 (if exists) -->
|
||||
{% if pagination.total_pages > 3 %}
|
||||
<li class="page-item {% if pagination.current_page == 3 %}active{% endif %}">
|
||||
{% if pagination.current_page == 3 %}
|
||||
<span class="page-link">4</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=3">4</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 5 (if exists) -->
|
||||
{% if pagination.total_pages > 4 %}
|
||||
<li class="page-item {% if pagination.current_page == 4 %}active{% endif %}">
|
||||
{% if pagination.current_page == 4 %}
|
||||
<span class="page-link">5</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=4">5</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Next Page -->
|
||||
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
||||
{% if pagination.has_next %}
|
||||
<a class="page-link" href="?page={{ pagination.next_page }}">Next</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Next</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="text-center text-muted mt-2">
|
||||
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
|
||||
({{ pagination.total_count }} total services)
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Service Categories -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-4">Service Categories</h3>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-lightbulb fs-1 text-primary mb-3"></i>
|
||||
<h5>Consulting & Strategy</h5>
|
||||
<p class="small text-muted">Architecture planning, technology assessment, and strategic guidance for your decentralized infrastructure.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-tools fs-1 text-primary mb-3"></i>
|
||||
<h5>Deployment & Setup</h5>
|
||||
<p class="small text-muted">Professional deployment, configuration, and optimization of your ThreeFold infrastructure and applications.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-headset fs-1 text-primary mb-3"></i>
|
||||
<h5>Support & Maintenance</h5>
|
||||
<p class="small text-muted">Ongoing support, monitoring, updates, and maintenance to keep your systems running smoothly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.service-detail {
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- JSON hydration for services (CSP-safe) -->
|
||||
<script type="application/json" id="services-data">{}</script>
|
||||
<!-- External JS (CSP-compliant) -->
|
||||
<script src="/static/js/services.js"></script>
|
||||
{% endblock %}
|
447
src/views/marketplace/slice_rental_form.html
Normal file
447
src/views/marketplace/slice_rental_form.html
Normal file
@@ -0,0 +1,447 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Rent Slice{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<a href="/marketplace/compute" class="btn btn-outline-secondary me-3">
|
||||
<i class="bi bi-arrow-left"></i> Back to Compute Resources
|
||||
</a>
|
||||
<h1 class="mb-0">Rent Compute Slice</h1>
|
||||
</div>
|
||||
|
||||
{% if slice %}
|
||||
<div class="row">
|
||||
<!-- Slice Information -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Slice Details</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<strong>Node:</strong> {{ slice.node_id }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong>Farmer:</strong> {{ slice.farmer_email }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong>Specifications:</strong>
|
||||
<ul class="list-unstyled mt-2">
|
||||
<li><i class="bi bi-cpu me-2"></i>{{ slice.cpu_cores }} CPU Cores</li>
|
||||
<li><i class="bi bi-memory me-2"></i>{{ slice.memory_gb }} GB RAM</li>
|
||||
<li><i class="bi bi-hdd me-2"></i>{{ slice.storage_gb }} GB Storage</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong>Price:</strong> ${{ slice.price_per_hour }}/hour
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong>Available:</strong>
|
||||
<span class="badge bg-success">{{ slice.available_quantity }} slices</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rental Configuration Form -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Deployment Configuration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="sliceRentalForm" method="POST" action="/marketplace/slice/rent">
|
||||
<!-- Hidden fields -->
|
||||
<input type="hidden" name="farmer_email" value="{{ farmer_email }}">
|
||||
<input type="hidden" name="node_id" value="{{ node_id }}">
|
||||
<input type="hidden" name="combination_id" value="{{ combination_id }}">
|
||||
|
||||
<!-- Basic Configuration -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="quantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity"
|
||||
min="1" max="{{ slice.available_quantity }}" value="1" required>
|
||||
<div class="form-text">Number of slices to rent</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="rental_duration_hours" class="form-label">Duration (Hours)</label>
|
||||
<select class="form-select" id="rental_duration_hours" name="rental_duration_hours" required>
|
||||
<option value="24">1 Day (24 hours)</option>
|
||||
<option value="168">1 Week (168 hours)</option>
|
||||
<option value="720" selected>1 Month (720 hours)</option>
|
||||
<option value="2160">3 Months (2160 hours)</option>
|
||||
<option value="4320">6 Months (4320 hours)</option>
|
||||
<option value="8760">1 Year (8760 hours)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deployment Type Selection -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Deployment Type</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card deployment-option" data-deployment="vm">
|
||||
<div class="card-body text-center">
|
||||
<input type="radio" class="btn-check" name="deployment_type" id="vm_deployment" value="vm" checked>
|
||||
<label class="btn btn-outline-primary w-100" for="vm_deployment">
|
||||
<i class="bi bi-pc-display-horizontal display-6 d-block mb-2"></i>
|
||||
<h5>Virtual Machine</h5>
|
||||
<p class="text-muted small mb-0">Deploy a single VM with your chosen OS</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card deployment-option" data-deployment="kubernetes">
|
||||
<div class="card-body text-center">
|
||||
<input type="radio" class="btn-check" name="deployment_type" id="k8s_deployment" value="kubernetes">
|
||||
<label class="btn btn-outline-primary w-100" for="k8s_deployment">
|
||||
<i class="bi bi-diagram-3 display-6 d-block mb-2"></i>
|
||||
<h5>Kubernetes Cluster</h5>
|
||||
<p class="text-muted small mb-0">Deploy a K8s cluster with configurable nodes</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VM Configuration -->
|
||||
<div id="vm_config" class="deployment-config">
|
||||
<h6 class="mb-3">VM Configuration</h6>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="vm_name" class="form-label">VM Name</label>
|
||||
<input type="text" class="form-control" id="vm_name" name="vm_name"
|
||||
placeholder="my-vm" pattern="[a-zA-Z0-9-]+" required>
|
||||
<div class="form-text">Alphanumeric characters and hyphens only</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="vm_os" class="form-label">Operating System</label>
|
||||
<select class="form-select" id="vm_os" name="vm_os">
|
||||
<option value="ubuntu" selected>Ubuntu 22.04 LTS</option>
|
||||
<option value="debian">Debian 12</option>
|
||||
<option value="centos">CentOS Stream 9</option>
|
||||
<option value="alpine">Alpine Linux</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="vm_ssh_key" class="form-label">SSH Public Key (Optional)</label>
|
||||
<textarea class="form-control" id="vm_ssh_key" name="vm_ssh_key" rows="3"
|
||||
placeholder="ssh-rsa AAAAB3NzaC1yc2E..."></textarea>
|
||||
<div class="form-text">Paste your SSH public key for secure access</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kubernetes Configuration -->
|
||||
<div id="k8s_config" class="deployment-config" style="display: none;">
|
||||
<h6 class="mb-3">Kubernetes Configuration</h6>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="cluster_name" class="form-label">Cluster Name</label>
|
||||
<input type="text" class="form-control" id="cluster_name" name="cluster_name"
|
||||
placeholder="my-k8s-cluster" pattern="[a-zA-Z0-9-]+">
|
||||
<div class="form-text">Alphanumeric characters and hyphens only</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="k8s_version" class="form-label">Kubernetes Version</label>
|
||||
<select class="form-select" id="k8s_version" name="k8s_version">
|
||||
<option value="1.29" selected>1.29 (Recommended)</option>
|
||||
<option value="1.28">1.28</option>
|
||||
<option value="1.30">1.30 (Latest)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label for="k8s_masters" class="form-label">Master Nodes</label>
|
||||
<select class="form-select" id="k8s_masters" name="k8s_masters">
|
||||
<option value="1" selected>1 Master</option>
|
||||
<option value="3">3 Masters (HA)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="k8s_workers" class="form-label">Worker Nodes</label>
|
||||
<select class="form-select" id="k8s_workers" name="k8s_workers">
|
||||
<option value="1" selected>1 Worker</option>
|
||||
<option value="2">2 Workers</option>
|
||||
<option value="3">3 Workers</option>
|
||||
<option value="5">5 Workers</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="k8s_network_plugin" class="form-label">Network Plugin</label>
|
||||
<select class="form-select" id="k8s_network_plugin" name="k8s_network_plugin">
|
||||
<option value="flannel" selected>Flannel</option>
|
||||
<option value="calico">Calico</option>
|
||||
<option value="weave">Weave Net</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Options -->
|
||||
<div class="mb-4">
|
||||
<h6 class="mb-3">Additional Options</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="auto_scaling" name="auto_scaling">
|
||||
<label class="form-check-label" for="auto_scaling">
|
||||
Auto Scaling
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="backup_enabled" name="backup_enabled">
|
||||
<label class="form-check-label" for="backup_enabled">
|
||||
Enable Backups
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="monitoring_enabled" name="monitoring_enabled" checked>
|
||||
<label class="form-check-label" for="monitoring_enabled">
|
||||
Enable Monitoring
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cost Calculation -->
|
||||
<div class="card bg-light mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="mb-3">Cost Calculation</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Base Price per Hour:</span>
|
||||
<span id="base_price">${{ slice.price_per_hour }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Quantity:</span>
|
||||
<span id="quantity_display">1</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Duration:</span>
|
||||
<span id="duration_display">720 hours</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Subtotal:</span>
|
||||
<span id="subtotal">${{ slice.price_per_hour * 720 }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>K8s Multiplier:</span>
|
||||
<span id="k8s_multiplier">1x</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between fw-bold">
|
||||
<span>Total Cost:</span>
|
||||
<span id="total_cost">${{ slice.price_per_hour * 720 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
|
||||
<i class="bi bi-rocket me-2"></i>Deploy Now
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-exclamation-triangle display-1 text-warning"></i>
|
||||
<h4 class="mt-3">Slice Not Found</h4>
|
||||
<p class="text-muted">The requested slice combination could not be found.</p>
|
||||
<a href="/marketplace/compute" class="btn btn-primary">Browse Available Slices</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pass data to JavaScript -->
|
||||
<script type="application/json" id="slice-data">
|
||||
{
|
||||
"basePrice": {% if slice %}{{ slice.price_per_hour }}{% else %}0{% endif %},
|
||||
"availableQuantity": {% if slice %}{{ slice.available_quantity }}{% else %}1{% endif %}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const vmRadio = document.getElementById('vm_deployment');
|
||||
const k8sRadio = document.getElementById('k8s_deployment');
|
||||
const vmConfig = document.getElementById('vm_config');
|
||||
const k8sConfig = document.getElementById('k8s_config');
|
||||
const form = document.getElementById('sliceRentalForm');
|
||||
|
||||
// Get data from JSON script tag
|
||||
const sliceData = JSON.parse(document.getElementById('slice-data').textContent);
|
||||
const basePrice = sliceData.basePrice;
|
||||
|
||||
// Toggle deployment configuration
|
||||
function toggleDeploymentConfig() {
|
||||
if (vmRadio.checked) {
|
||||
vmConfig.style.display = 'block';
|
||||
k8sConfig.style.display = 'none';
|
||||
// Make VM fields required
|
||||
document.getElementById('vm_name').required = true;
|
||||
document.getElementById('cluster_name').required = false;
|
||||
} else {
|
||||
vmConfig.style.display = 'none';
|
||||
k8sConfig.style.display = 'block';
|
||||
// Make K8s fields required
|
||||
document.getElementById('vm_name').required = false;
|
||||
document.getElementById('cluster_name').required = true;
|
||||
}
|
||||
updateCostCalculation();
|
||||
}
|
||||
|
||||
// Update cost calculation
|
||||
function updateCostCalculation() {
|
||||
const quantity = parseInt(document.getElementById('quantity').value) || 1;
|
||||
const duration = parseInt(document.getElementById('rental_duration_hours').value) || 720;
|
||||
const masters = parseInt(document.getElementById('k8s_masters').value) || 1;
|
||||
const workers = parseInt(document.getElementById('k8s_workers').value) || 1;
|
||||
|
||||
let multiplier = 1;
|
||||
if (k8sRadio.checked) {
|
||||
multiplier = masters + workers;
|
||||
}
|
||||
|
||||
const subtotal = basePrice * quantity * duration;
|
||||
const total = subtotal * multiplier;
|
||||
|
||||
document.getElementById('quantity_display').textContent = quantity;
|
||||
document.getElementById('duration_display').textContent = duration + ' hours';
|
||||
document.getElementById('subtotal').textContent = '$' + subtotal.toFixed(2);
|
||||
document.getElementById('k8s_multiplier').textContent = multiplier + 'x';
|
||||
document.getElementById('total_cost').textContent = '$' + total.toFixed(2);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
vmRadio.addEventListener('change', toggleDeploymentConfig);
|
||||
k8sRadio.addEventListener('change', toggleDeploymentConfig);
|
||||
document.getElementById('quantity').addEventListener('input', updateCostCalculation);
|
||||
document.getElementById('rental_duration_hours').addEventListener('change', updateCostCalculation);
|
||||
document.getElementById('k8s_masters').addEventListener('change', updateCostCalculation);
|
||||
document.getElementById('k8s_workers').addEventListener('change', updateCostCalculation);
|
||||
|
||||
// Form submission
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
submitBtn.innerHTML = '<i class="bi bi-hourglass-split me-2"></i>Deploying...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
window.apiJson('/marketplace/slice/rent', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-success alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
<i class="bi bi-check-circle me-2"></i>
|
||||
${data.message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
form.parentNode.insertBefore(alertDiv, form);
|
||||
|
||||
// Redirect after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (data.redirect_url) {
|
||||
window.location.href = data.redirect_url;
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
// Show error message
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
${data.message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
form.parentNode.insertBefore(alertDiv, form);
|
||||
|
||||
submitBtn.innerHTML = originalText;
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
An error occurred while processing your request.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
form.parentNode.insertBefore(alertDiv, form);
|
||||
|
||||
submitBtn.innerHTML = originalText;
|
||||
submitBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize
|
||||
toggleDeploymentConfig();
|
||||
updateCostCalculation();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.deployment-option {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.deployment-option:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.deployment-config {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.375rem;
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.btn-check:checked + .btn {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spec-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
249
src/views/marketplace/statistics.html
Normal file
249
src/views/marketplace/statistics.html
Normal file
@@ -0,0 +1,249 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - Statistics{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>Marketplace Statistics</h1>
|
||||
<p class="lead">Analytics and utilization statistics for the Project Mycelium</p>
|
||||
|
||||
<!-- Overview Stats -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Resource Distribution</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="resourceDistributionChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Monthly Growth</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="monthlyGrowthChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Compute Resources Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="dashboard-section">
|
||||
<h3>Compute Resources Statistics</h3>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>CPU Utilization by Region</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="cpuUtilizationChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Memory Allocation</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="memoryAllocationChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Storage Distribution</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="storageDistributionChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Resource Pricing Trends</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="resourcePricingChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3Nodes Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="dashboard-section">
|
||||
<h3>3Nodes Statistics</h3>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Geographic Distribution</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="nodeGeographicChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Node Types Distribution</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="nodeTypesChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Uptime Performance</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="nodeUptimeChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>3Node Certification Rate</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="nodeCertificationChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gateways Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="dashboard-section">
|
||||
<h3>Gateways Statistics</h3>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Gateway Traffic</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="gatewayTrafficChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Gateway Availability</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="gatewayAvailabilityChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="dashboard-section">
|
||||
<h3>Applications Statistics</h3>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Application Categories</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="appCategoriesChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Deployment Trends</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="appDeploymentChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Services Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="dashboard-section">
|
||||
<h3>Services Statistics</h3>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Service Categories</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="serviceCategoriesChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card mb-4 h-100">
|
||||
<div class="card-header">
|
||||
<h5>Average Hourly Rates</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-center align-items-center">
|
||||
<canvas id="serviceRatesChart" width="400" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
|
||||
<style>
|
||||
/* Ensure charts have consistent sizes */
|
||||
.card.h-100 {
|
||||
height: 400px !important;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
height: 340px;
|
||||
}
|
||||
</style>
|
||||
<script type="application/json" id="statistics-data">
|
||||
{
|
||||
}
|
||||
</script>
|
||||
<script src="/static/js/statistics.js"></script>
|
||||
{% endblock %}
|
383
src/views/marketplace/three_nodes.html
Normal file
383
src/views/marketplace/three_nodes.html
Normal file
@@ -0,0 +1,383 @@
|
||||
{% extends "marketplace/layout.html" %}
|
||||
|
||||
{% block title %}Project Mycelium - 3Nodes Hardware{% endblock %}
|
||||
|
||||
{% block marketplace_content %}
|
||||
<div class="my-4">
|
||||
<h1>3Nodes Hardware</h1>
|
||||
<p class="lead">Discover certified hardware nodes that power the ThreeFold Grid infrastructure.</p>
|
||||
|
||||
<!-- 3Nodes Introduction -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<div class="d-flex">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-server fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="alert-heading">What are 3Nodes?</h5>
|
||||
<p>3Nodes are the physical hardware units that make up the ThreeFold Grid. These certified servers provide compute, storage, and network capacity to the decentralized internet infrastructure.</p>
|
||||
<hr>
|
||||
<p class="mb-0">By purchasing a 3Node, you become a farmer in the ThreeFold ecosystem, earning TFT rewards while contributing to the decentralized internet.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter and Search Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Filter 3Nodes</h5>
|
||||
<form class="row g-3" method="GET">
|
||||
<div class="col-md-3">
|
||||
<label for="locationFilter" class="form-label">Location</label>
|
||||
<select name="location" id="locationFilter" class="form-select">
|
||||
<option value="">All Locations</option>
|
||||
<option value="Belgium">Belgium</option>
|
||||
<option value="Austria">Austria</option>
|
||||
<option value="Switzerland">Switzerland</option>
|
||||
<option value="Netherlands">Netherlands</option>
|
||||
<option value="Germany">Germany</option>
|
||||
<option value="France">France</option>
|
||||
<option value="UK">UK</option>
|
||||
<option value="Canada">Canada</option>
|
||||
<option value="USA">USA</option>
|
||||
<option value="Japan">Japan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="certificationFilter" class="form-label">Certification</label>
|
||||
<select name="certification_status" id="certificationFilter" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
<option value="DIY">DIY Nodes</option>
|
||||
<option value="Certified">Certified Nodes</option>
|
||||
<option value="Titan">Titan Nodes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="minPriceFilter" class="form-label">Min Price ($)</label>
|
||||
<input type="number" name="min_price" id="minPriceFilter" class="form-control"
|
||||
value="" placeholder="0" min="0" step="10">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="maxPriceFilter" class="form-label">Max Price ($)</label>
|
||||
<input type="number" name="max_price" id="maxPriceFilter" class="form-control"
|
||||
value="" placeholder="500" min="0" step="10">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary me-2">Apply Filters</button>
|
||||
<a href="/marketplace/3nodes" class="btn btn-outline-secondary">Clear</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3Nodes Grid -->
|
||||
<div class="row">
|
||||
{% if hardware_products and hardware_products | length > 0 %}
|
||||
{% for product_data in hardware_products %}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div class="node-icon me-3">
|
||||
<i class="bi bi-server fs-2 text-primary"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h5 class="card-title mb-1">
|
||||
<a href="/products/{{ product_data.product.id }}" class="text-decoration-none text-dark">
|
||||
{{ product_data.product.name }}
|
||||
</a>
|
||||
</h5>
|
||||
{% if product_data.product.metadata.featured %}
|
||||
<span class="badge bg-warning">Featured</span>
|
||||
{% elif product_data.product.availability == "Available" %}
|
||||
<span class="badge bg-success">Available</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ product_data.product.availability }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-muted small mb-2">
|
||||
<i class="bi bi-building me-1"></i>{{ product_data.product.provider_name }}
|
||||
{% if product_data.product.metadata.location %}
|
||||
<span class="ms-2">
|
||||
<i class="bi bi-geo-alt me-1"></i>{{ product_data.product.metadata.location }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="card-text">{{ product_data.product.description | truncate(length=120) }}</p>
|
||||
|
||||
<!-- Hardware Specifications -->
|
||||
{% if product_data.product.attributes %}
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">Hardware Specifications:</h6>
|
||||
<div class="row">
|
||||
{% if product_data.product.attributes.cpu_cores %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-cpu me-2 text-primary"></i>
|
||||
<span>{{ product_data.product.attributes.cpu_cores.value }} CPU Cores</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.memory_gb %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-memory me-2 text-primary"></i>
|
||||
<span>{{ product_data.product.attributes.memory_gb.value }} GB RAM</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.storage_gb %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-hdd me-2 text-primary"></i>
|
||||
<span>{{ product_data.product.attributes.storage_gb.value }} GB Storage</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product_data.product.attributes.bandwidth_mbps %}
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item">
|
||||
<i class="bi bi-speedometer2 me-2 text-primary"></i>
|
||||
<span>{{ product_data.product.attributes.bandwidth_mbps.value }} Mbps Network</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Node Features -->
|
||||
{% if product_data.product.metadata.tags and product_data.product.metadata.tags | length > 0 %}
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">Features:</h6>
|
||||
<div class="d-flex flex-wrap">
|
||||
{% for tag in product_data.product.metadata.tags %}
|
||||
<span class="badge bg-light text-dark me-1 mb-1">{{ tag | title }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Farming Potential -->
|
||||
<div class="mb-3">
|
||||
<div class="alert alert-success small">
|
||||
<i class="bi bi-currency-dollar me-1"></i>
|
||||
<strong>Farming Potential:</strong> Earn TFT rewards by contributing to the ThreeFold Grid
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="price-info">
|
||||
<div class="fw-bold text-primary fs-5">{{ product_data.formatted_price }}</div>
|
||||
<small class="text-muted">one-time purchase</small>
|
||||
</div>
|
||||
<div class="btn-group-vertical w-100">
|
||||
<div class="btn-group mb-2">
|
||||
<button class="btn btn-success btn-sm buy-now-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}"
|
||||
data-category="3nodes">
|
||||
<i class="bi bi-lightning-charge me-1"></i>Buy Now
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm add-to-cart-btn"
|
||||
data-product-id="{{ product_data.product.id }}"
|
||||
data-product-name="{{ product_data.product.name }}"
|
||||
data-unit-price="{{ product_data.price.display_amount }}"
|
||||
data-currency="{{ product_data.price.display_currency }}">
|
||||
<i class="bi bi-cart-plus me-1"></i>Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
<a href="/products/{{ product_data.product.id }}" class="btn btn-outline-primary btn-sm">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-server display-1 text-muted"></i>
|
||||
<h4 class="mt-3">No 3Nodes Available</h4>
|
||||
<p class="text-muted">Check back later for new hardware offerings.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.total_pages > 1 %}
|
||||
<nav aria-label="3Node pages" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Previous Page -->
|
||||
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
||||
{% if pagination.has_previous %}
|
||||
<a class="page-link" href="?page={{ pagination.previous_page }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 1 -->
|
||||
<li class="page-item {% if pagination.current_page == 0 %}active{% endif %}">
|
||||
{% if pagination.current_page == 0 %}
|
||||
<span class="page-link">1</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=0">1</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<!-- Page 2 (if exists) -->
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<li class="page-item {% if pagination.current_page == 1 %}active{% endif %}">
|
||||
{% if pagination.current_page == 1 %}
|
||||
<span class="page-link">2</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=1">2</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 3 (if exists) -->
|
||||
{% if pagination.total_pages > 2 %}
|
||||
<li class="page-item {% if pagination.current_page == 2 %}active{% endif %}">
|
||||
{% if pagination.current_page == 2 %}
|
||||
<span class="page-link">3</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=2">3</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 4 (if exists) -->
|
||||
{% if pagination.total_pages > 3 %}
|
||||
<li class="page-item {% if pagination.current_page == 3 %}active{% endif %}">
|
||||
{% if pagination.current_page == 3 %}
|
||||
<span class="page-link">4</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=3">4</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Page 5 (if exists) -->
|
||||
{% if pagination.total_pages > 4 %}
|
||||
<li class="page-item {% if pagination.current_page == 4 %}active{% endif %}">
|
||||
{% if pagination.current_page == 4 %}
|
||||
<span class="page-link">5</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page=4">5</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- Next Page -->
|
||||
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
||||
{% if pagination.has_next %}
|
||||
<a class="page-link" href="?page={{ pagination.next_page }}">Next</a>
|
||||
{% else %}
|
||||
<span class="page-link" tabindex="-1" aria-disabled="true">Next</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Results Info -->
|
||||
<div class="text-center text-muted mt-2">
|
||||
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
|
||||
({{ pagination.total_count }} total 3Nodes)
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Farming Information -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Become a ThreeFold Farmer</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>1. Purchase Your 3Node</h5>
|
||||
<p>Choose from certified hardware options that meet ThreeFold Grid requirements.</p>
|
||||
|
||||
<h5>2. Connect to the Grid</h5>
|
||||
<p>Boot your 3Node with Zero-OS and connect it to the ThreeFold Grid infrastructure.</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>3. Start Earning</h5>
|
||||
<p>Your 3Node automatically provides capacity to the grid and earns TFT rewards based on utilization.</p>
|
||||
|
||||
<h5>4. Monitor & Maintain</h5>
|
||||
<p>Use the ThreeFold Dashboard to monitor your node's performance and earnings.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-3">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<strong>ROI Potential:</strong> 3Node farmers typically see return on investment within 2-4 years, depending on grid utilization and TFT price.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Node Types -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h3 class="mb-4">3Node Types</h3>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-tools fs-1 text-primary mb-3"></i>
|
||||
<h5>DIY Nodes</h5>
|
||||
<p class="small text-muted">Build your own 3Node using compatible hardware. Most cost-effective option for technical users.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-award fs-1 text-primary mb-3"></i>
|
||||
<h5>Certified Nodes</h5>
|
||||
<p class="small text-muted">Pre-built and certified hardware that's guaranteed to work with the ThreeFold Grid.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-lightning fs-1 text-primary mb-3"></i>
|
||||
<h5>Titan Nodes</h5>
|
||||
<p class="small text-muted">High-performance enterprise-grade nodes for maximum farming potential and grid contribution.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.spec-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/static/js/marketplace-category.js"></script>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user