18 KiB
18 KiB
Dual UX Specification: Modern App + E-commerce Flows
Document Purpose: Comprehensive UX specification supporting both modern app-style instant purchase and traditional e-commerce cart workflows.
Last Updated: 2025-08-04
Status: Implementation Ready
Overview
The Project Mycelium supports two distinct user experience patterns to accommodate different user preferences and use cases:
- Modern App Flow (OpenRouter-style): Instant purchase with wallet top-up
- Traditional E-commerce Flow: Cart-based shopping with checkout
UX Flow Comparison
Flow 1: Modern App Style (OpenRouter-inspired)
graph TD
A[Browse Marketplace] --> B[Register/Login]
B --> C[View Service]
C --> D[Buy Now Button]
D --> E{TFC Balance Check}
E -->|Sufficient| F[Instant Deploy]
E -->|Insufficient| G[Auto Top-up Modal]
G --> H[Stripe Payment]
H --> I[TFC Added]
I --> F
F --> J[Deployment Status]
J --> K[Service Active]
Key Features:
- Instant Purchase: Single-click buying
- Auto Top-up: Seamless balance management
- Wallet-centric: Balance always visible
- Minimal Friction: No cart, no checkout process
Flow 2: Traditional E-commerce Style
graph TD
A[Browse Marketplace] --> B[View Service]
B --> C[Add to Cart]
C --> D[Continue Shopping]
D --> E[Review Cart]
E --> F[Checkout]
F --> G{Payment Method}
G -->|TFC Balance| H[Pay with TFC]
G -->|Credit Card| I[Stripe Checkout]
I --> J[TFC Purchase]
J --> H
H --> K[Batch Deployment]
K --> L[Order Confirmation]
Key Features:
- Bulk Shopping: Multiple items in cart
- Price Comparison: Review before purchase
- Batch Deployment: Deploy multiple services together
- Familiar UX: Traditional e-commerce experience
Detailed UX Specifications
Modern App Flow Implementation
1. Wallet-First Interface
<!-- Wallet Status Component (Always Visible) -->
<div class="wallet-status-bar">
<div class="balance-display">
<span class="balance-amount">{{user.tfc_balance}} TFC</span>
<span class="usd-equivalent">${{user.tfc_balance}} USD</span>
</div>
<div class="wallet-actions">
<button class="btn btn-sm btn-outline-primary" onclick="showTopUpModal()">
<i class="bi bi-plus-circle"></i> Top Up
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="toggleAutoTopUp()">
<i class="bi bi-arrow-repeat"></i> Auto Top-up: {{#if user.auto_topup}}ON{{else}}OFF{{/if}}
</button>
</div>
</div>
2. Buy Now Button (Primary Action)
<!-- Service Card with Buy Now -->
<div class="service-card modern-style">
<div class="service-header">
<h4>{{service.name}}</h4>
<div class="price-tag">
<span class="tfc-price">{{service.price_tfc}} TFC</span>
<span class="deployment-time">~2 min deploy</span>
</div>
</div>
<div class="service-actions">
<button class="btn btn-primary btn-lg buy-now-btn"
onclick="buyNowInstant('{{service.id}}')">
<i class="bi bi-lightning-fill"></i>
Buy Now & Deploy
</button>
<button class="btn btn-outline-secondary add-to-cart-btn"
onclick="addToCart('{{service.id}}')">
<i class="bi bi-cart-plus"></i>
Add to Cart
</button>
</div>
<!-- Balance Check Indicator -->
<div class="balance-check">
{{#if (gte user.tfc_balance service.price_tfc)}}
<span class="text-success">
<i class="bi bi-check-circle"></i> Ready to deploy
</span>
{{else}}
<span class="text-warning">
<i class="bi bi-exclamation-triangle"></i>
Need {{subtract service.price_tfc user.tfc_balance}} more TFC
</span>
{{/if}}
</div>
</div>
3. Auto Top-up Configuration
<!-- Auto Top-up Settings Modal -->
<div class="modal fade" id="autoTopUpModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5>Auto Top-up Settings</h5>
</div>
<div class="modal-body">
<div class="form-group">
<label>Enable Auto Top-up</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="enableAutoTopUp">
<label class="form-check-label">Automatically add TFC when balance is low</label>
</div>
</div>
<div class="form-group">
<label>Trigger Threshold</label>
<select class="form-select" id="topUpThreshold">
<option value="10">When balance < 10 TFC</option>
<option value="25">When balance < 25 TFC</option>
<option value="50" selected>When balance < 50 TFC</option>
<option value="100">When balance < 100 TFC</option>
</select>
</div>
<div class="form-group">
<label>Top-up Amount</label>
<select class="form-select" id="topUpAmount">
<option value="50">Add 50 TFC ($50)</option>
<option value="100" selected>Add 100 TFC ($100)</option>
<option value="200">Add 200 TFC ($200)</option>
<option value="500">Add 500 TFC ($500)</option>
</select>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
Auto top-up uses your saved payment method. You'll receive an email confirmation for each transaction.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveAutoTopUpSettings()">Save Settings</button>
</div>
</div>
</div>
</div>
Traditional E-commerce Flow Implementation
1. Shopping Cart Component
<!-- Shopping Cart (Sidebar or Page) -->
<div class="shopping-cart">
<div class="cart-header">
<h4>Shopping Cart</h4>
<span class="item-count">{{cart.items.length}} items</span>
</div>
<div class="cart-items">
{{#each cart.items}}
<div class="cart-item">
<div class="item-info">
<h6>{{this.service_name}}</h6>
<p class="item-specs">{{this.cpu}}CPU • {{this.memory}}GB RAM • {{this.storage}}GB</p>
</div>
<div class="item-price">
<span class="tfc-price">{{this.price_tfc}} TFC</span>
<button class="btn btn-sm btn-outline-danger" onclick="removeFromCart('{{this.id}}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
{{/each}}
</div>
<div class="cart-summary">
<div class="summary-line">
<span>Subtotal:</span>
<span>{{cart.subtotal}} TFC</span>
</div>
<div class="summary-line">
<span>Estimated Deploy Time:</span>
<span>~{{cart.estimated_deploy_time}} minutes</span>
</div>
<div class="summary-line total">
<span><strong>Total:</strong></span>
<span><strong>{{cart.total}} TFC</strong></span>
</div>
<button class="btn btn-primary btn-lg w-100" onclick="proceedToCheckout()">
<i class="bi bi-credit-card"></i>
Proceed to Checkout
</button>
</div>
</div>
2. Checkout Process
<!-- Checkout Page -->
<div class="checkout-container">
<div class="checkout-steps">
<div class="step active">1. Review Order</div>
<div class="step">2. Payment</div>
<div class="step">3. Deployment</div>
</div>
<div class="checkout-content">
<div class="order-review">
<h5>Order Summary</h5>
<!-- Cart items review -->
<div class="deployment-options">
<h6>Deployment Options</h6>
<div class="form-check">
<input class="form-check-input" type="radio" name="deploymentTiming" value="immediate" checked>
<label class="form-check-label">Deploy immediately after payment</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="deploymentTiming" value="scheduled">
<label class="form-check-label">Schedule deployment for later</label>
</div>
</div>
</div>
<div class="payment-section">
<h5>Payment Method</h5>
<div class="payment-options">
<div class="payment-option" onclick="selectPaymentMethod('tfc')">
<div class="option-header">
<i class="bi bi-wallet2"></i>
<span>Pay with TFC Balance</span>
<span class="balance-info">{{user.tfc_balance}} TFC available</span>
</div>
{{#if (lt user.tfc_balance cart.total)}}
<div class="insufficient-notice">
<span class="text-warning">Insufficient balance. Need {{subtract cart.total user.tfc_balance}} more TFC.</span>
</div>
{{/if}}
</div>
<div class="payment-option" onclick="selectPaymentMethod('stripe')">
<div class="option-header">
<i class="bi bi-credit-card"></i>
<span>Credit/Debit Card</span>
<span class="amount-info">${{cart.total}} USD</span>
</div>
</div>
<div class="payment-option" onclick="selectPaymentMethod('mixed')">
<div class="option-header">
<i class="bi bi-shuffle"></i>
<span>Use TFC + Credit Card</span>
<span class="mixed-info">{{user.tfc_balance}} TFC + ${{subtract cart.total user.tfc_balance}} USD</span>
</div>
</div>
</div>
</div>
</div>
</div>
JavaScript Implementation
Modern App Flow Functions
// Modern App Style - Instant Purchase
async function buyNowInstant(serviceId) {
try {
// Check balance first
const balance = await getUserTFCBalance();
const service = await getServiceDetails(serviceId);
if (balance >= service.price_tfc) {
// Sufficient balance - instant deploy
showLoadingToast('Starting deployment...');
const result = await initiateDeployment(serviceId, 'tfc');
if (result.success) {
showSuccessToast('Deployment started! Redirecting...');
window.location.href = `/dashboard/deployments/${result.deployment_id}`;
}
} else {
// Insufficient balance - auto top-up flow
const needed = service.price_tfc - balance;
if (await isAutoTopUpEnabled()) {
showAutoTopUpModal(needed, serviceId);
} else {
showManualTopUpModal(needed, serviceId);
}
}
} catch (error) {
showErrorToast('Failed to process purchase');
}
}
// Auto Top-up Flow
async function handleAutoTopUp(amount, serviceId) {
try {
showLoadingToast('Processing auto top-up...');
const topUpResult = await processAutoTopUp(amount);
if (topUpResult.success) {
showSuccessToast('Balance updated! Starting deployment...');
// Proceed with deployment
const deployResult = await initiateDeployment(serviceId, 'tfc');
if (deployResult.success) {
window.location.href = `/dashboard/deployments/${deployResult.deployment_id}`;
}
}
} catch (error) {
showErrorToast('Auto top-up failed');
}
}
// Real-time Balance Updates
function startBalancePolling() {
setInterval(async () => {
const balance = await getUserTFCBalance();
updateBalanceDisplay(balance);
}, 10000); // Update every 10 seconds
}
function updateBalanceDisplay(balance) {
document.querySelector('.balance-amount').textContent = `${balance} TFC`;
document.querySelector('.usd-equivalent').textContent = `$${balance} USD`;
// Update buy buttons state
document.querySelectorAll('.buy-now-btn').forEach(btn => {
const servicePrice = parseFloat(btn.dataset.price);
if (balance >= servicePrice) {
btn.classList.remove('insufficient-balance');
btn.disabled = false;
} else {
btn.classList.add('insufficient-balance');
btn.disabled = true;
}
});
}
Traditional E-commerce Functions
// Traditional E-commerce - Cart Management
class ShoppingCart {
constructor() {
this.items = JSON.parse(localStorage.getItem('cart_items') || '[]');
this.updateCartDisplay();
}
addItem(serviceId, serviceName, price, specs) {
const existingItem = this.items.find(item => item.service_id === serviceId);
if (existingItem) {
showInfoToast('Item already in cart');
return;
}
this.items.push({
id: generateId(),
service_id: serviceId,
service_name: serviceName,
price_tfc: price,
specs: specs,
added_at: new Date().toISOString()
});
this.saveCart();
this.updateCartDisplay();
showSuccessToast('Added to cart');
}
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId);
this.saveCart();
this.updateCartDisplay();
showSuccessToast('Removed from cart');
}
getTotal() {
return this.items.reduce((total, item) => total + item.price_tfc, 0);
}
async proceedToCheckout() {
if (this.items.length === 0) {
showErrorToast('Cart is empty');
return;
}
// Navigate to checkout page with cart data
const cartData = encodeURIComponent(JSON.stringify(this.items));
window.location.href = `/checkout?cart=${cartData}`;
}
saveCart() {
localStorage.setItem('cart_items', JSON.stringify(this.items));
}
updateCartDisplay() {
const cartCount = document.querySelector('.cart-count');
const cartTotal = document.querySelector('.cart-total');
if (cartCount) cartCount.textContent = this.items.length;
if (cartTotal) cartTotal.textContent = `${this.getTotal()} TFC`;
}
}
// Checkout Process
async function processCheckout(paymentMethod, cartItems) {
try {
showLoadingToast('Processing order...');
const orderData = {
items: cartItems,
payment_method: paymentMethod,
total_tfc: cartItems.reduce((sum, item) => sum + item.price_tfc, 0)
};
const result = await fetch('/api/checkout/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
});
const response = await result.json();
if (response.success) {
// Clear cart
localStorage.removeItem('cart_items');
// Redirect to order confirmation
window.location.href = `/orders/${response.order_id}`;
} else {
showErrorToast(response.message);
}
} catch (error) {
showErrorToast('Checkout failed');
}
}
User Preference System
UX Mode Selection
<!-- User Preferences - UX Mode -->
<div class="ux-preference-setting">
<h6>Shopping Experience</h6>
<div class="form-check">
<input class="form-check-input" type="radio" name="uxMode" value="modern" id="modernUX">
<label class="form-check-label" for="modernUX">
<strong>Modern App Style</strong>
<br><small>Instant purchases with wallet top-up (like OpenRouter)</small>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="uxMode" value="ecommerce" id="ecommerceUX">
<label class="form-check-label" for="ecommerceUX">
<strong>Traditional E-commerce</strong>
<br><small>Shopping cart with checkout process</small>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="uxMode" value="both" id="bothUX" checked>
<label class="form-check-label" for="bothUX">
<strong>Both Options</strong>
<br><small>Show both "Buy Now" and "Add to Cart" buttons</small>
</label>
</div>
</div>
Benefits of Dual UX Approach
Modern App Flow Benefits:
- ✅ Speed: Instant purchases
- ✅ Simplicity: Minimal clicks
- ✅ Mobile-friendly: Touch-optimized
- ✅ Auto-management: Set-and-forget top-ups
Traditional E-commerce Benefits:
- ✅ Bulk Shopping: Multiple services at once
- ✅ Price Comparison: Review before buying
- ✅ Familiar: Standard shopping experience
- ✅ Planning: Schedule deployments
Combined Advantages:
- 🎯 User Choice: Accommodate different preferences
- 🎯 Use Case Flexibility: Quick single purchases OR planned bulk orders
- 🎯 Market Coverage: Appeal to both app users and traditional shoppers
- 🎯 Conversion Optimization: Multiple paths to purchase
This dual UX specification ensures the Project Mycelium appeals to both modern app users (who want instant, frictionless purchases) and traditional e-commerce users (who prefer to review and plan their purchases).