← back to Handbag Auth Nextjs
arbitrage/arbitrage.html
725 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LUXVAULT Arbitrage Calculator - Japan → USA/HK</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
color: white;
margin-bottom: 30px;
}
h1 {
font-size: 3em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.subtitle {
font-size: 1.2em;
opacity: 0.9;
}
.exchange-rates {
background: rgba(255,255,255,0.2);
padding: 15px;
border-radius: 10px;
color: white;
text-align: center;
margin: 20px 0;
}
.exchange-rates strong {
font-size: 1.1em;
margin: 0 15px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
@media (max-width: 1024px) {
.grid {
grid-template-columns: 1fr;
}
}
.card {
background: white;
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.card h2 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.8em;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #333;
}
input, select {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: #667eea;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 30px;
font-size: 1.1em;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
width: 100%;
margin-top: 10px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.result {
margin-top: 20px;
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 10px;
display: none;
}
.result.show {
display: block;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.result-header {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 15px;
text-align: center;
}
.result-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 15px;
}
.result-box {
background: white;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.result-box h4 {
color: #667eea;
margin-bottom: 10px;
font-size: 0.9em;
text-transform: uppercase;
}
.result-box .value {
font-size: 1.8em;
font-weight: bold;
color: #333;
}
.profit-positive {
color: #10b981;
}
.profit-negative {
color: #ef4444;
}
.recommendation {
text-align: center;
padding: 15px;
border-radius: 8px;
font-size: 1.3em;
font-weight: bold;
margin-top: 15px;
}
.recommendation.excellent {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.recommendation.good {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
}
.recommendation.marginal {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
}
.recommendation.skip {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.opportunities-list {
margin-top: 20px;
}
.opportunity-card {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 20px;
border-radius: 10px;
margin-bottom: 15px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.opportunity-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.opportunity-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.opportunity-brand {
font-size: 1.3em;
font-weight: bold;
color: #333;
}
.opportunity-margin {
font-size: 1.5em;
font-weight: bold;
padding: 5px 15px;
border-radius: 20px;
background: white;
}
.opportunity-details {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-top: 10px;
}
.opportunity-detail {
text-align: center;
}
.opportunity-detail-label {
font-size: 0.8em;
color: #666;
margin-bottom: 3px;
}
.opportunity-detail-value {
font-size: 1.1em;
font-weight: bold;
color: #333;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.stat-value {
font-size: 2em;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.9em;
opacity: 0.9;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab {
flex: 1;
padding: 15px;
background: #e0e0e0;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1.1em;
transition: all 0.3s;
}
.tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-yen-sign"></i> LUXVAULT Arbitrage Calculator</h1>
<p class="subtitle">Find profitable luxury handbag deals: Japan → USA / Hong Kong</p>
</header>
<div class="exchange-rates" id="exchangeRates">
<i class="fas fa-sync-alt fa-spin"></i> Loading exchange rates...
</div>
<div class="tabs">
<button class="tab active" onclick="switchTab('calculator')">
<i class="fas fa-calculator"></i> Calculator
</button>
<button class="tab" onclick="switchTab('opportunities')">
<i class="fas fa-list"></i> Opportunities
</button>
</div>
<div id="calculator" class="tab-content active">
<div class="grid">
<!-- Calculator Form -->
<div class="card">
<h2><i class="fas fa-calculator"></i> Calculate Arbitrage</h2>
<form id="arbitrageForm">
<div class="form-group">
<label>Brand</label>
<input type="text" id="brand" placeholder="e.g., Hermès" required>
</div>
<div class="form-group">
<label>Model</label>
<input type="text" id="model" placeholder="e.g., Birkin 30" required>
</div>
<div class="form-group">
<label>Condition</label>
<select id="condition">
<option value="new">New</option>
<option value="excellent" selected>Excellent</option>
<option value="good">Good</option>
<option value="fair">Fair</option>
</select>
</div>
<div class="form-group">
<label>Japan Price (¥)</label>
<input type="number" id="japanPrice" placeholder="e.g., 350000" required>
</div>
<div class="row">
<div class="form-group">
<label>USA Price ($) <small>(optional)</small></label>
<input type="number" id="usaPrice" placeholder="e.g., 4200">
</div>
<div class="form-group">
<label>HK Price (HK$) <small>(optional)</small></label>
<input type="number" id="hkPrice" placeholder="e.g., 32000">
</div>
</div>
<div class="row">
<div class="form-group">
<label>Shipping</label>
<select id="shipping">
<option value="economy">Economy ($50)</option>
<option value="express">Express ($100)</option>
</select>
</div>
<div class="form-group">
<label>Selling Platform</label>
<select id="platform">
<option value="ebay">eBay (13%)</option>
<option value="fashionphile" selected>Fashionphile (15%)</option>
<option value="therealreal">TheRealReal (20%)</option>
<option value="rebag">Rebag (18%)</option>
</select>
</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="includeAuth" checked>
Include Authentication ($50)
</label>
</div>
<button type="submit" class="btn">
<i class="fas fa-chart-line"></i> Calculate Profit
</button>
</form>
</div>
<!-- Results -->
<div class="card">
<h2><i class="fas fa-chart-bar"></i> Results</h2>
<div id="results" class="result">
<!-- Results will be displayed here -->
</div>
<div id="noResults" style="text-align: center; padding: 60px; color: #999;">
<i class="fas fa-calculator" style="font-size: 4em; margin-bottom: 20px;"></i>
<p>Enter details and click "Calculate Profit" to see results</p>
</div>
</div>
</div>
</div>
<div id="opportunities" class="tab-content">
<div class="card">
<h2><i class="fas fa-list"></i> Top Arbitrage Opportunities</h2>
<div class="stats-grid" id="statsGrid">
<div class="stat-card">
<div class="stat-value" id="totalOpportunities">-</div>
<div class="stat-label">Total Opportunities</div>
</div>
<div class="stat-card">
<div class="stat-value" id="avgProfit">-</div>
<div class="stat-label">Avg Profit</div>
</div>
<div class="stat-card">
<div class="stat-value" id="avgMargin">-</div>
<div class="stat-label">Avg Margin</div>
</div>
<div class="stat-card">
<div class="stat-value" id="topBrand">-</div>
<div class="stat-label">Top Brand</div>
</div>
</div>
<div class="opportunities-list" id="opportunitiesList">
<div class="loading">
<i class="fas fa-spinner fa-spin" style="font-size: 2em;"></i>
<p>Loading opportunities...</p>
</div>
</div>
</div>
</div>
</div>
<script>
const API_BASE = 'http://45.61.58.125:7600/api/arbitrage';
let exchangeRates = {};
// Load exchange rates
async function loadExchangeRates() {
try {
const response = await fetch(`${API_BASE}/rates`);
const data = await response.json();
if (data.success) {
exchangeRates = data.rates;
document.getElementById('exchangeRates').innerHTML = `
<strong>JPY/USD:</strong> ¥${(1/data.rates.JPYUSD).toFixed(2)}/$1 |
<strong>HKD/USD:</strong> HK$${(1/data.rates.HKDUSD).toFixed(2)}/$1 |
<small>Updated: ${new Date(data.rates.lastUpdated).toLocaleString()}</small>
`;
}
} catch (error) {
console.error('Failed to load exchange rates:', error);
}
}
// Calculate arbitrage
document.getElementById('arbitrageForm').addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
brand: document.getElementById('brand').value,
model: document.getElementById('model').value,
condition: document.getElementById('condition').value,
japanPriceJPY: parseFloat(document.getElementById('japanPrice').value),
usaPriceUSD: parseFloat(document.getElementById('usaPrice').value) || null,
hkPriceHKD: parseFloat(document.getElementById('hkPrice').value) || null,
shippingType: document.getElementById('shipping').value,
sellingPlatform: document.getElementById('platform').value,
includeAuth: document.getElementById('includeAuth').checked
};
try {
const response = await fetch(`${API_BASE}/calculate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
displayResults(result.data);
}
} catch (error) {
console.error('Calculation failed:', error);
alert('Failed to calculate arbitrage. Please try again.');
}
});
function displayResults(data) {
const resultsDiv = document.getElementById('results');
const noResultsDiv = document.getElementById('noResults');
noResultsDiv.style.display = 'none';
resultsDiv.classList.add('show');
const cost = data.costBreakdown;
const usa = data.usa;
const hk = data.hongkong;
const best = data.bestRoute;
let html = `
<div class="result-header">
${data.product.brand} ${data.product.model}
</div>
<div class="result-box">
<h4>Japan Purchase</h4>
<div class="value">¥${cost.japan.purchaseJPY.toLocaleString()}</div>
<small>- Tax Refund: ¥${cost.japan.taxRefundJPY.toLocaleString()}</small><br>
<small>= Net: $${cost.japan.purchaseUSD.toLocaleString()}</small>
</div>
<div class="result-box">
<h4>Additional Costs</h4>
<div class="value">$${cost.totalCostUSD.toLocaleString()}</div>
<small>Shipping: $${cost.additionalCosts.shipping} | Duties: $${cost.additionalCosts.duties.toFixed(0)} | Fees: $${cost.additionalCosts.paymentFees.toFixed(0)} | Auth: $${cost.additionalCosts.authentication}</small>
</div>
`;
if (usa) {
html += `
<div class="result-box">
<h4>USA Market</h4>
<div class="value">$${usa.sellPrice.toLocaleString()}</div>
<small>- Selling Fee: $${usa.sellingFee.toFixed(0)} = Net: $${usa.netRevenue.toLocaleString()}</small>
</div>
<div class="result-box">
<h4>USA Profit</h4>
<div class="value ${usa.profitable ? 'profit-positive' : 'profit-negative'}">
$${usa.profit.toLocaleString()} (${usa.profitMargin.toFixed(1)}%)
</div>
</div>
`;
}
if (hk) {
html += `
<div class="result-box">
<h4>Hong Kong Market</h4>
<div class="value">$${hk.sellPrice.toLocaleString()}</div>
<small>HK$${hk.sellPriceHKD.toLocaleString()} | Fee: $${hk.sellingFee.toFixed(0)}</small>
</div>
<div class="result-box">
<h4>HK Profit</h4>
<div class="value ${hk.profitable ? 'profit-positive' : 'profit-negative'}">
$${hk.profit.toLocaleString()} (${hk.profitMargin.toFixed(1)}%)
</div>
</div>
`;
}
if (best) {
const recClass = best.recommendation.toLowerCase();
html += `
<div class="recommendation ${recClass}">
<i class="fas fa-${best.recommendation === 'EXCELLENT' ? 'star' : (best.recommendation === 'GOOD' ? 'thumbs-up' : (best.recommendation === 'MARGINAL' ? 'hand-point-right' : 'times-circle'))}"></i>
${best.recommendation}: Best route is ${best.market}
<br>
Profit: $${best.profit.toLocaleString()} (${best.profitMargin.toFixed(1)}% margin)
</div>
`;
}
resultsDiv.innerHTML = html;
}
// Load opportunities
async function loadOpportunities() {
try {
const [oppsResponse, statsResponse] = await Promise.all([
fetch(`${API_BASE}/opportunities?minMargin=0&limit=20`),
fetch(`${API_BASE}/stats`)
]);
const oppsData = await oppsResponse.json();
const statsData = await statsResponse.json();
if (statsData.success) {
displayStats(statsData.stats);
}
if (oppsData.success) {
displayOpportunities(oppsData.data);
}
} catch (error) {
console.error('Failed to load opportunities:', error);
}
}
function displayStats(stats) {
document.getElementById('totalOpportunities').textContent = stats.totalOpportunities;
document.getElementById('avgProfit').textContent = '$' + stats.avgProfit.toLocaleString();
document.getElementById('avgMargin').textContent = stats.avgMargin.toFixed(1) + '%';
document.getElementById('topBrand').textContent = stats.topBrand.brand;
}
function displayOpportunities(opportunities) {
const listDiv = document.getElementById('opportunitiesList');
if (opportunities.length === 0) {
listDiv.innerHTML = '<div class="loading">No opportunities found</div>';
return;
}
let html = '';
opportunities.forEach(opp => {
const marginClass = opp.profit_margin_pct >= 40 ? 'profit-positive' : (opp.profit_margin_pct >= 25 ? 'profit-positive' : '');
html += `
<div class="opportunity-card">
<div class="opportunity-header">
<div class="opportunity-brand">
${opp.brand} ${opp.model}
</div>
<div class="opportunity-margin ${marginClass}">
${opp.profit_margin_pct.toFixed(1)}%
</div>
</div>
<div class="opportunity-details">
<div class="opportunity-detail">
<div class="opportunity-detail-label">Japan Price</div>
<div class="opportunity-detail-value">¥${opp.japan_price_jpy.toLocaleString()}</div>
</div>
<div class="opportunity-detail">
<div class="opportunity-detail-label">USA Price</div>
<div class="opportunity-detail-value">$${opp.usa_price_usd ? opp.usa_price_usd.toLocaleString() : 'N/A'}</div>
</div>
<div class="opportunity-detail">
<div class="opportunity-detail-label">Profit</div>
<div class="opportunity-detail-value profit-positive">$${opp.potential_profit_usd ? opp.potential_profit_usd.toLocaleString() : 'N/A'}</div>
</div>
<div class="opportunity-detail">
<div class="opportunity-detail-label">Source</div>
<div class="opportunity-detail-value">${opp.japan_source || 'Manual'}</div>
</div>
</div>
</div>
`;
});
listDiv.innerHTML = html;
}
function switchTab(tab) {
// Update tab buttons
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.closest('.tab').classList.add('active');
// Update content
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.getElementById(tab).classList.add('active');
// Load opportunities if switching to that tab
if (tab === 'opportunities') {
loadOpportunities();
}
}
// Initialize
loadExchangeRates();
setInterval(loadExchangeRates, 60000); // Refresh every minute
</script>
</body>
</html>