Update pricing UX, billing flows, and API rules

This commit is contained in:
Dwindi Ramadhana
2026-02-12 00:52:40 +07:00
parent cf065fab1e
commit a905256353
202 changed files with 22348 additions and 301 deletions

View File

@@ -5,7 +5,7 @@
@push('head')
<style>
.doc-table tbody tr { border-bottom: 1px solid rgba(148,163,184,0.2); }
.doc-table tbody tr { border-bottom: 1px solid var(--muted-border); }
.doc-table tbody tr:hover { background: rgba(32,83,255,0.08); }
.codewrap { position: relative; }
.copy-btn {
@@ -56,15 +56,28 @@
</nav>
</div>
<div class="space-y-1">
@auth
<a href="{{ route('dashboard.overview') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="layout-dashboard" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Dashboard</span></a>
@endauth
@guest
<a href="{{ route('login') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="log-in" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Login</span></a>
@endguest
<a href="{{ route('privacy') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="shield-check" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Privacy</span></a>
<a href="{{ route('terms') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="file-text" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Terms</span></a>
</div>
</aside>
<main class="flex-1 flex flex-col h-full min-w-0 relative z-10">
<header class="glass-header px-6 py-5 shrink-0">
<div class="text-[11px] uppercase tracking-wider text-gray-500 mb-1">Public / Documentation</div>
<h1 class="font-display text-2xl md:text-3xl font-bold">API Documentation</h1>
<header class="glass-header px-6 py-5 shrink-0 flex items-center justify-between">
<div>
<div class="text-[11px] uppercase tracking-wider text-gray-500 mb-1">Public / Documentation</div>
<h1 class="font-display text-2xl md:text-3xl font-bold">API Documentation</h1>
</div>
<button id="theme-toggle" class="w-9 h-9 rounded-full theme-surface border border-white/10 shadow-lg flex items-center justify-center text-gray-300 hover:text-white transition-colors">
<span class="sr-only">Toggle theme</span>
<i data-lucide="moon" class="w-4 h-4" data-theme-icon="dark"></i>
<i data-lucide="sun" class="w-4 h-4 hidden" data-theme-icon="light"></i>
</button>
</header>
<div class="flex-1 overflow-y-auto p-6 md:p-10">
@@ -85,7 +98,7 @@
<section id="overview" class="glass-card rounded-2xl p-6 scroll-mt-28">
<div class="flex items-center gap-2 mb-2">
<h2 class="font-display text-2xl font-bold">Emoji API Docs</h2>
<span id="pro-badge" class="hidden text-[10px] font-semibold px-2 py-1 rounded bg-emerald-500/20 text-emerald-300 border border-emerald-400/40">Pro mode</span>
<span id="api-badge" class="hidden text-[10px] font-semibold px-2 py-1 rounded bg-emerald-500/20 text-emerald-300 border border-emerald-400/40">API key active</span>
</div>
<p class="text-sm text-gray-300">Read-only API with search, categories, and emoji detail. Responses are cacheable and include ETag.</p>
<div class="codewrap mt-4">
@@ -96,20 +109,20 @@
<section id="auth" class="glass-card rounded-2xl p-6 scroll-mt-28">
<h3 class="text-lg font-semibold">Authentication</h3>
<p class="text-sm text-gray-300 mt-2">Free tier is public with daily caps. Pro unlocks higher limits with license key.</p>
<p class="text-sm text-gray-300 mt-2">Public endpoints are open. Personal plan holders can create API keys for private keyword access.</p>
<div class="mt-3 space-y-3">
<div class="codewrap">
<button class="copy-btn" data-copy-target="auth-bearer">Copy</button>
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="auth-bearer">Authorization: Bearer YOUR_LICENSE_KEY</code></pre>
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="auth-bearer">Authorization: Bearer YOUR_API_KEY</code></pre>
</div>
<div class="codewrap">
<button class="copy-btn" data-copy-target="auth-query">Copy</button>
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="auth-query">?key=YOUR_LICENSE_KEY</code></pre>
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="auth-query">X-Api-Key: YOUR_API_KEY</code></pre>
</div>
</div>
<ul class="mt-3 list-disc pl-5 text-sm text-gray-300 space-y-1">
<li><code>Authorization</code> (recommended)</li>
<li><code>X-License-Key</code> (also supported)</li>
<li><code>Authorization</code> or <code>X-Api-Key</code> for Personal API keys</li>
<li><code>X-License-Key</code> or <code>?key=</code> is only used for <code>/license/*</code> endpoints</li>
<li><code>X-Account-Id</code> (optional usage association)</li>
</ul>
</section>
@@ -122,22 +135,25 @@
<tr class="text-left text-gray-400">
<th class="py-2 pr-6">Tier</th>
<th class="py-2 pr-6 text-right">Page size cap</th>
<th class="py-2 pr-6 text-right">Daily cap</th>
<th class="py-2 pr-6">Auth</th>
<th class="py-2 pr-6">Rate limits</th>
<th class="py-2">Notes</th>
</tr>
</thead>
<tbody class="align-top text-gray-200">
<tr>
<td class="py-2 pr-6"><strong>Free</strong></td>
<td class="py-2 pr-6"><strong>Free (public)</strong></td>
<td class="py-2 pr-6 text-right">20</td>
<td class="py-2 pr-6 text-right">~30 (page-1, distinct)</td>
<td class="py-2">Cached responses do not count.</td>
<td class="py-2 pr-6">None</td>
<td class="py-2 pr-6">Hourly public limit (if enabled)</td>
<td class="py-2">Public dataset (EN + ID) only.</td>
</tr>
<tr>
<td class="py-2 pr-6"><strong>Pro</strong></td>
<td class="py-2 pr-6"><strong>Personal</strong></td>
<td class="py-2 pr-6 text-right">50</td>
<td class="py-2 pr-6 text-right">5,000 / day / license</td>
<td class="py-2">Up to 3 Chrome profiles.</td>
<td class="py-2 pr-6">API key</td>
<td class="py-2 pr-6">Unlimited public + private</td>
<td class="py-2">Unlocks private keyword search + sync.</td>
</tr>
</tbody>
</table>
@@ -204,11 +220,11 @@
<section id="try-it" class="glass-card rounded-2xl p-6 scroll-mt-28">
<h3 class="text-lg font-semibold">Try it</h3>
<p class="text-sm text-gray-300 mt-2">Demo request is limited to <strong>page=1</strong> and <strong>limit=10</strong>.</p>
<p id="ti-pro-note" class="hidden text-xs text-emerald-300 mt-2">Using Pro key via Authorization header.</p>
<p id="ti-api-note" class="hidden text-xs text-emerald-300 mt-2">Using API key via Authorization header.</p>
<div class="grid gap-3 md:grid-cols-3 mt-4">
<input id="ti-q" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200" placeholder="keyword (love)">
<input id="ti-category" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200" placeholder="category (Smileys & Emotion)">
<input id="ti-subcategory" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200" placeholder="subcategory (face-smiling)">
<input id="ti-q" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200 theme-surface" placeholder="keyword (love)">
<input id="ti-category" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200 theme-surface" placeholder="category (Smileys & Emotion)">
<input id="ti-subcategory" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200 theme-surface" placeholder="subcategory (face-smiling)">
</div>
<div class="mt-4 flex gap-3">
<button id="ti-run" type="button" class="px-4 py-2 rounded-lg bg-brand-ocean hover:bg-brand-oceanSoft text-white text-sm">Run /emojis</button>
@@ -236,12 +252,12 @@
(() => {
const BASE_API = @json(url('/v1'));
const urlParams = new URLSearchParams(location.search);
const proKey = urlParams.get('key') || '';
const proBadge = document.getElementById('pro-badge');
const proNote = document.getElementById('ti-pro-note');
if (proKey) {
proBadge?.classList.remove('hidden');
proNote?.classList.remove('hidden');
const apiKey = urlParams.get('key') || '';
const apiBadge = document.getElementById('api-badge');
const apiNote = document.getElementById('ti-api-note');
if (apiKey) {
apiBadge?.classList.remove('hidden');
apiNote?.classList.remove('hidden');
}
const baseUrlCode = document.getElementById('base-url-code');
@@ -253,14 +269,14 @@
const lim = 10;
const exCurlEmojis = document.getElementById('ex-curl-emojis');
if (exCurlEmojis) {
exCurlEmojis.textContent = proKey
? `curl -H "Authorization: Bearer ${proKey}" "${BASE_API}/emojis?q=${q}&category=${cat}&limit=${lim}&page=1"`
exCurlEmojis.textContent = apiKey
? `curl -H "Authorization: Bearer ${apiKey}" "${BASE_API}/emojis?q=${q}&category=${cat}&limit=${lim}&page=1"`
: `curl "${BASE_API}/emojis?q=${q}&category=${cat}&limit=${lim}&page=1"`;
}
const exCurlCats = document.getElementById('ex-curl-cats');
if (exCurlCats) {
exCurlCats.textContent = proKey
? `curl -H "Authorization: Bearer ${proKey}" "${BASE_API}/categories"`
exCurlCats.textContent = apiKey
? `curl -H "Authorization: Bearer ${apiKey}" "${BASE_API}/categories"`
: `curl "${BASE_API}/categories"`;
}
const exCurlEmoji = document.getElementById('ex-curl-emoji');
@@ -290,15 +306,15 @@
const resultDrawer = document.createElement('aside');
const backdrop = document.createElement('div');
resultDrawer.id = 'ti-drawer';
resultDrawer.className = 'fixed top-0 right-0 h-full w-full max-w-xl bg-[#0b0b0f] border-l border-white/10 translate-x-full transition-transform z-50';
backdrop.className = 'fixed inset-0 bg-black/40 opacity-0 pointer-events-none transition-opacity z-40';
resultDrawer.className = 'fixed top-0 right-0 h-full w-full max-w-xl border-l translate-x-full transition-transform z-50 offcanvas-panel';
backdrop.className = 'fixed inset-0 opacity-0 pointer-events-none transition-opacity z-40 offcanvas-backdrop';
resultDrawer.innerHTML = `
<div class="flex items-center justify-between p-4 border-b border-white/10">
<h3 class="text-lg font-semibold">Try it Result</h3>
<button id="ti-close" class="p-2 rounded-full text-gray-400 hover:bg-white/5"></button>
</div>
<div class="p-4 h-[calc(100%-64px)] overflow-auto">
<pre class="p-3 rounded bg-black/30 overflow-auto text-xs"><code id="ti-result">{ }</code></pre>
<pre class="p-3 rounded overflow-auto text-xs offcanvas-code"><code id="ti-result">{ }</code></pre>
</div>`;
document.body.appendChild(backdrop);
document.body.appendChild(resultDrawer);
@@ -340,7 +356,7 @@
params.set('page', '1');
const headers = {};
if (proKey) headers['Authorization'] = 'Bearer ' + proKey;
if (apiKey) headers['Authorization'] = 'Bearer ' + apiKey;
try {
const res = await fetch(`${BASE_API}/emojis?` + params.toString(), { headers });
if (!res.ok) throw new Error('Request failed: ' + res.status);
@@ -355,7 +371,7 @@
document.getElementById('ti-cats')?.addEventListener('click', async () => {
const headers = {};
if (proKey) headers['Authorization'] = 'Bearer ' + proKey;
if (apiKey) headers['Authorization'] = 'Bearer ' + apiKey;
try {
const res = await fetch(`${BASE_API}/categories`, { headers });
if (!res.ok) throw new Error('Request failed: ' + res.status);