Update pricing UX, billing flows, and API rules
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user