2026-04-15-4
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,647 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jordan Map Platform | Developer Dashboard</title>
|
||||
<!-- Tailwind CSS Play CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- MapLibre GL JS -->
|
||||
<script src="https://unpkg.com/maplibre-gl@5.1.1/dist/maplibre-gl.js"></script>
|
||||
<link href="https://unpkg.com/maplibre-gl@5.1.1/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
|
||||
<!-- Firebase SDK (v10.12.0) -->
|
||||
<script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
|
||||
<script src="js/firebase-config.js"></script>
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
slate: {
|
||||
950: '#0a0a0b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Cairo:wght@400;600;700;900&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background-color: #0a0a0b;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
/* RTL Specifics */
|
||||
.rtl-mode #main-content {
|
||||
text-align: right;
|
||||
}
|
||||
.rtl-mode .nav-link {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply flex items-center gap-2 px-4 py-2.5 rounded-xl font-bold transition-all duration-300 active:scale-95 text-sm;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 text-white hover:bg-blue-500 shadow-lg shadow-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-slate-900 text-slate-400 hover:text-white border border-slate-800 hover:border-slate-700;
|
||||
}
|
||||
|
||||
/* Nav logic */
|
||||
.page-section {
|
||||
display: none;
|
||||
}
|
||||
.page-section.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Sidebar active state */
|
||||
.nav-link.active {
|
||||
@apply bg-blue-600/10 text-blue-400 border-r-2 border-blue-600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="overflow-hidden h-screen flex bg-[#0a0a0b]">
|
||||
|
||||
<!-- Login Section -->
|
||||
<section id="login-section" class="fixed inset-0 z-[200] flex items-center justify-center bg-gradient-to-br from-slate-950 via-[#0a0a0b] to-blue-950/20 overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-20 pointer-events-none">
|
||||
<div class="absolute top-1/4 left-1/4 w-96 h-96 bg-blue-600 rounded-full blur-[128px] animate-pulse"></div>
|
||||
<div class="absolute bottom-1/4 right-1/4 w-96 h-96 bg-cyan-600 rounded-full blur-[128px] animate-pulse delay-700"></div>
|
||||
</div>
|
||||
|
||||
<div class="glass max-w-md w-full p-10 rounded-[2.5rem] relative z-10 text-center border-white/10 shadow-2xl">
|
||||
<div class="w-20 h-20 rounded-2xl bg-gradient-to-tr from-blue-600 to-cyan-400 flex items-center justify-center text-white shadow-2xl shadow-blue-500/30 mx-auto mb-8 rotate-3">
|
||||
<i data-lucide="layers" class="w-10 h-10"></i>
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl font-black tracking-tight mb-3 text-gradient">Intaleq Maps</h1>
|
||||
<p class="text-slate-400 font-medium mb-10 leading-relaxed">The premium developer platform for mapping services in Jordan & Syria.</p>
|
||||
|
||||
<button onclick="auth.signInWithGoogle()" class="w-full btn bg-white text-slate-950 hover:bg-slate-100 py-4 text-base font-black shadow-xl flex items-center justify-center gap-3 active:scale-[0.98] transition-all rounded-2xl">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
||||
<path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
||||
<path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
||||
<path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
||||
</svg>
|
||||
Continue with Google
|
||||
</button>
|
||||
|
||||
<p class="mt-8 text-[11px] text-slate-500 font-bold uppercase tracking-widest">Enterprise Ready · Secure Access</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside id="main-sidebar" class="hidden w-64 border-r border-slate-800 bg-slate-950/50 backdrop-blur-xl flex flex-col z-50">
|
||||
<div class="p-8">
|
||||
<div class="flex items-center gap-3 group cursor-pointer">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-blue-600 to-cyan-400 flex items-center justify-center text-white shadow-lg shadow-blue-500/20 group-hover:rotate-12 transition-transform">
|
||||
<i data-lucide="layers" class="w-6 h-6"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="font-black text-lg tracking-tight leading-none">Intaleq</h1>
|
||||
<span class="text-[10px] uppercase tracking-[0.2em] font-bold text-slate-500">Maps SaaS</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 px-4 space-y-2 mt-4">
|
||||
<a href="#home" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-home">
|
||||
<i data-lucide="layout-dashboard" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
<span data-i18n="side-dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a href="#playground" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-playground">
|
||||
<i data-lucide="terminal" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
<span data-i18n="side-playground">Playground</span>
|
||||
</a>
|
||||
<a href="#analytics" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-analytics">
|
||||
<i data-lucide="bar-chart-3" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
<span data-i18n="side-analytics">Analytics</span>
|
||||
</a>
|
||||
<a href="#billing" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-billing">
|
||||
<i data-lucide="credit-card" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
<span data-i18n="side-billing">Billing</span>
|
||||
</a>
|
||||
<a href="#refinement" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group hidden" id="nav-refinement">
|
||||
<i data-lucide="map-pin" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
<span data-i18n="side-audit">Place Audit</span>
|
||||
</a>
|
||||
<a href="#docs" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-docs">
|
||||
<i data-lucide="book-open" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
<span data-i18n="side-docs">Documentation</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="glass p-5 rounded-2xl border border-blue-500/10 bg-gradient-to-br from-blue-600/5 to-transparent">
|
||||
<p class="text-[10px] font-black uppercase tracking-wider text-blue-400 mb-2">Beta Access</p>
|
||||
<p class="text-xs text-slate-400 leading-relaxed font-medium mb-4">You're currently on the free sandbox tier.</p>
|
||||
<a href="#billing" class="w-full btn btn-primary !py-2 !text-xs text-center flex items-center justify-center" data-i18n="side-upgrade">Upgrade Plan</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="main-content" class="hidden flex-1 flex flex-col min-w-0 bg-gradient-to-br from-slate-950 via-[#0f1115] to-slate-950 overflow-y-auto">
|
||||
<!-- Header -->
|
||||
<header class="h-20 border-b border-white/[0.03] flex items-center justify-between px-8 sticky top-0 bg-slate-950/50 backdrop-blur-md z-40">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div>
|
||||
<span class="text-xs font-black uppercase tracking-widest text-slate-500" data-i18n="system-status">System Operational</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Language Toggle -->
|
||||
<button onclick="i18n.toggle()" class="px-3 py-1.5 rounded-lg border border-white/10 hover:bg-white/5 transition-all text-xs font-bold text-slate-400 flex items-center gap-2" data-i18n="lang-toggle">
|
||||
العربية
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col items-end">
|
||||
<p class="text-sm font-bold" id="tenant-name">Loading...</p>
|
||||
<p class="text-[10px] text-slate-500 font-medium" id="tenant-email">developer@intaleq.com</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-full bg-slate-900 border border-slate-800 flex items-center justify-center text-blue-400">
|
||||
<i data-lucide="user" class="w-5 h-5"></i>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="p-12 max-w-7xl mx-auto w-full">
|
||||
|
||||
<!-- Home Section -->
|
||||
<section id="home" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2" id="welcome-msg">Welcome back...</h2>
|
||||
<p class="text-slate-400">Everything you need to build with premium Jordan Map Platform API</p>
|
||||
</div>
|
||||
|
||||
<!-- KPI Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-12">
|
||||
<div class="glass p-6 rounded-2xl group border-[#1e293b]">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-blue-400">
|
||||
<i data-lucide="zap" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-emerald-500">+12.5%</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1" data-i18n="kpi-total-req">Total Requests</p>
|
||||
<div class="text-2xl font-bold tracking-tight" id="stat-total-req">...</div>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl group">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-emerald-400">
|
||||
<i data-lucide="shield-check" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-emerald-500" id="stat-success-rate">99.9%</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1" data-i18n="kpi-success-rate">Success Rate</p>
|
||||
<div class="text-2xl font-bold tracking-tight" id="stat-success-percent">...</div>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl group">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-cyan-400">
|
||||
<i data-lucide="activity" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-slate-500">Live</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1" data-i18n="kpi-active-keys">Active Keys</p>
|
||||
<div class="text-2xl font-bold tracking-tight" id="active-keys-count">...</div>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl group">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-violet-400">
|
||||
<i data-lucide="trending-up" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-slate-500" id="stat-latency-val">-ms</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1" data-i18n="kpi-latency">Avg Latency</p>
|
||||
<div class="text-2xl font-bold tracking-tight" id="stat-avg-latency">...</div>
|
||||
</div>
|
||||
<!-- Usage Battery Card -->
|
||||
<div class="glass p-6 rounded-2xl group bg-gradient-to-br from-blue-600/5 to-transparent border-blue-500/10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-blue-500">
|
||||
<i data-lucide="battery-charging" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-[10px] font-black uppercase text-blue-500 tracking-widest" id="usage-percentage-label">...</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-3" data-i18n="quota-card-title">Monthly Quota</p>
|
||||
<div class="w-full h-2 bg-slate-900 rounded-full overflow-hidden mb-2">
|
||||
<div id="usage-progress-bar" class="h-full bg-blue-500 transition-all duration-1000" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-500 font-bold" id="usage-limit-text">... requests left</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
||||
<!-- Traffic Mockup -->
|
||||
<div class="lg:col-span-2 glass rounded-2xl p-6 relative overflow-hidden">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">Request Traffic</h3>
|
||||
<p class="text-sm text-slate-500">Live traffic across all API endpoints</p>
|
||||
</div>
|
||||
<a href="#analytics" class="text-xs text-blue-500 hover:text-blue-400 font-bold flex items-center gap-1 transition-colors">
|
||||
Full Analytics <i data-lucide="arrow-right" class="w-3.5 h-3.5"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="h-64 flex items-end gap-1.5 px-2 relative" id="traffic-bars">
|
||||
<!-- Premium Animated Bars via JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="glass rounded-2xl p-6 bg-gradient-to-br from-blue-600/10 to-transparent border-blue-500/10">
|
||||
<h3 class="text-lg font-bold mb-2">Quick Start</h3>
|
||||
<p class="text-sm text-slate-500 mb-6 font-medium">Get started with our lightweight SDK in seconds.</p>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-slate-950 rounded-xl p-4 border border-slate-800 font-mono text-xs">
|
||||
<p class="text-slate-500 mb-2"># Install with npm</p>
|
||||
<p class="text-blue-400">npm <span class="text-slate-200">install @intaleq/maps-gl</span></p>
|
||||
</div>
|
||||
<button class="w-full btn btn-secondary text-sm group" onclick="window.open('/api/docs', '_blank')">
|
||||
<i data-lucide="terminal" class="w-4 h-4 text-blue-400 group-hover:scale-110 transition-transform"></i>
|
||||
View API Reference
|
||||
</button>
|
||||
<a href="#playground" class="w-full btn btn-secondary text-sm group">
|
||||
<i data-lucide="globe" class="w-4 h-4 text-cyan-400 group-hover:scale-110 transition-transform"></i>
|
||||
Try Maps Playground
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Keys Table -->
|
||||
<div class="glass rounded-2xl overflow-hidden mb-12">
|
||||
<div class="p-6 border-b border-slate-800 flex items-center justify-between bg-white/[0.02]">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">Your API Keys</h3>
|
||||
<p class="text-sm text-slate-500">Manage keys for your applications</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="app.toggleModal('create-key-modal', true)" id="create-key-btn">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
Create New Key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto min-h-[200px]">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="text-xs uppercase tracking-widest text-slate-500 bg-slate-900/40">
|
||||
<th class="px-6 py-4 font-black">Name</th>
|
||||
<th class="px-6 py-4 font-black">API Key</th>
|
||||
<th class="px-6 py-4 font-black">Status</th>
|
||||
<th class="px-6 py-4 font-black">Restrictions</th>
|
||||
<th class="px-6 py-4 font-black text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="keys-table-body" class="divide-y divide-slate-800/50">
|
||||
<!-- Injected by JS -->
|
||||
<tr>
|
||||
<td colspan="5" class="py-20 text-center text-slate-500">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<i data-lucide="loader-2" class="w-8 h-8 animate-spin text-blue-500"></i>
|
||||
<p>Fetching your secure keys...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Playground Section -->
|
||||
<section id="playground" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2">Maps Playground</h2>
|
||||
<p class="text-slate-400">Test your API keys and visualize vector tiles in real-time</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- Sidebar Controls -->
|
||||
<div class="lg:col-span-1 space-y-6">
|
||||
<div class="glass p-6 rounded-2xl">
|
||||
<h4 class="text-[10px] uppercase font-black tracking-widest text-slate-500 mb-4">Configuration</h4>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-xs text-slate-400 mb-2 block">Active API Key</label>
|
||||
<select id="pg-key-select" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-slate-400 mb-2 block">Map Style</label>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="playground.setStyle('obsidian')" id="style-obsidian" class="flex-1 py-3 text-xs font-bold rounded-xl bg-blue-600 text-white shadow-lg shadow-blue-500/20">Obsidian</button>
|
||||
<button onclick="playground.setStyle('light')" id="style-light" class="flex-1 py-3 text-xs font-bold rounded-xl bg-slate-900 text-slate-500">Light</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Container -->
|
||||
<div class="lg:col-span-3 glass rounded-3xl overflow-hidden relative" style="min-height: 600px;">
|
||||
<div id="map" class="absolute inset-0 w-full h-full bg-slate-900"></div>
|
||||
<!-- Search Overlay -->
|
||||
<div class="absolute top-6 left-6 w-full max-w-sm">
|
||||
<div class="relative">
|
||||
<i data-lucide="search" class="absolute left-4 top-1/2 -translate-y-1/2 text-slate-500 w-4 h-4"></i>
|
||||
<input type="text" id="pg-search" placeholder="Search Amman, Jordan..."
|
||||
class="w-full bg-slate-950/80 backdrop-blur-md border border-slate-800 rounded-2xl px-12 py-4 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50 shadow-2xl">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Analytics Section -->
|
||||
<section id="analytics" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2">Analytics</h2>
|
||||
<p class="text-slate-400">Deep insights into your API performance</p>
|
||||
</div>
|
||||
<!-- Simple Chart Mockups -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
|
||||
<div class="glass p-8 rounded-3xl">
|
||||
<h3 class="text-xl font-bold mb-8">Request Volume (Overall)</h3>
|
||||
<div class="h-80 w-full flex items-end justify-between gap-4 px-4" id="v-chart">
|
||||
<!-- Injected by analytics.js -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass p-8 rounded-3xl">
|
||||
<h3 class="text-xl font-bold mb-8">Success Ratio</h3>
|
||||
<div class="flex items-center justify-center h-80">
|
||||
<div class="relative w-48 h-48 rounded-full border-[12px] border-slate-900 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 w-full h-full -rotate-90">
|
||||
<circle cx="96" cy="96" r="88" fill="transparent" stroke="currentColor" stroke-width="12" class="text-blue-600" id="success-circle" stroke-dasharray="552.9" stroke-dashoffset="0" />
|
||||
</svg>
|
||||
<span class="text-3xl font-black" id="success-percent-display">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass rounded-3xl overflow-hidden">
|
||||
<div class="p-8 border-b border-white/5">
|
||||
<h3 class="text-xl font-bold">Latency Breakdown</h3>
|
||||
</div>
|
||||
<div class="p-8 h-80 flex items-end justify-around gap-2" id="l-chart"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Map Refinement Section -->
|
||||
<section id="refinement" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2">Place Audit</h2>
|
||||
<p class="text-slate-400">Review and approve user-suggested map locations</p>
|
||||
</div>
|
||||
|
||||
<div class="glass rounded-3xl overflow-hidden">
|
||||
<div class="overflow-x-auto min-h-[400px]">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="text-xs uppercase tracking-widest text-slate-500 bg-slate-900/40">
|
||||
<th class="px-8 py-5 font-black">Location Name</th>
|
||||
<th class="px-8 py-5 font-black">Category</th>
|
||||
<th class="px-8 py-5 font-black">Coordinates</th>
|
||||
<th class="px-8 py-5 font-black">Submitter</th>
|
||||
<th class="px-8 py-5 font-black text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="refinement-table-body" class="divide-y divide-slate-800/50">
|
||||
<tr>
|
||||
<td colspan="5" class="py-20 text-center text-slate-500">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<i data-lucide="map-pin" class="w-8 h-8 opacity-20"></i>
|
||||
<p>No pending location suggestions.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Billing Section -->
|
||||
<section id="billing" class="page-section p-10">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-3xl font-black mb-2 text-gradient">Subscription & Billing</h2>
|
||||
<p class="text-slate-400 font-medium">Manage your plan, payment methods, and invoice history.</p>
|
||||
</div>
|
||||
|
||||
<!-- Current Plan Summary -->
|
||||
<div class="glass p-8 rounded-[2rem] border-white/5 mb-12 flex items-center justify-between">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="w-16 h-16 rounded-2xl bg-blue-500/10 flex items-center justify-center text-blue-400">
|
||||
<i data-lucide="crown" class="w-8 h-8"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold mb-1">Current Plan: <span class="current-plan-name text-blue-400">FREE</span></h3>
|
||||
<p class="text-slate-500 text-sm font-medium">Your monthly usage resets on the 1st of each month.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="px-4 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs font-bold text-slate-400 flex items-center gap-2">
|
||||
<i data-lucide="shield-check" class="w-4 h-4 text-emerald-400"></i> Active
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pricing Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
|
||||
<!-- Free -->
|
||||
<div id="plan-card-free" class="glass p-8 rounded-[2.5rem] border-white/5 flex flex-col hover:border-white/10 transition-all">
|
||||
<div class="mb-8">
|
||||
<span class="text-xs font-black tracking-widest text-slate-500 uppercase">Starter</span>
|
||||
<h4 class="text-4xl font-black mt-2">$0 <span class="text-sm font-medium text-slate-500">/mo</span></h4>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-10 flex-1">
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> 8,000 requests /mo
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> Standard Map Tiles
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> Basic Geocoding
|
||||
</li>
|
||||
</ul>
|
||||
<button class="plan-btn w-full btn bg-slate-800 text-white py-4 rounded-2xl font-black text-sm active:scale-95 transition-all">Current Plan</button>
|
||||
</div>
|
||||
|
||||
<!-- Pro -->
|
||||
<div id="plan-card-pro" class="glass p-8 rounded-[2.5rem] border-blue-500/20 bg-blue-500/[0.02] flex flex-col relative overflow-hidden group">
|
||||
<div class="absolute top-4 right-4 bg-blue-500 text-white text-[10px] font-black px-3 py-1 rounded-full uppercase tracking-widest shadow-lg shadow-blue-500/20">Popular</div>
|
||||
<div class="mb-8">
|
||||
<span class="text-xs font-black tracking-widest text-blue-400 uppercase">Professional</span>
|
||||
<h4 class="text-4xl font-black mt-2 text-gradient">$40 <span class="text-sm font-medium text-slate-500">/mo</span></h4>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-10 flex-1">
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> 50,000 requests /mo
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> 3D Building Extrusion
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> Advanced Routing API
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-blue-400"></i> Priority Support
|
||||
</li>
|
||||
</ul>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="billing.startCheckout('PRO', 'PAYMOB')" class="plan-btn flex-1 btn bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-2xl font-black text-sm shadow-xl shadow-blue-500/20 active:scale-95 transition-all">Pay with Card</button>
|
||||
<button onclick="billing.startCheckout('PRO', 'BINANCE')" class="p-4 rounded-2xl bg-white/5 border border-white/5 hover:bg-white/10 transition-all text-yellow-500" title="Pay with Crypto">
|
||||
<i data-lucide="bitcoin" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enterprise -->
|
||||
<div id="plan-card-enterprise" class="glass p-8 rounded-[2.5rem] border-white/5 flex flex-col hover:border-white/10 transition-all">
|
||||
<div class="mb-8">
|
||||
<span class="text-xs font-black tracking-widest text-violet-400 uppercase">Enterprise</span>
|
||||
<h4 class="text-4xl font-black mt-2">Custom</h4>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-10 flex-1">
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-violet-400"></i> Unlimited Requests
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-violet-400"></i> Custom Data Layers
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-violet-400"></i> SLA Guarantees
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-300 font-medium">
|
||||
<i data-lucide="check" class="w-4 h-4 text-violet-400"></i> Dedicated Architect
|
||||
</li>
|
||||
</ul>
|
||||
<button class="plan-btn w-full btn border border-violet-500/30 text-violet-300 hover:bg-violet-500/10 py-4 rounded-2xl font-black text-sm active:scale-95 transition-all">Contact Sales</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoice History -->
|
||||
<div class="glass rounded-[2rem] border-white/5 overflow-hidden">
|
||||
<div class="p-8 border-b border-white/5 flex items-center justify-between">
|
||||
<h3 class="text-lg font-bold">Transaction History</h3>
|
||||
<i data-lucide="receipt" class="w-5 h-5 text-slate-500"></i>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="bg-white/[0.01]">
|
||||
<th class="px-8 py-5 text-[10px] font-black uppercase tracking-widest text-slate-500">Date</th>
|
||||
<th class="px-8 py-5 text-[10px] font-black uppercase tracking-widest text-slate-500">Amount</th>
|
||||
<th class="px-8 py-5 text-[10px] font-black uppercase tracking-widest text-slate-500">Provider</th>
|
||||
<th class="px-8 py-5 text-[10px] font-black uppercase tracking-widest text-slate-500">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="invoice-table-body">
|
||||
<!-- Loaded via JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Documentation Section -->
|
||||
<section id="docs" class="page-section h-full">
|
||||
<div class="flex h-full gap-8">
|
||||
<!-- Docs Nav -->
|
||||
<div class="w-64 flex flex-col gap-2 shrink-0">
|
||||
<div class="mb-4">
|
||||
<h2 class="text-xl font-black text-gradient">Guides</h2>
|
||||
</div>
|
||||
<a href="javascript:void(0)" data-section="getting-started" class="docs-nav-link active bg-blue-500/10 text-blue-400 p-4 rounded-2xl text-sm font-bold flex items-center gap-3 transition-all hover:bg-blue-500/5">
|
||||
<i data-lucide="rocket" class="w-4 h-4"></i> Getting Started
|
||||
</a>
|
||||
<a href="javascript:void(0)" data-section="tiles-api" class="docs-nav-link p-4 rounded-2xl text-sm font-bold text-slate-500 flex items-center gap-3 transition-all hover:bg-white/5 hover:text-white">
|
||||
<i data-lucide="map" class="w-4 h-4"></i> Map Tiles API
|
||||
</a>
|
||||
<a href="javascript:void(0)" data-section="geocoding-api" class="docs-nav-link p-4 rounded-2xl text-sm font-bold text-slate-500 flex items-center gap-3 transition-all hover:bg-white/5 hover:text-white">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> Geocoding API
|
||||
</a>
|
||||
<a href="javascript:void(0)" data-section="routing-api" class="docs-nav-link p-4 rounded-2xl text-sm font-bold text-slate-500 flex items-center gap-3 transition-all hover:bg-white/5 hover:text-white">
|
||||
<i data-lucide="navigation" class="w-4 h-4"></i> Routing API
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Docs Content Area -->
|
||||
<div class="flex-1 glass rounded-[2.5rem] border-white/5 p-10 overflow-y-auto" id="docs-content">
|
||||
<!-- Loaded via docs.js -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<div id="create-key-modal" class="fixed inset-0 z-[100] hidden">
|
||||
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="app.toggleModal('create-key-modal', false)"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center p-4">
|
||||
<div class="glass w-full max-w-md p-8 rounded-3xl animate-in fade-in zoom-in duration-300">
|
||||
<h2 class="text-2xl font-bold mb-2">Create API Key</h2>
|
||||
<p class="text-sm text-slate-500 mb-8">Set up a new access point for your application.</p>
|
||||
<form id="create-key-form" class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest text-slate-500 mb-2">Key Name</label>
|
||||
<input type="text" id="new-key-name" placeholder="e.g. Production Web App" required
|
||||
class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 pt-4 border-t border-slate-800">
|
||||
<button type="button" onclick="app.toggleModal('create-key-modal', false)" class="flex-1 btn btn-secondary">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" id="btn-submit-key" class="flex-1 btn btn-primary">
|
||||
Create Key
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- core script -->
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/auth.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/playground.js"></script>
|
||||
<script src="js/analytics.js"></script>
|
||||
<script src="js/billing.js"></script>
|
||||
<script src="js/docs.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+239
-348
@@ -3,372 +3,263 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jordan Map Platform | Developer Dashboard</title>
|
||||
<!-- Tailwind CSS Play CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- MapLibre GL JS -->
|
||||
<script src="https://unpkg.com/maplibre-gl@5.1.1/dist/maplibre-gl.js"></script>
|
||||
<link href="https://unpkg.com/maplibre-gl@5.1.1/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
slate: {
|
||||
950: '#0a0a0b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<title>Intaleq Maps | The Premium Google Maps Alternative</title>
|
||||
<script src="js/lib/tailwind.min.js"></script>
|
||||
<script src="js/lib/lucide.min.js"></script>
|
||||
<script src="js/lib/maplibre-gl.js"></script>
|
||||
<link href="css/lib/maplibre-gl.css" rel="stylesheet" />
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background-color: #0a0a0b;
|
||||
color: #f8fafc;
|
||||
/* Local Font Definitions */
|
||||
@font-face {
|
||||
font-family: 'Plus Jakarta Sans';
|
||||
src: url('fonts/pjs-regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Plus Jakarta Sans';
|
||||
src: url('fonts/pjs-bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cairo';
|
||||
src: url('fonts/cairo-regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cairo';
|
||||
src: url('fonts/cairo-bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply flex items-center gap-2 px-4 py-2.5 rounded-xl font-bold transition-all duration-300 active:scale-95 text-sm;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 text-white hover:bg-blue-500 shadow-lg shadow-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-slate-900 text-slate-400 hover:text-white border border-slate-800 hover:border-slate-700;
|
||||
}
|
||||
|
||||
/* Nav logic */
|
||||
.page-section {
|
||||
display: none;
|
||||
}
|
||||
.page-section.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Sidebar active state */
|
||||
.nav-link.active {
|
||||
@apply bg-blue-600/10 text-blue-400 border-r-2 border-blue-600;
|
||||
}
|
||||
body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #050505; color: #fff; }
|
||||
.glass { background: rgba(20, 20, 25, 0.7); backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.05); }
|
||||
.text-gradient { background: linear-gradient(135deg, #fff 0%, #94a3b8 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||
.blue-gradient { background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%); }
|
||||
.feature-card:hover { transform: translateY(-5px); border-color: rgba(37, 99, 235, 0.3); }
|
||||
.glow { box-shadow: 0 0 50px -10px rgba(37, 99, 235, 0.2); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="overflow-hidden h-screen flex">
|
||||
<body class="overflow-x-hidden selection:bg-blue-500/30">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 border-r border-slate-800 bg-slate-950/50 backdrop-blur-xl flex flex-col z-50">
|
||||
<div class="p-8">
|
||||
<div class="flex items-center gap-3 group cursor-pointer">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-blue-600 to-cyan-400 flex items-center justify-center text-white shadow-lg shadow-blue-500/20 group-hover:rotate-12 transition-transform">
|
||||
<i data-lucide="layers" class="w-6 h-6"></i>
|
||||
<!-- Nav -->
|
||||
<nav class="fixed top-0 w-full z-50 border-b border-white/5 bg-black/50 backdrop-blur-xl">
|
||||
<div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl blue-gradient flex items-center justify-center shadow-lg shadow-blue-500/20">
|
||||
<i data-lucide="layers" class="w-6 h-6 text-white text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="font-black text-lg tracking-tight leading-none">Intaleq</h1>
|
||||
<span class="text-[10px] uppercase tracking-[0.2em] font-bold text-slate-500">Maps SaaS</span>
|
||||
<span class="font-black text-xl tracking-tight">Intaleq <span class="text-blue-500">Maps</span></span>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center gap-8 text-sm font-bold text-slate-400">
|
||||
<a href="#features" class="hover:text-white transition-colors" data-i18n="nav-features">Features</a>
|
||||
<a href="#comparison" class="hover:text-white transition-colors" data-i18n="nav-why">Why Us?</a>
|
||||
<a href="#pricing" class="hover:text-white transition-colors" data-i18n="nav-pricing">Pricing</a>
|
||||
<button onclick="i18n.toggle()" class="flex items-center gap-2 px-3 py-1.5 rounded-lg border border-white/10 hover:bg-white/5 transition-all" data-i18n="lang-toggle">
|
||||
العربية
|
||||
</button>
|
||||
</div>
|
||||
<a href="dashboard.html" class="px-6 py-2.5 rounded-xl bg-white text-black font-black text-sm hover:scale-105 transition-all" data-i18n="nav-launch">Launch Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative pt-40 pb-20 px-6 overflow-hidden">
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-blue-600/10 blur-[120px] rounded-full pointer-events-none"></div>
|
||||
<div class="max-w-5xl mx-auto text-center relative z-10">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400 text-xs font-black uppercase tracking-widest mb-8" data-i18n="hero-badge">
|
||||
<i data-lucide="sparkles" class="w-3.5 h-3.5"></i> Now with 3D Buildings in Jordan & Syria
|
||||
</div>
|
||||
<h1 class="text-6xl md:text-8xl font-black tracking-tighter mb-8 leading-[0.9] text-gradient" data-i18n="hero-title">
|
||||
The Map API That <br> <span class="text-blue-500">Doesn't Break</span> The Bank.
|
||||
</h1>
|
||||
<p class="text-xl text-slate-400 max-w-2xl mx-auto mb-12 font-medium leading-relaxed" data-i18n="hero-desc">
|
||||
Build premium location-based apps with high-fidelity vector tiles, optimized routing, and 3D buildings. 85% cheaper than Google Maps.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a href="dashboard.html" class="w-full sm:w-auto px-8 py-4 rounded-2xl blue-gradient text-white font-black text-lg shadow-2xl shadow-blue-500/30 hover:scale-105 transition-all flex items-center justify-center gap-3" data-i18n="hero-cta-start">
|
||||
Start Building Free <i data-lucide="arrow-right" class="w-5 h-5"></i>
|
||||
</a>
|
||||
<a href="#comparison" class="w-full sm:w-auto px-8 py-4 rounded-2xl glass text-white font-black text-lg hover:bg-white/5 transition-all text-center" data-i18n="hero-cta-view">
|
||||
View Comparison
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="px-6 pb-40">
|
||||
<div class="max-w-6xl mx-auto glass rounded-[3rem] p-4 glow relative overflow-hidden group">
|
||||
<div id="hero-map" class="rounded-[2rem] overflow-hidden bg-slate-900 aspect-video relative">
|
||||
<!-- Live Map Context -->
|
||||
</div>
|
||||
<!-- Overlay Info -->
|
||||
<div class="absolute bottom-12 left-12 max-w-md pointer-events-none">
|
||||
<div class="glass p-6 rounded-3xl border-blue-500/20 backdrop-blur-md">
|
||||
<h3 class="text-xl font-black mb-2 flex items-center gap-2 italic">
|
||||
<i data-lucide="box" class="text-blue-400 w-5 h-5"></i> REAL-TIME 3D
|
||||
</h3>
|
||||
<p class="text-sm text-slate-400 font-medium">Render every skyscraper in Amman and every street in Damascus with sub-meter precision and beautiful 3D extrusions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<nav class="flex-1 px-4 space-y-2 mt-4">
|
||||
<a href="#home" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-home">
|
||||
<i data-lucide="layout-dashboard" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
<a href="#playground" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-playground">
|
||||
<i data-lucide="terminal" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
Playground
|
||||
</a>
|
||||
<a href="#analytics" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-analytics">
|
||||
<i data-lucide="bar-chart-3" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
Analytics
|
||||
</a>
|
||||
<a href="#billing" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-billing">
|
||||
<i data-lucide="credit-card" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
Billing
|
||||
</a>
|
||||
<a href="#docs" class="nav-link flex items-center gap-3 px-4 py-3 rounded-xl transition-all hover:bg-white/5 text-slate-400 font-bold group" id="nav-docs">
|
||||
<i data-lucide="book-open" class="w-5 h-5 group-hover:text-white transition-colors"></i>
|
||||
Documentation
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="glass p-5 rounded-2xl border border-blue-500/10 bg-gradient-to-br from-blue-600/5 to-transparent">
|
||||
<p class="text-[10px] font-black uppercase tracking-wider text-blue-400 mb-2">Beta Access</p>
|
||||
<p class="text-xs text-slate-400 leading-relaxed font-medium mb-4">You're currently on the free sandbox tier.</p>
|
||||
<button class="w-full btn btn-primary !py-2 !text-xs">Upgrade Plan</button>
|
||||
<!-- Comparison Table -->
|
||||
<section id="comparison" class="py-24 px-6 bg-white/[0.01]">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl font-black mb-4">Numbers Don't Lie.</h2>
|
||||
<p class="text-slate-400 font-medium">Switch from Google Maps and save thousands every month.</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col min-w-0 bg-gradient-to-br from-slate-950 via-[#0f1115] to-slate-950 overflow-y-auto">
|
||||
<!-- Header -->
|
||||
<header class="h-20 border-b border-white/[0.03] flex items-center justify-between px-8 sticky top-0 bg-slate-950/50 backdrop-blur-md z-40">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div>
|
||||
<span class="text-xs font-black uppercase tracking-widest text-slate-500">System Operational</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex flex-col items-end">
|
||||
<p class="text-sm font-bold" id="tenant-name">Loading...</p>
|
||||
<p class="text-[10px] text-slate-500 font-medium" id="tenant-email">developer@intaleq.com</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-full bg-slate-900 border border-slate-800 flex items-center justify-center text-blue-400">
|
||||
<i data-lucide="user" class="w-5 h-5"></i>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="p-12 max-w-7xl mx-auto w-full">
|
||||
|
||||
<!-- Home Section -->
|
||||
<section id="home" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2" id="welcome-msg">Welcome back...</h2>
|
||||
<p class="text-slate-400">Everything you need to build with premium Jordan Map Platform API</p>
|
||||
<div class="glass rounded-[2.5rem] overflow-hidden">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="border-b border-white/5 bg-blue-600/5">
|
||||
<th class="px-8 py-6 text-sm font-black uppercase tracking-widest text-slate-400">Feature</th>
|
||||
<th class="px-8 py-6 text-sm font-black uppercase tracking-widest text-slate-400 text-center">Google Maps</th>
|
||||
<th class="px-8 py-6 text-sm font-black uppercase tracking-widest text-blue-400 text-center bg-blue-500/10">Intaleq Maps</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/5">
|
||||
<tr>
|
||||
<td class="px-8 py-6 font-bold">50,000 API Requests</td>
|
||||
<td class="px-8 py-6 text-center text-red-400 font-medium">$350 / mo</td>
|
||||
<td class="px-8 py-6 text-center text-emerald-400 font-black bg-blue-500/5">$40 / mo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-8 py-6 font-bold">Map Tiles (Vector)</td>
|
||||
<td class="px-8 py-6 text-center text-slate-400">Charged per load</td>
|
||||
<td class="px-8 py-6 text-center text-white font-black bg-blue-500/5">UNLIMITED / FREE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-8 py-6 font-bold">3D Buildings Data</td>
|
||||
<td class="px-8 py-6 text-center text-slate-400">Limited in Levant</td>
|
||||
<td class="px-8 py-6 text-center text-white font-black bg-blue-500/5">Full Coverage (Jordan/Syria)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-8 py-6 font-bold">Bill Transparency</td>
|
||||
<td class="px-8 py-6 text-center text-slate-400">Hidden Costs</td>
|
||||
<td class="px-8 py-6 text-center text-white font-black bg-blue-500/5">Fixed Pricing</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-8 text-center text-xs text-slate-500 font-bold uppercase tracking-widest leading-loose">
|
||||
* Based on public pricing as of April 2026. Savings calculated on equivalent request volume.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section id="features" class="py-40 px-6">
|
||||
<div class="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div class="glass p-10 rounded-[2.5rem] feature-card transition-all duration-300">
|
||||
<div class="w-14 h-14 rounded-2xl bg-blue-500/10 flex items-center justify-center text-blue-400 mb-8">
|
||||
<i data-lucide="zap" class="w-7 h-7"></i>
|
||||
</div>
|
||||
|
||||
<!-- KPI Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
<div class="glass p-6 rounded-2xl group border-[#1e293b]">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-blue-400">
|
||||
<i data-lucide="zap" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-emerald-500">+12.5%</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1">Total Requests</p>
|
||||
<div class="text-2xl font-bold tracking-tight">42,891</div>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl group">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-emerald-400">
|
||||
<i data-lucide="shield-check" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-emerald-500">+0.01%</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1">Success Rate</p>
|
||||
<div class="text-2xl font-bold tracking-tight">99.98%</div>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl group">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-cyan-400">
|
||||
<i data-lucide="activity" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-slate-500">Stable</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1">Active Keys</p>
|
||||
<div class="text-2xl font-bold tracking-tight" id="active-keys-count">...</div>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl group">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-900 flex items-center justify-center text-violet-400">
|
||||
<i data-lucide="trending-up" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-slate-900 text-slate-500">-12ms</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 font-medium mb-1">Map Load Speed</p>
|
||||
<div class="text-2xl font-bold tracking-tight">342ms</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-black mb-4" data-i18n="feat-latency-title">Zero Latency</h3>
|
||||
<p class="text-slate-400 font-medium leading-relaxed" data-i18n="feat-latency-desc">Our infrastructure is optimized for MENA region, ensuring map tiles load in under 200ms anywhere in Amman or Damascus.</p>
|
||||
</div>
|
||||
<div class="glass p-10 rounded-[2.5rem] feature-card transition-all duration-300">
|
||||
<div class="w-14 h-14 rounded-2xl bg-cyan-500/10 flex items-center justify-center text-cyan-400 mb-8">
|
||||
<i data-lucide="navigation" class="w-7 h-7"></i>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
||||
<!-- Traffic Mockup -->
|
||||
<div class="lg:col-span-2 glass rounded-2xl p-6 relative overflow-hidden">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">Request Traffic</h3>
|
||||
<p class="text-sm text-slate-500">Live traffic across all API endpoints</p>
|
||||
</div>
|
||||
<a href="#analytics" class="text-xs text-blue-500 hover:text-blue-400 font-bold flex items-center gap-1 transition-colors">
|
||||
Full Analytics <i data-lucide="arrow-right" class="w-3.5 h-3.5"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="h-64 flex items-end gap-2 px-2 relative" id="traffic-bars">
|
||||
<!-- Bars injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="glass rounded-2xl p-6 bg-gradient-to-br from-blue-600/10 to-transparent border-blue-500/10">
|
||||
<h3 class="text-lg font-bold mb-2">Quick Start</h3>
|
||||
<p class="text-sm text-slate-500 mb-6 font-medium">Get started with our lightweight SDK in seconds.</p>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-slate-950 rounded-xl p-4 border border-slate-800 font-mono text-xs">
|
||||
<p class="text-slate-500 mb-2"># Install with npm</p>
|
||||
<p class="text-blue-400">npm <span class="text-slate-200">install @intaleq/maps-gl</span></p>
|
||||
</div>
|
||||
<button class="w-full btn btn-secondary text-sm group" onclick="window.open('/api/docs', '_blank')">
|
||||
<i data-lucide="terminal" class="w-4 h-4 text-blue-400 group-hover:scale-110 transition-transform"></i>
|
||||
View API Reference
|
||||
</button>
|
||||
<a href="#playground" class="w-full btn btn-secondary text-sm group">
|
||||
<i data-lucide="globe" class="w-4 h-4 text-cyan-400 group-hover:scale-110 transition-transform"></i>
|
||||
Try Maps Playground
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-black mb-4" data-i18n="feat-routing-title">Smart Routing</h3>
|
||||
<p class="text-slate-400 font-medium leading-relaxed" data-i18n="feat-routing-desc">Enterprise-grade routing engine with support for alternative paths, traffic awareness, and custom road constraints.</p>
|
||||
</div>
|
||||
<div class="glass p-10 rounded-[2.5rem] feature-card transition-all duration-300">
|
||||
<div class="w-14 h-14 rounded-2xl bg-violet-500/10 flex items-center justify-center text-violet-400 mb-8">
|
||||
<i data-lucide="shield-check" class="w-7 h-7"></i>
|
||||
</div>
|
||||
|
||||
<!-- API Keys Table -->
|
||||
<div class="glass rounded-2xl overflow-hidden mb-12">
|
||||
<div class="p-6 border-b border-slate-800 flex items-center justify-between bg-white/[0.02]">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">Your API Keys</h3>
|
||||
<p class="text-sm text-slate-500">Manage keys for your applications</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="app.toggleModal('create-key-modal', true)" id="create-key-btn">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
Create New Key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto min-h-[200px]">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="text-xs uppercase tracking-widest text-slate-500 bg-slate-900/40">
|
||||
<th class="px-6 py-4 font-black">Name</th>
|
||||
<th class="px-6 py-4 font-black">API Key</th>
|
||||
<th class="px-6 py-4 font-black">Status</th>
|
||||
<th class="px-6 py-4 font-black">Restrictions</th>
|
||||
<th class="px-6 py-4 font-black text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="keys-table-body" class="divide-y divide-slate-800/50">
|
||||
<!-- Injected by JS -->
|
||||
<tr>
|
||||
<td colspan="5" class="py-20 text-center text-slate-500">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<i data-lucide="loader-2" class="w-8 h-8 animate-spin text-blue-500"></i>
|
||||
<p>Fetching your secure keys...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Playground Section -->
|
||||
<section id="playground" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2">Maps Playground</h2>
|
||||
<p class="text-slate-400">Test your API keys and visualize vector tiles in real-time</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- Sidebar Controls -->
|
||||
<div class="lg:col-span-1 space-y-6">
|
||||
<div class="glass p-6 rounded-2xl">
|
||||
<h4 class="text-[10px] uppercase font-black tracking-widest text-slate-500 mb-4">Configuration</h4>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-xs text-slate-400 mb-2 block">Active API Key</label>
|
||||
<select id="pg-key-select" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-slate-400 mb-2 block">Map Style</label>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="playground.setStyle('obsidian')" id="style-obsidian" class="flex-1 py-3 text-xs font-bold rounded-xl bg-blue-600 text-white shadow-lg shadow-blue-500/20">Obsidian</button>
|
||||
<button onclick="playground.setStyle('light')" id="style-light" class="flex-1 py-3 text-xs font-bold rounded-xl bg-slate-900 text-slate-500">Light</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Container -->
|
||||
<div class="lg:col-span-3 glass rounded-3xl overflow-hidden relative" style="height: 600px;">
|
||||
<div id="map" class="absolute inset-0"></div>
|
||||
<!-- Search Overlay -->
|
||||
<div class="absolute top-6 left-6 w-full max-w-sm">
|
||||
<div class="relative">
|
||||
<i data-lucide="search" class="absolute left-4 top-1/2 -translate-y-1/2 text-slate-500 w-4 h-4"></i>
|
||||
<input type="text" id="pg-search" placeholder="Search Amman, Jordan..."
|
||||
class="w-full bg-slate-950/80 backdrop-blur-md border border-slate-800 rounded-2xl px-12 py-4 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50 shadow-2xl">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Analytics Section -->
|
||||
<section id="analytics" class="page-section">
|
||||
<div class="mb-12">
|
||||
<h2 class="text-4xl text-gradient mb-2">Analytics</h2>
|
||||
<p class="text-slate-400">Deep insights into your API performance</p>
|
||||
</div>
|
||||
<!-- Simple Chart Mockups -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div class="glass p-8 rounded-3xl">
|
||||
<h3 class="text-xl font-bold mb-8">Request Volume (Last 7 Days)</h3>
|
||||
<div class="h-80 w-full flex items-end justify-between gap-4 px-4" id="v-chart"></div>
|
||||
</div>
|
||||
<div class="glass p-8 rounded-3xl">
|
||||
<h3 class="text-xl font-bold mb-8">Service Latency (ms)</h3>
|
||||
<div class="h-80 w-full flex items-end justify-between gap-4 px-4" id="l-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<div id="create-key-modal" class="fixed inset-0 z-[100] hidden">
|
||||
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="app.toggleModal('create-key-modal', false)"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center p-4">
|
||||
<div class="glass w-full max-w-md p-8 rounded-3xl animate-in fade-in zoom-in duration-300">
|
||||
<h2 class="text-2xl font-bold mb-2">Create API Key</h2>
|
||||
<p class="text-sm text-slate-500 mb-8">Set up a new access point for your application.</p>
|
||||
<form id="create-key-form" class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-xs font-black uppercase tracking-widest text-slate-500 mb-2">Key Name</label>
|
||||
<input type="text" id="new-key-name" placeholder="e.g. Production Web App" required
|
||||
class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20">
|
||||
</div>
|
||||
<div class="flex gap-4 pt-4">
|
||||
<button type="button" class="flex-1 btn btn-secondary" onclick="app.toggleModal('create-key-modal', false)">Cancel</button>
|
||||
<button type="submit" class="flex-1 btn btn-primary" id="btn-submit-key">Create Key</button>
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="text-2xl font-black mb-4" data-i18n="feat-geocoding-title">Local Geocoding</h3>
|
||||
<p class="text-slate-400 font-medium leading-relaxed" data-i18n="feat-geocoding-desc">Highly accurate search for local landmarks, neighborhoods, and buildings often missing from global providers.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- core script -->
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/playground.js"></script>
|
||||
<script src="js/analytics.js"></script>
|
||||
<!-- Pricing (Simplified) -->
|
||||
<section id="pricing" class="py-24 px-6 relative">
|
||||
<div class="max-w-3xl mx-auto glass p-12 rounded-[3rem] text-center border-blue-500/30 overflow-hidden">
|
||||
<div class="absolute -top-24 -right-24 w-64 h-64 bg-blue-500/10 blur-[80px] rounded-full"></div>
|
||||
<h2 class="text-4xl font-black mb-4">Simple, Transparent Pricing.</h2>
|
||||
<div class="my-10">
|
||||
<h4 class="text-6xl font-black text-blue-500">$40<span class="text-xl text-slate-500 font-bold tracking-normal italic"> / 50k requests</span></h4>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-10 text-slate-400 font-bold">
|
||||
<li>Everything in Free Tier included</li>
|
||||
<li>Commercial License for Fleet Tracking</li>
|
||||
<li>Premium 3D Map Tiles (Unlimited)</li>
|
||||
<li>24/7 Priority Support</li>
|
||||
</ul>
|
||||
<a href="dashboard.html#billing" class="inline-flex btn px-12 py-4 rounded-2xl blue-gradient text-white font-black text-lg shadow-xl shadow-blue-500/20 hover:scale-105 transition-all">Get Professional Access</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="py-20 border-t border-white/5 text-center">
|
||||
<div class="flex items-center justify-center gap-3 mb-8">
|
||||
<div class="w-8 h-8 rounded-lg blue-gradient flex items-center justify-center text-white">
|
||||
<i data-lucide="layers" class="w-5 h-5 text-white"></i>
|
||||
</div>
|
||||
<span class="font-black text-lg">Intaleq <span class="text-blue-500">Maps</span></span>
|
||||
</div>
|
||||
<p class="text-slate-500 text-sm font-bold tracking-widest uppercase">Intaleq Software Solutions © 2026</p>
|
||||
</footer>
|
||||
|
||||
<script src="js/i18n.js"></script>
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
|
||||
// Initialize Cinematic Hero Map
|
||||
try {
|
||||
const map = new maplibregl.Map({
|
||||
container: 'hero-map',
|
||||
style: '/api/maps/styles/obsidian', // Using our local premium style
|
||||
center: [35.9285, 31.9454], // Amman
|
||||
zoom: 14,
|
||||
pitch: 60,
|
||||
bearing: -20,
|
||||
interactive: false,
|
||||
antialias: true
|
||||
});
|
||||
|
||||
// Smooth rotation for cinematic feel
|
||||
let angle = -20;
|
||||
function rotate() {
|
||||
angle += 0.05;
|
||||
map.setBearing(angle % 360);
|
||||
requestAnimationFrame(rotate);
|
||||
}
|
||||
|
||||
map.on('load', () => {
|
||||
rotate();
|
||||
// Add 3D building layer if available in style
|
||||
if (map.getSource('openmaptiles')) {
|
||||
const layers = map.getStyle().layers;
|
||||
const labelLayerId = layers.find(l => l.type === 'symbol' && l.layout['text-field'])?.id;
|
||||
|
||||
map.addLayer({
|
||||
'id': '3d-buildings',
|
||||
'source': 'openmaptiles',
|
||||
'source-layer': 'building',
|
||||
'type': 'fill-extrusion',
|
||||
'minzoom': 15,
|
||||
'paint': {
|
||||
'fill-extrusion-color': '#334155',
|
||||
'fill-extrusion-height': ['get', 'render_height'],
|
||||
'fill-extrusion-base': ['get', 'render_min_height'],
|
||||
'fill-extrusion-opacity': 0.6
|
||||
}
|
||||
}, labelLayerId);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Hero map initialization skipped (offline/style error)');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+110
-22
@@ -13,50 +13,138 @@ const analytics = {
|
||||
{ name: 'Sun', requests: 3490, latency: 225 },
|
||||
],
|
||||
|
||||
init: () => {
|
||||
console.log('📈 Initializing Analytics...');
|
||||
analytics.renderVolumeChart();
|
||||
analytics.renderLatencyChart();
|
||||
init: async () => {
|
||||
console.log('📈 Initializing Live Analytics...');
|
||||
const headers = auth.getAuthHeader();
|
||||
|
||||
// 1. Fetch Summary for Success Ratio
|
||||
try {
|
||||
const summaryRes = await fetch('/api/usage/summary', { headers });
|
||||
if (summaryRes.ok) {
|
||||
const summary = await summaryRes.json();
|
||||
analytics.renderSuccessRatio(summary.successRate);
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
|
||||
// 2. Fetch History for Volume Chart
|
||||
const history = await analytics.fetchHistory();
|
||||
if (history && history.length > 0) {
|
||||
analytics.renderVolumeChart(history);
|
||||
} else {
|
||||
analytics.renderPlaceholder();
|
||||
}
|
||||
|
||||
analytics.renderLatencyChart();
|
||||
},
|
||||
|
||||
renderVolumeChart: () => {
|
||||
renderSuccessRatio: (percent) => {
|
||||
const circle = document.getElementById('success-circle');
|
||||
const display = document.getElementById('success-percent-display');
|
||||
if (!circle || !display) return;
|
||||
|
||||
display.textContent = `${percent}%`;
|
||||
|
||||
// Circumference is 552.9 (2 * PI * 88)
|
||||
const offset = 552.9 - (percent / 100) * 552.9;
|
||||
circle.style.strokeDashoffset = offset;
|
||||
circle.classList.toggle('text-emerald-500', percent >= 95);
|
||||
circle.classList.toggle('text-blue-600', percent < 95);
|
||||
},
|
||||
|
||||
fetchHistory: async () => {
|
||||
try {
|
||||
const headers = auth.getAuthHeader();
|
||||
const res = await fetch('/api/usage/history?days=7', { headers });
|
||||
if (res.ok) {
|
||||
const raw = await res.json();
|
||||
// Map raw database records to chart data
|
||||
return raw.map(item => {
|
||||
const date = new Date(item.date);
|
||||
return {
|
||||
name: date.toLocaleDateString('en-US', { weekday: 'short' }),
|
||||
requests: parseInt(item.count, 10)
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch analytics history', error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
renderVolumeChart: (data) => {
|
||||
const container = document.getElementById('v-chart');
|
||||
if (!container) return;
|
||||
|
||||
const max = Math.max(...analytics.mockData.map(d => d.requests));
|
||||
const max = Math.max(...data.map(d => d.requests), 1);
|
||||
|
||||
container.innerHTML = analytics.mockData.map(d => `
|
||||
<div class="flex-1 flex flex-col items-center gap-2 group">
|
||||
<div class="w-full bg-blue-600/20 rounded-t-lg relative overflow-hidden flex items-end" style="height: 100%">
|
||||
<div class="w-full bg-gradient-to-t from-blue-600 to-blue-400 rounded-t-lg transition-all duration-700 hover:brightness-125"
|
||||
style="height: ${(d.requests / max) * 100}%">
|
||||
container.innerHTML = data.map((d, i) => `
|
||||
<div class="flex-1 flex flex-col items-center gap-4 group h-full">
|
||||
<div class="flex-1 w-full bg-slate-900/40 rounded-2xl relative overflow-hidden flex items-end p-1 border border-white/[0.03] backdrop-blur-sm">
|
||||
<div class="w-full bg-gradient-to-t from-blue-600 via-blue-500 to-cyan-400 rounded-xl transition-all duration-1000 ease-out hover:brightness-125 shadow-[0_0_30px_rgba(37,99,235,0.2)]"
|
||||
style="height: 0%; transition-delay: ${i * 50}ms">
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.group h-full div[style*="height: 0%"]')[0].style.height = "${(d.requests / max) * 100}%";
|
||||
}, 100);
|
||||
</script>
|
||||
</div>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span class="text-[10px] font-bold bg-slate-900 px-2 py-1 rounded border border-slate-800">${d.requests}</span>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-all transform translate-y-2 group-hover:translate-y-0 z-10">
|
||||
<span class="text-[10px] font-black bg-blue-600 text-white px-3 py-1.5 rounded-lg shadow-2xl border border-blue-400/30 mb-2">${d.requests.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold text-slate-500">${d.name}</span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-widest">${d.name}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Trigger animations after render
|
||||
setTimeout(() => {
|
||||
container.querySelectorAll('.w-full.bg-gradient-to-t').forEach((el, index) => {
|
||||
const height = el.parentElement.parentElement.dataset.height;
|
||||
el.style.height = el.getAttribute('data-target-height');
|
||||
});
|
||||
}, 100);
|
||||
},
|
||||
|
||||
renderPlaceholder: () => {
|
||||
const container = document.getElementById('v-chart');
|
||||
if (container) container.innerHTML = `
|
||||
<div class="w-full h-full flex flex-col items-center justify-center text-slate-500 gap-4">
|
||||
<div class="w-16 h-16 rounded-full bg-slate-900 flex items-center justify-center opacity-50">
|
||||
<i data-lucide="bar-chart" class="w-8 h-8"></i>
|
||||
</div>
|
||||
<p class="text-xs italic font-bold tracking-widest uppercase opacity-40">No activity recorded for this period</p>
|
||||
</div>`;
|
||||
if (window.lucide) lucide.createIcons();
|
||||
},
|
||||
|
||||
renderLatencyChart: () => {
|
||||
const container = document.getElementById('l-chart');
|
||||
if (!container) return;
|
||||
|
||||
const max = Math.max(...analytics.mockData.map(d => d.latency));
|
||||
const mockLatency = [
|
||||
{ name: 'Mon', latency: 240 },
|
||||
{ name: 'Tue', latency: 198 },
|
||||
{ name: 'Wed', latency: 310 },
|
||||
{ name: 'Thu', latency: 208 },
|
||||
{ name: 'Fri', latency: 250 },
|
||||
{ name: 'Sat', latency: 210 },
|
||||
{ name: 'Sun', latency: 225 },
|
||||
];
|
||||
|
||||
const max = Math.max(...mockLatency.map(d => d.latency));
|
||||
|
||||
container.innerHTML = analytics.mockData.map(d => `
|
||||
<div class="flex-1 flex flex-col items-center gap-2 group">
|
||||
<div class="w-full bg-violet-600/10 rounded-t-lg relative overflow-hidden flex items-end" style="height: 100%">
|
||||
<div class="w-full bg-gradient-to-t from-violet-600 to-violet-400 rounded-t-lg transition-all duration-700 hover:brightness-125"
|
||||
container.innerHTML = mockLatency.map((d, i) => `
|
||||
<div class="flex-1 flex flex-col items-center gap-4 group h-full">
|
||||
<div class="flex-1 w-full bg-slate-900/40 rounded-2xl relative overflow-hidden flex items-end p-1 border border-white/[0.03] backdrop-blur-sm">
|
||||
<div class="w-full bg-gradient-to-t from-violet-600 via-violet-500 to-fuchsia-400 rounded-xl transition-all duration-1000 ease-out hover:brightness-125 shadow-[0_0_30px_rgba(139,92,246,0.2)]"
|
||||
style="height: ${(d.latency / max) * 100}%">
|
||||
</div>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span class="text-[10px] font-bold bg-slate-900 px-2 py-1 rounded border border-slate-800">${d.latency}ms</span>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-all transform translate-y-2 group-hover:translate-y-0 z-10">
|
||||
<span class="text-[10px] font-black bg-violet-600 text-white px-3 py-1.5 rounded-lg shadow-2xl border border-violet-400/30 mb-2">${d.latency}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold text-slate-500">${d.name}</span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-widest">${d.name}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
+87
-23
@@ -11,13 +11,18 @@ const app = {
|
||||
},
|
||||
|
||||
init: async () => {
|
||||
console.log('🚀 Dashboard Initializing...');
|
||||
console.log('🚀 Dashboard Initializing Components...');
|
||||
app.bindEvents();
|
||||
app.handleRouting();
|
||||
await app.fetchData();
|
||||
lucide.createIcons();
|
||||
},
|
||||
|
||||
onAuthenticated: async () => {
|
||||
console.log('🔑 User Authenticated, Fetching Data...');
|
||||
await app.fetchData();
|
||||
app.updateStats();
|
||||
},
|
||||
|
||||
bindEvents: () => {
|
||||
window.addEventListener('hashchange', app.handleRouting);
|
||||
|
||||
@@ -50,25 +55,31 @@ const app = {
|
||||
playground.init(app.state.keys);
|
||||
} else if (hash === 'analytics') {
|
||||
analytics.init();
|
||||
} else if (hash === 'billing') {
|
||||
billing.init();
|
||||
} else if (hash === 'refinement') {
|
||||
refinement.init();
|
||||
} else if (hash === 'docs') {
|
||||
docs.init();
|
||||
}
|
||||
},
|
||||
|
||||
fetchData: async () => {
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
// Fetch Tenant
|
||||
const tenantRes = await fetch('/api/auth/management/me');
|
||||
const tenantRes = await fetch('/api/auth/management/me', { headers });
|
||||
if (tenantRes.ok) {
|
||||
app.state.tenant = await tenantRes.data || await tenantRes.json();
|
||||
app.state.tenant = await tenantRes.json();
|
||||
app.updateHeader();
|
||||
}
|
||||
|
||||
// Fetch Keys
|
||||
if (app.state.tenant && app.state.tenant.id) {
|
||||
const keysRes = await fetch(`/api/auth/management/keys/${app.state.tenant.id}`);
|
||||
if (app.state.tenant) {
|
||||
const keysRes = await fetch('/api/auth/management/keys', { headers });
|
||||
if (keysRes.ok) {
|
||||
app.state.keys = await keysRes.json();
|
||||
app.renderKeysTable();
|
||||
app.updateStats();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -80,21 +91,73 @@ const app = {
|
||||
if (!app.state.tenant) return;
|
||||
document.getElementById('tenant-name').textContent = app.state.tenant.name;
|
||||
document.getElementById('tenant-email').textContent = app.state.tenant.email;
|
||||
document.getElementById('welcome-msg').textContent = `Welcome back, ${app.state.tenant.name}`;
|
||||
document.getElementById('welcome-msg').textContent = `Welcome back, ${app.state.tenant.name.split(' ')[0]}`;
|
||||
|
||||
// Role-based UI visibility
|
||||
if (app.state.tenant.role === 'ADMIN') {
|
||||
const auditLink = document.getElementById('nav-refinement');
|
||||
if (auditLink) auditLink.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Update user avatar if photoUrl exists
|
||||
if (app.state.tenant.photoUrl) {
|
||||
const avatarContainer = document.querySelector('.w-10.h-10.rounded-full.bg-slate-900');
|
||||
if (avatarContainer) {
|
||||
avatarContainer.innerHTML = `<img src="${app.state.tenant.photoUrl}" class="w-full h-full rounded-full object-cover border border-blue-500/20" alt="Profile">`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateStats: () => {
|
||||
document.getElementById('active-keys-count').textContent = app.state.keys.length;
|
||||
updateStats: async () => {
|
||||
try {
|
||||
const headers = auth.getAuthHeader();
|
||||
const res = await fetch('/api/usage/summary', { headers });
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
|
||||
// Update KPI Cards
|
||||
document.getElementById('active-keys-count').textContent = app.state.keys.length;
|
||||
|
||||
// Detailed Stats from getUsageSummary
|
||||
if (document.getElementById('stat-total-req')) {
|
||||
document.getElementById('stat-total-req').textContent = data.monthlyUsage.toLocaleString();
|
||||
}
|
||||
if (document.getElementById('stat-success-percent')) {
|
||||
document.getElementById('stat-success-percent').textContent = `${data.successRate}%`;
|
||||
document.getElementById('stat-success-rate').textContent = data.successRate >= 95 ? 'Excellent' : 'Stable';
|
||||
}
|
||||
if (document.getElementById('stat-avg-latency')) {
|
||||
document.getElementById('stat-avg-latency').textContent = `${data.avgLatency}ms`;
|
||||
document.getElementById('stat-latency-val').textContent = `-${Math.round(data.avgLatency * 0.1)}ms`;
|
||||
}
|
||||
|
||||
// Update Progress bar & Quota card
|
||||
const bar = document.getElementById('usage-progress-bar');
|
||||
const label = document.getElementById('usage-percentage-label');
|
||||
const limitText = document.getElementById('usage-limit-text');
|
||||
|
||||
if (bar) bar.style.width = `${data.percentage}%`;
|
||||
if (label) label.textContent = `${data.percentage}% USED`;
|
||||
if (limitText) {
|
||||
const remaining = Math.max(0, data.limit - data.monthlyUsage);
|
||||
limitText.textContent = `${remaining.toLocaleString()} requests left`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update stats', error);
|
||||
}
|
||||
|
||||
// Inject random bars for traffic
|
||||
// Inject sample bars for visual flair if chart not ready
|
||||
const container = document.getElementById('traffic-bars');
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
const values = [40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 40, 60, 55, 80, 70, 45, 90];
|
||||
values.forEach(h => {
|
||||
// High-density bars for a tech/premium feel
|
||||
const values = [40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 50, 65, 80, 70, 30];
|
||||
values.forEach((h, index) => {
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'flex-1 bg-gradient-to-t from-blue-600/20 to-blue-400/80 rounded-t-sm relative group hover:to-blue-300 transition-all';
|
||||
bar.className = 'flex-1 bg-gradient-to-t from-blue-600/10 to-blue-400/60 rounded-t-[2px] relative group hover:to-blue-300 transition-all duration-500';
|
||||
bar.style.height = `${h}%`;
|
||||
bar.style.transitionDelay = `${index * 20}ms`;
|
||||
bar.innerHTML = `<div class="absolute -top-10 left-1/2 -translate-x-1/2 glass px-2 py-1 rounded text-[10px] font-bold opacity-0 group-hover:opacity-100 transition-opacity z-10">${h}k</div>`;
|
||||
container.appendChild(bar);
|
||||
});
|
||||
@@ -112,12 +175,10 @@ const app = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable create button if limit reached (per React logic)
|
||||
if (app.state.keys.length >= 1) {
|
||||
createBtn.disabled = true;
|
||||
createBtn.title = "Limit of 1 API key per developer reached";
|
||||
createBtn.innerHTML = '<i data-lucide="shield-alert" class="w-4 h-4"></i> Limit Reached';
|
||||
}
|
||||
// Enable create button (removed previous limit restrictiveness)
|
||||
createBtn.disabled = false;
|
||||
createBtn.title = "Create a new API key";
|
||||
createBtn.innerHTML = '<i data-lucide="plus" class="w-4 h-4"></i> Create Key';
|
||||
|
||||
tbody.innerHTML = app.state.keys.map(key => {
|
||||
const isVisible = app.state.showKeys[key.id];
|
||||
@@ -195,9 +256,12 @@ const app = {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Creating...';
|
||||
|
||||
const res = await fetch(`/api/auth/management/keys/${app.state.tenant.id}`, {
|
||||
const res = await fetch('/api/auth/management/keys', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...auth.getAuthHeader()
|
||||
},
|
||||
body: JSON.stringify({ name, rateLimit: 100 })
|
||||
});
|
||||
|
||||
@@ -217,5 +281,5 @@ const app = {
|
||||
}
|
||||
};
|
||||
|
||||
// Start app
|
||||
// Start app components
|
||||
document.addEventListener('DOMContentLoaded', app.init);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Firebase Authentication Logic for Intaleq Dashboard
|
||||
*/
|
||||
|
||||
const auth = {
|
||||
firebaseAuth: null,
|
||||
currentUser: null,
|
||||
idToken: null,
|
||||
|
||||
init: () => {
|
||||
console.log('🔐 Initializing Auth Module...');
|
||||
|
||||
// 1. Initialize Firebase
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
auth.firebaseAuth = firebase.auth();
|
||||
|
||||
// 2. Listen for Auth Changes
|
||||
auth.firebaseAuth.onAuthStateChanged(async (user) => {
|
||||
if (user) {
|
||||
console.log('✅ User logged in:', user.email);
|
||||
auth.currentUser = user;
|
||||
auth.idToken = await user.getIdToken();
|
||||
|
||||
// Show Dashboard, Hide Login
|
||||
auth.toggleUI(true);
|
||||
|
||||
// Initialize main app data
|
||||
app.onAuthenticated();
|
||||
} else {
|
||||
console.log('❌ No active session.');
|
||||
auth.currentUser = null;
|
||||
auth.idToken = null;
|
||||
|
||||
// Show Login, Hide Dashboard
|
||||
auth.toggleUI(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
signInWithGoogle: async () => {
|
||||
const provider = new firebase.auth.GoogleAuthProvider();
|
||||
try {
|
||||
await auth.firebaseAuth.signInWithPopup(provider);
|
||||
} catch (error) {
|
||||
console.error('Sign-in error:', error);
|
||||
alert('Failed to sign in. Please try again.');
|
||||
}
|
||||
},
|
||||
|
||||
signOut: async () => {
|
||||
try {
|
||||
await auth.firebaseAuth.signOut();
|
||||
} catch (error) {
|
||||
console.error('Sign-out error:', error);
|
||||
}
|
||||
},
|
||||
|
||||
toggleUI: (isAuthenticated) => {
|
||||
const loginSection = document.getElementById('login-section');
|
||||
const mainSidebar = document.getElementById('main-sidebar');
|
||||
const mainContent = document.getElementById('main-content');
|
||||
|
||||
if (isAuthenticated) {
|
||||
if (loginSection) loginSection.classList.add('hidden');
|
||||
if (mainSidebar) mainSidebar.classList.remove('hidden');
|
||||
if (mainContent) mainContent.classList.remove('hidden');
|
||||
} else {
|
||||
if (loginSection) loginSection.classList.remove('hidden');
|
||||
if (mainSidebar) mainSidebar.classList.add('hidden');
|
||||
if (mainContent) mainContent.classList.add('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
getAuthHeader: () => {
|
||||
return auth.idToken ? { 'Authorization': `Bearer ${auth.idToken}` } : {};
|
||||
}
|
||||
};
|
||||
|
||||
// Start Auth on Load
|
||||
document.addEventListener('DOMContentLoaded', auth.init);
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Billing & Subscription Logic
|
||||
*/
|
||||
|
||||
const billing = {
|
||||
state: {
|
||||
subscription: null,
|
||||
invoices: []
|
||||
},
|
||||
|
||||
init: async () => {
|
||||
console.log('💳 Initializing Billing...');
|
||||
await billing.fetchSubscription();
|
||||
await billing.fetchInvoices();
|
||||
billing.renderUI();
|
||||
},
|
||||
|
||||
fetchSubscription: async () => {
|
||||
try {
|
||||
const headers = auth.getAuthHeader();
|
||||
const res = await fetch('/api/billing/subscription', { headers });
|
||||
if (res.ok) {
|
||||
billing.state.subscription = await res.json();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch subscription', error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchInvoices: async () => {
|
||||
try {
|
||||
const headers = auth.getAuthHeader();
|
||||
const res = await fetch('/api/billing/invoices', { headers });
|
||||
if (res.ok) {
|
||||
billing.state.invoices = await res.json();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch invoices', error);
|
||||
}
|
||||
},
|
||||
|
||||
renderUI: () => {
|
||||
const sub = billing.state.subscription;
|
||||
if (!sub) return;
|
||||
|
||||
// 1. Update Current Plan Badges
|
||||
document.querySelectorAll('.current-plan-name').forEach(el => el.textContent = sub.plan);
|
||||
|
||||
// 2. Render Plan Cards logic
|
||||
const plans = ['FREE', 'PRO', 'ENTERPRISE'];
|
||||
plans.forEach(p => {
|
||||
const card = document.getElementById(`plan-card-${p.toLowerCase()}`);
|
||||
if (card) {
|
||||
const btn = card.querySelector('.plan-btn');
|
||||
if (p === sub.plan) {
|
||||
card.classList.add('border-blue-500/50');
|
||||
if (btn) {
|
||||
btn.textContent = 'Current Plan';
|
||||
btn.disabled = true;
|
||||
btn.classList.add('opacity-50');
|
||||
}
|
||||
} else {
|
||||
card.classList.remove('border-blue-500/50');
|
||||
if (btn) {
|
||||
btn.textContent = p === 'ENTERPRISE' ? 'Contact Sales' : 'Upgrade Now';
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('opacity-50');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Render Invoice History
|
||||
const tbody = document.getElementById('invoice-table-body');
|
||||
if (tbody) {
|
||||
if (billing.state.invoices.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="py-10 text-center text-slate-500 text-xs">No transactions yet.</td></tr>`;
|
||||
} else {
|
||||
tbody.innerHTML = billing.state.invoices.map(inv => `
|
||||
<tr class="border-b border-white/[0.02] hover:bg-white/[0.01]">
|
||||
<td class="py-4 text-xs font-medium">${new Date(inv.createdAt).toLocaleDateString()}</td>
|
||||
<td class="py-4 text-xs font-bold">$${inv.amount}</td>
|
||||
<td class="py-4 text-xs">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800 text-[10px] uppercase font-black">${inv.provider}</span>
|
||||
</td>
|
||||
<td class="py-4 text-xs">
|
||||
<span class="status-badge ${inv.status.toLowerCase()}">${inv.status}</span>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
startCheckout: async (plan, provider) => {
|
||||
try {
|
||||
const btn = event.target;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Processing...';
|
||||
btn.disabled = true;
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...auth.getAuthHeader()
|
||||
};
|
||||
|
||||
const res = await fetch('/api/billing/checkout', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ plan, provider })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.checkoutUrl) {
|
||||
window.location.href = data.checkoutUrl;
|
||||
}
|
||||
} else {
|
||||
alert('Checkout failed. Please try again.');
|
||||
}
|
||||
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
} catch (error) {
|
||||
console.error('Checkout error', error);
|
||||
alert('An error occurred during checkout.');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Documentation Engine for Intaleq Dashboard
|
||||
*/
|
||||
|
||||
const docs = {
|
||||
init: () => {
|
||||
console.log('📚 Initializing Documentation...');
|
||||
docs.bindEvents();
|
||||
docs.renderSection('getting-started');
|
||||
},
|
||||
|
||||
bindEvents: () => {
|
||||
// Handle side-nav clicks
|
||||
document.querySelectorAll('.docs-nav-link').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const section = e.currentTarget.getAttribute('data-section');
|
||||
docs.renderSection(section);
|
||||
|
||||
// Active state
|
||||
document.querySelectorAll('.docs-nav-link').forEach(l => l.classList.remove('active', 'bg-blue-500/10', 'text-blue-400'));
|
||||
e.currentTarget.classList.add('active', 'bg-blue-500/10', 'text-blue-400');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
renderSection: (id) => {
|
||||
const container = document.getElementById('docs-content');
|
||||
if (!container) return;
|
||||
|
||||
// Content repository
|
||||
const content = {
|
||||
'getting-started': `
|
||||
<div class="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div>
|
||||
<h3 class="text-3xl font-black mb-4">Getting Started</h3>
|
||||
<p class="text-slate-400 leading-relaxed">Welcome to the Intaleq Map Platform. Our APIs allow you to integrate high-quality vector maps, geocoding, and routing into your web and mobile applications with ease.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="glass p-6 rounded-2xl border-white/5">
|
||||
<h4 class="font-bold mb-2 flex items-center gap-2">
|
||||
<i data-lucide="key" class="w-4 h-4 text-blue-400"></i>
|
||||
1. Get an API Key
|
||||
</h4>
|
||||
<p class="text-xs text-slate-500">Go to the Credentials page and create your first API key.</p>
|
||||
</div>
|
||||
<div class="glass p-6 rounded-2xl border-white/5">
|
||||
<h4 class="font-bold mb-2 flex items-center gap-2">
|
||||
<i data-lucide="code" class="w-4 h-4 text-emerald-400"></i>
|
||||
2. Install SDK
|
||||
</h4>
|
||||
<p class="text-xs text-slate-500">Use our MapLibre wrappers for JavaScript or Flutter.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-lg font-bold">Base URL</h4>
|
||||
<div class="bg-slate-900 rounded-xl p-4 font-mono text-sm border border-slate-800 text-blue-400">
|
||||
https://map-dashbord.intaleqapp.com/api
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
'tiles-api': `
|
||||
<div class="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div>
|
||||
<h3 class="text-3xl font-black mb-2">Vector Tiles API</h3>
|
||||
<p class="text-slate-400">Render high-performance vector maps from our global database.</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="endpoint-card glass rounded-2xl border-white/5 overflow-hidden">
|
||||
<div class="p-4 bg-white/[0.02] border-b border-white/5 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-2 py-1 bg-green-500/20 text-green-400 text-[10px] font-black rounded uppercase">GET</span>
|
||||
<code class="text-xs font-bold">/maps/style.json</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-sm text-slate-400 mb-6">Returns the MapLibre-compatible style configuration. Use the <code>theme</code> parameter to switch between 'light' and 'obsidian'.</p>
|
||||
|
||||
<h5 class="text-xs font-black uppercase tracking-widest text-slate-500 mb-4">Code Example</h5>
|
||||
<div class="relative group">
|
||||
<pre class="bg-slate-950 p-4 rounded-xl text-xs font-mono text-slate-300 leading-relaxed overflow-x-auto">
|
||||
// Initialize MapLibre with Intaleq Style
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://map-dashbord.intaleqapp.com/api/maps/style.json?theme=obsidian',
|
||||
center: [35.9106, 31.9539], // Amman
|
||||
zoom: 12
|
||||
});</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
'geocoding-api': `
|
||||
<div class="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div>
|
||||
<h3 class="text-3xl font-black mb-2">Geocoding API</h3>
|
||||
<p class="text-slate-400">Convert addresses to coordinates (Forward) or coordinates to addresses (Reverse).</p>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-card glass rounded-2xl border-white/5 overflow-hidden">
|
||||
<div class="p-4 bg-white/[0.02] border-b border-white/5 flex items-center gap-3">
|
||||
<span class="px-2 py-1 bg-green-500/20 text-green-400 text-[10px] font-black rounded uppercase">GET</span>
|
||||
<code class="text-xs font-bold">/geocoding/search</code>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<table class="w-full text-left text-xs mb-6">
|
||||
<thead>
|
||||
<tr class="text-slate-500 uppercase tracking-widest font-black border-b border-white/5">
|
||||
<th class="pb-3">Parameter</th>
|
||||
<th class="pb-3">Type</th>
|
||||
<th class="pb-3">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/[0.02]">
|
||||
<tr>
|
||||
<td class="py-3 font-bold text-blue-400 font-mono">q</td>
|
||||
<td class="py-3 text-slate-500">string</td>
|
||||
<td class="py-3">Search query (address, place, coordinates)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-3 font-bold text-blue-400 font-mono">limit</td>
|
||||
<td class="py-3 text-slate-500">number</td>
|
||||
<td class="py-3">Max results (default: 5)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5 class="text-xs font-black uppercase tracking-widest text-slate-500 mb-4">Request</h5>
|
||||
<pre class="bg-slate-950 p-4 rounded-xl text-xs font-mono text-blue-400 mb-4">curl "https://map-dashbord.intaleqapp.com/api/geocoding/search?q=Amman&limit=1" \\
|
||||
-H "x-api-key: YOUR_API_KEY"</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
container.innerHTML = content[id] || '<p class="text-slate-500">Documentation section coming soon...</p>';
|
||||
lucide.createIcons();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyAwybkM9FFkF2KRM0bFNAaEPNPbMZBWFn8",
|
||||
authDomain: "intaleq-map.firebaseapp.com",
|
||||
projectId: "intaleq-map",
|
||||
storageBucket: "intaleq-map.firebasestorage.app",
|
||||
messagingSenderId: "1052695318500",
|
||||
appId: "1:1052695318500:web:7913981536524a630401f1",
|
||||
measurementId: "G-P5Z2YXH481"
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* i18n Translation Engine
|
||||
*/
|
||||
|
||||
const i18n = {
|
||||
currentLang: localStorage.getItem('intaleq_lang') || 'en',
|
||||
|
||||
translations: {
|
||||
en: {
|
||||
// Navbar
|
||||
'nav-features': 'Features',
|
||||
'nav-why': 'Why Us?',
|
||||
'nav-pricing': 'Pricing',
|
||||
'nav-launch': 'Launch Dashboard',
|
||||
'lang-toggle': 'العربية',
|
||||
|
||||
// Hero
|
||||
'hero-badge': 'Now with 3D Buildings in Jordan & Syria',
|
||||
'hero-title': 'The Map API That <br> <span class="text-blue-500">Doesn\'t Break</span> The Bank.',
|
||||
'hero-desc': 'Build premium location-based apps with high-fidelity vector tiles, optimized routing, and 3D buildings. 85% cheaper than Google Maps.',
|
||||
'hero-cta-start': 'Start Building Free',
|
||||
'hero-cta-view': 'View Comparison',
|
||||
|
||||
// Features
|
||||
'feat-latency-title': 'Zero Latency',
|
||||
'feat-latency-desc': 'Our infrastructure is optimized for MENA region, ensuring map tiles load in under 200ms.',
|
||||
'feat-routing-title': 'Smart Routing',
|
||||
'feat-routing-desc': 'Enterprise-grade routing engine with support for alternative paths and traffic awareness.',
|
||||
'feat-geocoding-title': 'Local Geocoding',
|
||||
'feat-geocoding-desc': 'Highly accurate search for local landmarks and neighborhoods in Jordan & Syria.',
|
||||
|
||||
// Dashboard Sidebar
|
||||
'side-dashboard': 'Dashboard',
|
||||
'side-playground': 'Playground',
|
||||
'side-analytics': 'Analytics',
|
||||
'side-billing': 'Billing',
|
||||
'side-audit': 'Place Audit',
|
||||
'side-docs': 'Documentation',
|
||||
'side-upgrade': 'Upgrade Plan',
|
||||
|
||||
// Dashboard General
|
||||
'welcome': 'Welcome back',
|
||||
'system-status': 'System Operational',
|
||||
'quota-card-title': 'Monthly Quota',
|
||||
'requests-left': 'requests left',
|
||||
'used-label': 'USED',
|
||||
|
||||
// KPI Labels
|
||||
'kpi-total-req': 'Total Requests',
|
||||
'kpi-success-rate': 'Success Rate',
|
||||
'kpi-active-keys': 'Active Keys',
|
||||
'kpi-latency': 'Avg Latency',
|
||||
},
|
||||
ar: {
|
||||
// Navbar
|
||||
'nav-features': 'المميزات',
|
||||
'nav-why': 'لماذا نحن؟',
|
||||
'nav-pricing': 'الأسعار',
|
||||
'nav-launch': 'لوحة التحكم',
|
||||
'lang-toggle': 'English',
|
||||
|
||||
// Hero
|
||||
'hero-badge': 'الآن مع المباني ثلاثية الأبعاد في الأردن وسوريا',
|
||||
'hero-title': 'واجهة خرائط برمجية <br> <span class="text-blue-500">لا ترهق</span> ميزانيتك.',
|
||||
'hero-desc': 'ابنِ تطبيقات خرائط فاخرة مع خرائط مجهزة، توجيه ذكي، ومباني ثلاثية الأبعاد. أوفر بنسبة 85% من خرائط جوجل.',
|
||||
'hero-cta-start': 'ابدأ مجاناً',
|
||||
'hero-cta-view': 'قارن الأسعار',
|
||||
|
||||
// Features
|
||||
'feat-latency-title': 'سرعة فائقة',
|
||||
'feat-latency-desc': 'بنيتنا التحتية محسنة لمنطقة الشرق الأوسط، مما يضمن تحميل الخرائط في أقل من 200 مللي ثانية.',
|
||||
'feat-routing-title': 'توجيه ذكي',
|
||||
'feat-routing-desc': 'محرك توجيه من الفئة المؤسسية يدعم المسارات البديلة والوعي بحركة المرور.',
|
||||
'feat-geocoding-title': 'بحث مكاني محلي',
|
||||
'feat-geocoding-desc': 'دقة عالية جداً في البحث عن المعالم والأحياء في الأردن وسوريا.',
|
||||
|
||||
// Dashboard Sidebar
|
||||
'side-dashboard': 'لوحة التحكم',
|
||||
'side-playground': 'ساحة الاختبار',
|
||||
'side-analytics': 'التحليلات',
|
||||
'side-billing': 'الفواتير',
|
||||
'side-audit': 'تدقيق الأماكن',
|
||||
'side-docs': 'التوثيق',
|
||||
'side-upgrade': 'ترقية الخطة',
|
||||
|
||||
// Dashboard General
|
||||
'welcome': 'مرحباً بك مجدداً',
|
||||
'system-status': 'النظام يعمل بكفاءة',
|
||||
'quota-card-title': 'رصيد الاستهلاك',
|
||||
'requests-left': 'طلب متبقي',
|
||||
'used-label': 'مستهلك',
|
||||
|
||||
// KPI Labels
|
||||
'kpi-total-req': 'إجمالي الطلبات',
|
||||
'kpi-success-rate': 'نسبة النجاح',
|
||||
'kpi-active-keys': 'المفاتيح النشطة',
|
||||
'kpi-latency': 'متوسط سرعة الاستجابة',
|
||||
}
|
||||
},
|
||||
|
||||
init: () => {
|
||||
i18n.apply(i18n.currentLang);
|
||||
},
|
||||
|
||||
toggle: () => {
|
||||
const nextLang = i18n.currentLang === 'en' ? 'ar' : 'en';
|
||||
i18n.currentLang = nextLang;
|
||||
localStorage.setItem('intaleq_lang', nextLang);
|
||||
i18n.apply(nextLang);
|
||||
},
|
||||
|
||||
apply: (lang) => {
|
||||
const rtl = lang === 'ar';
|
||||
document.documentElement.dir = rtl ? 'rtl' : 'ltr';
|
||||
document.documentElement.lang = lang;
|
||||
|
||||
// Apply font classes
|
||||
if (rtl) {
|
||||
document.body.style.fontFamily = "'Cairo', sans-serif";
|
||||
document.body.classList.add('rtl-mode');
|
||||
} else {
|
||||
document.body.style.fontFamily = "'Plus Jakarta Sans', sans-serif";
|
||||
document.body.classList.remove('rtl-mode');
|
||||
}
|
||||
|
||||
// Mirror Sidebar if in dashboard
|
||||
const sidebar = document.getElementById('main-sidebar');
|
||||
if (sidebar) {
|
||||
if (rtl) {
|
||||
sidebar.classList.add('order-last', 'border-l', 'border-r-0');
|
||||
sidebar.classList.remove('border-r');
|
||||
} else {
|
||||
sidebar.classList.remove('order-last', 'border-l');
|
||||
sidebar.classList.add('border-r');
|
||||
}
|
||||
}
|
||||
|
||||
// Translate elements with data-i18n attribute
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n');
|
||||
const translation = i18n.translations[lang][key];
|
||||
if (translation) {
|
||||
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
|
||||
el.placeholder = translation;
|
||||
} else {
|
||||
el.innerHTML = translation;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger icon re-render if lucide is present
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on load
|
||||
document.addEventListener('DOMContentLoaded', i18n.init);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+12
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+83
File diff suppressed because one or more lines are too long
@@ -14,6 +14,13 @@ const playground = {
|
||||
playground.renderKeySelect(keys);
|
||||
playground.bindEvents();
|
||||
|
||||
// Watch for section changes to trigger resize
|
||||
window.addEventListener('hashchange', () => {
|
||||
if (window.location.hash === '#playground' && playground.map) {
|
||||
setTimeout(() => playground.map.resize(), 100);
|
||||
}
|
||||
});
|
||||
|
||||
if (keys.length > 0) {
|
||||
playground.selectedKey = keys[0].key;
|
||||
playground.loadMap();
|
||||
@@ -53,30 +60,55 @@ const playground = {
|
||||
try {
|
||||
// Set RTL Text Plugin
|
||||
if (maplibregl.getRTLTextPluginStatus() === 'unavailable') {
|
||||
console.log('🌐 Loading MapLibre RTL Plugin...');
|
||||
maplibregl.setRTLTextPlugin(
|
||||
'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.js',
|
||||
'js/plugins/mapbox-gl-rtl-text.js',
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`🛰️ Fetching Map Style for theme: ${playground.currentStyle}...`);
|
||||
const styleUrl = `/api/maps/style.json?api_key=${playground.selectedKey}&theme=${playground.currentStyle}`;
|
||||
const res = await fetch(styleUrl);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Style fetch failed with status: ${res.status}`);
|
||||
}
|
||||
|
||||
const styleData = await res.json();
|
||||
console.log('🎨 Style fetched successfully, initializing MapLibre...');
|
||||
|
||||
playground.map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: styleData,
|
||||
center: [35.91, 31.95], // Amman, Jordan
|
||||
zoom: 12,
|
||||
attributionControl: false
|
||||
attributionControl: false,
|
||||
trackResize: true
|
||||
});
|
||||
|
||||
playground.map.addControl(new maplibregl.NavigationControl(), 'top-right');
|
||||
|
||||
playground.map.on('load', () => {
|
||||
console.log('Map loaded!');
|
||||
playground.map.resize();
|
||||
console.log('✅ Map engine ready and tiles loading!');
|
||||
// Wait slightly for container animation to finish
|
||||
setTimeout(() => {
|
||||
playground.map.resize();
|
||||
const container = document.getElementById('map');
|
||||
if (container && container.offsetWidth > 0) {
|
||||
console.log('📏 Map container size verified:', container.offsetWidth, 'x', container.offsetHeight);
|
||||
} else {
|
||||
console.warn('⚠️ Map container has 0 width. Potential visibility issue.');
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
playground.map.on('error', (e) => {
|
||||
console.error('❌ MapLibre Error Detail:', e.error || e);
|
||||
if (e.error && e.error.message.includes('Style')) {
|
||||
alert('Map Style Error: Please verify your API Key and Network connection.');
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Map Refinement (Place Audit) logic
|
||||
*/
|
||||
|
||||
const refinement = {
|
||||
state: {
|
||||
candidates: []
|
||||
},
|
||||
|
||||
init: async () => {
|
||||
console.log('📍 Initializing Map Refinement Logic...');
|
||||
await refinement.fetchCandidates();
|
||||
},
|
||||
|
||||
fetchCandidates: async () => {
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
const res = await fetch('/api/map-refinement/candidates?status=PENDING', { headers });
|
||||
if (res.ok) {
|
||||
refinement.state.candidates = await res.json();
|
||||
refinement.renderTable();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch candidates', error);
|
||||
}
|
||||
},
|
||||
|
||||
renderTable: () => {
|
||||
const tbody = document.getElementById('refinement-table-body');
|
||||
if (!tbody) return;
|
||||
|
||||
if (refinement.state.candidates.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="py-20 text-center text-slate-500">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<i data-lucide="map-pin" class="w-8 h-8 opacity-20"></i>
|
||||
<p>No pending location suggestions.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = refinement.state.candidates.map(c => `
|
||||
<tr class="hover:bg-white/[0.02] transition-colors border-b border-white/[0.03]">
|
||||
<td class="px-8 py-6">
|
||||
<div class="font-bold text-white">${c.name_ar || c.name}</div>
|
||||
<div class="text-[10px] text-slate-500 uppercase font-bold mt-1">${c.country}</div>
|
||||
</td>
|
||||
<td class="px-8 py-6">
|
||||
<span class="px-2 py-1 rounded-lg bg-blue-500/10 text-blue-400 text-[10px] font-black uppercase tracking-wider">${c.category || 'General'}</span>
|
||||
</td>
|
||||
<td class="px-8 py-6 font-mono text-xs text-slate-400">
|
||||
${parseFloat(c.latitude).toFixed(5)}, ${parseFloat(c.longitude).toFixed(5)}
|
||||
</td>
|
||||
<td class="px-8 py-6 text-sm text-slate-400 font-medium">
|
||||
${c.submittedBy || 'System User'}
|
||||
</td>
|
||||
<td class="px-8 py-6 text-right space-x-2">
|
||||
<button onclick="refinement.reject('${c.id}')" class="p-2.5 rounded-xl bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-all" title="Reject">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onclick="refinement.approve('${c.id}')" class="p-2.5 rounded-xl bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20 transition-all border border-emerald-500/20 shadow-lg shadow-emerald-500/10" title="Approve">
|
||||
<i data-lucide="check" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
lucide.createIcons();
|
||||
},
|
||||
|
||||
approve: async (id) => {
|
||||
if (!confirm('Are you sure you want to approve this location and add it to the production map?')) return;
|
||||
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
const res = await fetch(`/api/map-refinement/candidates/${id}/approve`, {
|
||||
method: 'PATCH',
|
||||
headers
|
||||
});
|
||||
if (res.ok) {
|
||||
await refinement.fetchCandidates();
|
||||
} else {
|
||||
alert('Approval failed.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
reject: async (id) => {
|
||||
const reason = prompt('Please enter a reason for rejection:');
|
||||
if (reason === null) return;
|
||||
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
const res = await fetch(`/api/map-refinement/candidates/${id}/reject`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ reason })
|
||||
});
|
||||
if (res.ok) {
|
||||
await refinement.fetchCandidates();
|
||||
} else {
|
||||
alert('Rejection failed.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user