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

873
dewemoji-ux-flow-brief.md Executable file
View File

@@ -0,0 +1,873 @@
# Dewemoji User Flow & UX Brief
**Version:** 1.0
**Date:** February 8, 2026
**Purpose:** Define seamless user experience across visitor → logged → paid states
**Related Doc:** dewemoji-direction-2026.md
---
## Executive Summary
Dewemoji implements a **hybrid quick-add flow** where Personal users can add keywords instantly from emoji detail pages (80% of use cases) while maintaining full CRUD power in the dashboard (20% management). This creates a frictionless progression: discovery → personalization → habit, without disrupting the free user experience.
**Core UX Principle:** *"Add keywords where you discover emojis, manage them where you organize."*
---
## 1. User State Definitions
### 1.1 Visitor (Non-Logged)
**Who:** Anyone landing on dewemoji.com or using Chrome extension (no account)
**Intent:** Quick emoji search and usage
**Experience:** Zero friction, no login walls
### 1.2 Free User (Logged, No Subscription)
**Who:** Registered account, free tier
**Intent:** Exploring Personal features, considering upgrade
**Experience:** Full free features + gentle upgrade hints
### 1.3 Personal User (Logged, Paid)
**Who:** Active Personal subscription ($4.99/mo, $49/yr, or $99 lifetime)
**Intent:** Building and syncing personal keyword library
**Experience:** Full personalization power across all touchpoints
---
## 2. Primary User Flows
### 2.1 Discovery Flow (All Users)
```
User lands on dewemoji.com
Search bar: "bekicot"
Results page: 🐌 snail (public keywords: "snail, escargot")
Click emoji → Detail page
[State-dependent experience - see Section 3]
```
**Key Insight:** All users start here. Free discovery hooks them; Personal lets them own it.
---
### 2.2 Personalization Flow (Personal Users)
#### Primary: Quick Add from Detail Page (80% Usage)
```
Personal user searches "snail"
Detail page shows:
┌─────────────────────────────────────┐
│ 🐌 Snail │
│ │
│ Public Keywords: snail, escargot │
│ │
│ Your Keywords: (none yet) │
│ [+ Add Your Keyword] │
└─────────────────────────────────────┘
User clicks [+ Add Your Keyword]
Inline modal appears:
┌─────────────────────────────────────┐
│ What do you call this emoji? │
│ ┌─────────────────────────────────┐│
│ │ bekicot, siput ││
│ └─────────────────────────────────┘│
│ Language: [Indonesian ▾] │
│ [Cancel] [Save Keyword] │
└─────────────────────────────────────┘
Saves instantly → Toast: "Added 'bekicot' to your library ✓"
Detail page now shows:
┌─────────────────────────────────────┐
│ 🐌 Snail │
│ │
│ Public Keywords: snail, escargot │
│ │
│ Your Keywords: │
│ • bekicot (ID) [edit] [×] │
│ • siput (ID) [edit] [×] │
│ [+ Add Another] │
└─────────────────────────────────────┘
Next search: "bekicot" → 🐌 appears in results!
```
**Why This Flow:**
- **Contextual:** User is *already looking at* the emoji they want to name
- **Fast:** 2 clicks (add button → save), ~5 seconds total
- **Immediate gratification:** See change instantly in searches
- **Low friction:** No navigation away from discovery flow
---
#### Secondary: Dashboard Management (20% Usage)
**When Used:**
- Bulk editing/organizing keywords
- Reviewing all keywords at once
- Exporting/importing keyword libraries
- Fixing typos across multiple entries
```
User navigates to Dashboard → My Keywords tab
Table view:
┌───────────────────────────────────────────────────────────┐
│ Emoji | Your Keywords | Lang | Actions │
├───────────────────────────────────────────────────────────┤
│ 🐌 | bekicot, siput | ID | [Edit] [Delete] │
│ 😊 | senyum, happy | ID | [Edit] [Delete] │
│ 🔥 | api, fire, lit | ID | [Edit] [Delete] │
└───────────────────────────────────────────────────────────┘
[+ Add Keyword] [Import JSON] [Export JSON] [Search/Filter]
User clicks [+ Add Keyword]
Modal: Search emoji picker → Select 🎉 → Add keywords
Saves to table → Auto-sync to extension
```
**Why Dashboard Exists:**
- Power users want bulk operations
- Easy to review/audit entire library
- Import/export for backup
- Edit mistakes without finding emoji again
---
### 2.3 Upgrade Flow (Free → Personal)
#### Trigger Points
**1. On Detail Page (Soft Prompt)**
```
Free user on emoji detail page sees:
┌─────────────────────────────────────┐
│ 🐌 Snail │
│ │
│ Public Keywords: snail, escargot │
│ │
│ 💎 Want to add your own keywords? │
│ like 'bekicot' or 'siput'? │
│ [Upgrade to Personal →] │
└─────────────────────────────────────┘
```
**2. On Search Miss (Strong Prompt)**
```
User searches "bekicot" → No results
Results page shows:
┌─────────────────────────────────────┐
│ No results for "bekicot" │
│ │
│ 💡 With Personal, you can create │
│ "bekicot → 🐌" and sync it │
│ everywhere. │
│ [Try Personal Free for 7 Days] │
└─────────────────────────────────────┘
```
**3. From Extension (Contextual)**
```
Extension user searches "bekicot" → Not found
Extension shows inline banner:
"Add 'bekicot' to your library with Personal"
[Upgrade] button → Opens dewemoji.com/upgrade
```
**4. From Dashboard (Clear Path)**
```
Free user clicks "My Keywords" tab
Shows empty state:
┌─────────────────────────────────────┐
│ 📚 Your Personal Keyword Library │
│ │
│ You have 0 keywords. │
│ │
│ Upgrade to Personal to: │
│ ✓ Add unlimited keywords │
│ ✓ Search in your language │
│ ✓ Sync across all devices │
│ │
│ [Start 7-Day Free Trial] │
└─────────────────────────────────────┘
```
---
## 3. State-Dependent UI Elements
### 3.1 Emoji Detail Page
#### Visitor (Non-Logged)
```html
<!-- Public info only -->
<div class="emoji-detail">
<h1>🐌 Snail</h1>
<section class="public-keywords">
<h3>Keywords</h3>
<ul>
<li>snail</li>
<li>escargot</li>
<li>slow</li>
</ul>
</section>
<div class="cta-banner">
<p>💡 Want to search in your language?</p>
<button>Sign Up Free</button>
</div>
</div>
```
#### Free User (Logged)
```html
<!-- Public info + muted personal section -->
<div class="emoji-detail">
<h1>🐌 Snail</h1>
<section class="public-keywords">
<h3>Keywords</h3>
<ul><li>snail</li><li>escargot</li></ul>
</section>
<section class="user-keywords muted">
<h3>Your Keywords</h3>
<div class="upgrade-overlay">
<p>💎 Add personal keywords like 'bekicot'</p>
<button class="upgrade-btn">Upgrade to Personal</button>
</div>
</section>
</div>
```
#### Personal User (Logged + Paid)
```html
<!-- Full personalization active -->
<div class="emoji-detail">
<h1>🐌 Snail</h1>
<section class="public-keywords">
<h3>Public Keywords</h3>
<ul><li>snail</li><li>escargot</li><li>slow</li></ul>
</section>
<section class="user-keywords active">
<h3>Your Keywords</h3>
<ul class="keyword-list">
<li>
<span class="keyword">bekicot</span>
<span class="lang-tag">ID</span>
<button class="edit-btn">edit</button>
<button class="delete-btn">×</button>
</li>
<li>
<span class="keyword">siput</span>
<span class="lang-tag">ID</span>
<button class="edit-btn">edit</button>
<button class="delete-btn">×</button>
</li>
</ul>
<button class="add-keyword-btn">+ Add Keyword</button>
</section>
</div>
<!-- Add Keyword Modal (hidden until clicked) -->
<dialog id="add-keyword-modal">
<h3>What do you call this emoji?</h3>
<input type="text" placeholder="Enter keywords (comma-separated)" />
<select name="language">
<option value="en">English</option>
<option value="id">Indonesian</option>
<option value="ko">Korean</option>
<option value="ja">Japanese</option>
</select>
<div class="modal-actions">
<button class="cancel">Cancel</button>
<button class="save primary">Save Keyword</button>
</div>
</dialog>
```
---
### 3.2 Search Results Page
#### All Users (Public Results)
```html
<div class="search-results">
<div class="emoji-card">
<span class="emoji">🐌</span>
<span class="name">Snail</span>
<span class="matched-keyword">snail</span>
</div>
</div>
```
#### Personal User (Blended Results)
```html
<div class="search-results">
<!-- Private keyword match (appears first) -->
<div class="emoji-card user-match">
<span class="emoji">🐌</span>
<span class="name">Snail</span>
<span class="matched-keyword">
bekicot
<span class="badge personal">Your keyword</span>
</span>
<button class="quick-edit">Edit</button>
</div>
<!-- Public keyword matches (below) -->
<div class="emoji-card">
<span class="emoji">🐌</span>
<span class="name">Snail</span>
<span class="matched-keyword">snail</span>
</div>
</div>
```
---
### 3.3 Dashboard (Personal Users Only)
```html
<div class="dashboard">
<nav class="tabs">
<button class="active">My Keywords</button>
<button>API Keys</button>
<button>Billing</button>
</nav>
<!-- My Keywords Tab -->
<div class="tab-content" id="keywords">
<div class="toolbar">
<button class="primary">+ Add Keyword</button>
<button>Import JSON</button>
<button>Export JSON</button>
<input type="search" placeholder="Search your keywords..." />
</div>
<table class="keyword-table">
<thead>
<tr>
<th>Emoji</th>
<th>Your Keywords</th>
<th>Language</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>🐌</td>
<td>bekicot, siput</td>
<td><span class="lang-tag">ID</span></td>
<td>
<button class="edit">Edit</button>
<button class="delete">Delete</button>
</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
</div>
</div>
```
---
## 4. API Endpoints for UX Flows
### 4.1 Detail Page Quick Add
**Fetch user's keywords for specific emoji:**
```
GET /v1/emoji/:slug?include_user_keywords=true
Authorization: Bearer dew_abc123...
Response:
{
"emoji": {...},
"public_keywords": ["snail", "escargot"],
"user_keywords": [
{"id": "uk_1", "keyword": "bekicot", "lang": "id"},
{"id": "uk_2", "keyword": "siput", "lang": "id"}
]
}
```
**Add keyword from detail page:**
```
POST /v1/keywords/quick
Authorization: Bearer dew_abc123...
Body:
{
"emoji_slug": "snail",
"keywords": ["bekicot", "siput"],
"lang": "id"
}
Response:
{
"success": true,
"added": ["bekicot", "siput"],
"emoji_slug": "snail"
}
```
**Edit single keyword:**
```
PATCH /v1/keywords/:id
Authorization: Bearer dew_abc123...
Body:
{
"keyword": "bekicot kecil"
}
```
**Delete keyword:**
```
DELETE /v1/keywords/:id
Authorization: Bearer dew_abc123...
```
---
### 4.2 Dashboard Bulk Operations
**List all user keywords:**
```
GET /v1/keywords?page=1&limit=50
Authorization: Bearer dew_abc123...
Response:
{
"keywords": [
{
"id": "uk_1",
"emoji_slug": "snail",
"emoji": "🐌",
"keyword": "bekicot",
"lang": "id",
"created_at": "2026-02-08T10:30:00Z"
}
// ... more
],
"total": 47,
"page": 1
}
```
**Bulk import:**
```
POST /v1/keywords/import
Authorization: Bearer dew_abc123...
Body:
{
"keywords": [
{"emoji_slug": "snail", "keywords": ["bekicot"], "lang": "id"},
{"emoji_slug": "fire", "keywords": ["api"], "lang": "id"}
]
}
Response:
{
"imported": 2,
"skipped": 0,
"errors": []
}
```
**Export:**
```
GET /v1/keywords/export?format=json
Authorization: Bearer dew_abc123...
Response: (Download JSON file)
```
---
## 5. Frontend Implementation Specs
### 5.1 Emoji Detail Page Changes
**File:** `/pages/emoji/[slug].js` (or equivalent)
**Required Elements:**
1. **User keywords section** (Personal only)
- Fetch on page load: `GET /v1/emoji/:slug?include_user_keywords=true`
- Show list of user's keywords with edit/delete buttons
- Show "Add Keyword" button
2. **Add keyword modal**
- Input: comma-separated keywords
- Dropdown: language selector (EN, ID, KO, JA, etc.)
- Save button → POST `/v1/keywords/quick`
- Success → update UI without page reload
3. **Inline edit**
- Click "edit" → input becomes editable
- Save → PATCH `/v1/keywords/:id`
- Cancel → revert
4. **Inline delete**
- Click "×" → confirm modal
- Yes → DELETE `/v1/keywords/:id` → remove from UI
**State Management:**
```javascript
const [userKeywords, setUserKeywords] = useState([]);
const [isPersonal, setIsPersonal] = useState(false);
// On mount
useEffect(() => {
if (user?.tier === 'personal') {
fetchUserKeywords(emojiSlug);
}
}, [emojiSlug, user]);
// Add keyword
const handleAddKeyword = async (keywords, lang) => {
const result = await api.post('/keywords/quick', {
emoji_slug: emojiSlug,
keywords: keywords.split(',').map(k => k.trim()),
lang
});
if (result.success) {
fetchUserKeywords(emojiSlug); // Refresh list
toast.success('Keywords added!');
}
};
```
---
### 5.2 Dashboard Implementation
**File:** `/pages/dashboard.js`
**Tabs:**
1. My Keywords (default)
2. API Keys
3. Billing
**My Keywords Tab Components:**
```javascript
// Main table component
<KeywordTable
keywords={keywords}
onEdit={handleEdit}
onDelete={handleDelete}
onSearch={handleSearch}
/>
// Toolbar actions
<Toolbar>
<AddKeywordButton onClick={openModal} />
<ImportButton />
<ExportButton />
<SearchInput onChange={handleSearch} />
</Toolbar>
// Add keyword modal (with emoji picker)
<AddKeywordModal
onSave={handleAddKeyword}
onCancel={closeModal}
/>
```
**Features:**
- Pagination (50 per page)
- Search/filter by keyword or emoji
- Bulk select + delete
- Sort by: date added, emoji name, language
---
### 5.3 Upgrade Flow Pages
**File:** `/pages/upgrade.js` or `/pages/pricing.js`
**Components:**
1. **Feature comparison table**
```html
<table class="pricing-comparison">
<tr>
<th>Feature</th>
<th>Free</th>
<th>Personal</th>
</tr>
<tr>
<td>Public emoji search</td>
<td>✅ Unlimited</td>
<td>✅ Unlimited</td>
</tr>
<tr>
<td>Private keywords</td>
<td>❌</td>
<td>✅ Unlimited</td>
</tr>
<!-- ... -->
</table>
```
2. **Pricing cards**
```html
<div class="pricing-cards">
<div class="card">
<h3>Monthly</h3>
<p class="price">$4.99/mo</p>
<button>Subscribe</button>
</div>
<div class="card recommended">
<span class="badge">Best Value</span>
<h3>Annual</h3>
<p class="price">$49/year</p>
<p class="savings">Save 17%</p>
<button>Subscribe</button>
</div>
<div class="card">
<h3>Lifetime</h3>
<p class="price">$99 once</p>
<button>Buy Now</button>
</div>
</div>
```
3. **Stripe checkout integration**
```javascript
const handleCheckout = async (plan) => {
const session = await api.post('/stripe/checkout', {
plan, // 'monthly' | 'annual' | 'lifetime'
success_url: `${baseUrl}/dashboard?upgraded=true`,
cancel_url: `${baseUrl}/upgrade`
});
window.location.href = session.url;
};
```
---
## 6. UX Copy & Messaging
### 6.1 Upgrade Prompts
**Soft (Detail Page):**
> 💡 Want to search in your language? Add personal keywords like 'bekicot' for 🐌
> [Upgrade to Personal →]
**Strong (Search Miss):**
> No results for "bekicot"
>
> 💎 **Create your own keywords with Personal**
> Add "bekicot → 🐌" and sync it across all devices
> [Try Free for 7 Days]
**Empty State (Dashboard):**
> 📚 **Your Personal Keyword Library**
>
> You have 0 keywords.
>
> Upgrade to Personal to:
> ✓ Add unlimited keywords
> ✓ Search in your language
> ✓ Sync across all devices
>
> [Start 7-Day Free Trial]
---
### 6.2 Success Messages
**After adding keyword:**
> ✓ Added "bekicot" to your library
**After editing:**
> ✓ Keyword updated
**After deleting:**
> ✓ Keyword removed
**After upgrade:**
> 🎉 Welcome to Personal! Start adding your keywords.
---
### 6.3 Error Messages
**Duplicate keyword:**
> ⚠️ You already have "bekicot" for this emoji
**Network error:**
> ❌ Couldn't save. Check your connection and try again.
**Unauthorized:**
> 🔒 Please log in to add personal keywords
---
## 7. Mobile Considerations
### 7.1 Detail Page on Mobile
- Add keyword button: **sticky bottom bar** (always visible)
- Modal: **full-screen** on mobile (easier typing)
- Keyword list: **swipeable cards** (swipe left to delete)
### 7.2 Dashboard on Mobile
- Tabs: **horizontal scroll** or bottom nav
- Table: **card view** (stack columns vertically)
- Actions: **swipe gestures** (edit/delete)
---
## 8. Performance & Optimization
### 8.1 Caching Strategy
**Client-side:**
- Cache user keywords in localStorage/sessionStorage
- Only refetch when:
- User adds/edits/deletes
- Page reload after 5 minutes
**API-side:**
- ETag support (already implemented)
- Cache user keywords per user_id (Redis)
- Invalidate on mutation
### 8.2 Loading States
**Detail page:**
```
Loading user keywords...
└─ Skeleton: [▯▯▯▯] [▯▯▯] [▯▯▯▯▯]
```
**Dashboard:**
```
Loading your library...
└─ Table skeleton (5 rows)
```
---
## 9. Analytics & Tracking
### Key Events to Track
**Discovery:**
- `emoji_viewed` (slug, user_tier)
- `public_search` (query, has_results)
- `search_miss` (query) → upgrade opportunity
**Personalization:**
- `keyword_added` (method: 'quick_add' | 'dashboard')
- `keyword_edited`
- `keyword_deleted`
- `keywords_exported`
**Conversion:**
- `upgrade_prompt_shown` (location: 'detail' | 'search' | 'dashboard')
- `upgrade_clicked` (location)
- `trial_started`
- `subscription_created` (plan: 'monthly' | 'annual' | 'lifetime')
**Usage:**
- `private_keyword_searched` (query, has_results)
- `extension_synced` (keyword_count)
---
## 10. Testing Checklist
### 10.1 User State Tests
- [ ] Visitor can search and view emoji details (no login)
- [ ] Visitor sees upgrade CTA (non-intrusive)
- [ ] Free user sees muted keyword section
- [ ] Free user can't add keywords without upgrade
- [ ] Personal user sees active keyword section
- [ ] Personal user can add keywords from detail page
- [ ] Personal user can edit/delete keywords inline
### 10.2 Flow Tests
- [ ] Quick add: add keyword → appears in list immediately
- [ ] Quick add: new keyword searchable instantly
- [ ] Dashboard: bulk add works correctly
- [ ] Dashboard: export downloads valid JSON
- [ ] Dashboard: import restores keywords
- [ ] Search: private keywords appear first in results
- [ ] Search: private keywords have "Your keyword" badge
### 10.3 Edge Cases
- [ ] Add duplicate keyword → shows error
- [ ] Delete last keyword for emoji → removes emoji from search
- [ ] Offline: queue mutations, sync when online
- [ ] Rate limit: show friendly error
- [ ] API error: show retry option
---
## 11. Implementation Priority
### Phase 1: Detail Page Quick Add (Week 1)
1. Add user keywords section to detail page
2. Build add keyword modal
3. Implement POST /keywords/quick endpoint
4. Add inline edit/delete
### Phase 2: Search Blending (Week 1)
1. Update search to blend private + public
2. Add "Your keyword" badge to results
3. Show private matches first
### Phase 3: Dashboard (Week 2)
1. Build My Keywords tab with table
2. Add bulk actions (import/export)
3. Implement search/filter
### Phase 4: Upgrade Flow (Week 2)
1. Build upgrade prompts (detail, search miss, dashboard)
2. Create pricing page
3. Integrate Stripe checkout
---
## 12. Open Questions & Future Enhancements
1. **Keyword suggestions:** Should we suggest keywords based on emoji name/category?
2. **Keyboard shortcuts:** Quick add keyword with hotkey (Ctrl+K)?
3. **Recently added:** Show "Recently added keywords" widget on dashboard?
4. **Keyword collections:** Group keywords by category/theme?
5. **Sharing:** Allow users to share their keyword libraries with friends?
---
**End of UX Brief**
This document defines the seamless user experience for Dewemoji's core value proposition: instant, contextual personalization. Implementation should prioritize the detail page quick-add flow (80% of usage) while building dashboard for power users (20%).