chore: remove OfferBlock learn more button and change to coming soon

This commit is contained in:
Dwindi Ramadhana
2026-06-13 18:45:52 +07:00
parent 3a475e9df2
commit 6a14eebf25
112 changed files with 34918 additions and 10833 deletions

BIN
._node_modules Executable file

Binary file not shown.

BIN
._package-lock.json generated Executable file

Binary file not shown.

BIN
._package.json Executable file

Binary file not shown.

0
.env Normal file → Executable file
View File

0
.env.example Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

View File

@@ -1,13 +0,0 @@
---
trigger: always_on
---
keep look the issue globally, not narrow. we are done chasing symtomp with narrow sight, we have things to be achieved:
A. main goal: having a working HTML Preview with element inspector and editor feature, and
B. sub goal: implementing the "stable option A DOM Manipulation" properly to reach the main goal (A)
In every reported issue, check if that prevent us to achieved the sub goal. Failing sub goal means fail to reach the main goal. So pivot everything to make a success sub goal, to achieve main goal.
I believe promised sub goal is the way to get succeed on the main goal.
Avoid any looping thought

0
BACKEND_REQUIREMENTS.md Normal file → Executable file
View File

0
DOCUMENTATION_INDEX.md Normal file → Executable file
View File

0
EDITOR_CHECKLIST.md Normal file → Executable file
View File

0
EDITOR_TOOL_GUIDE.md Normal file → Executable file
View File

0
FEATURE_TOGGLE_GUIDE.md Normal file → Executable file
View File

811
PLAN_ACCOUNTS_AND_STORAGE.md Executable file
View File

@@ -0,0 +1,811 @@
# Dewe.Dev — Accounts, File Storage & Pro Features Plan
**Created:** February 18, 2026
---
## Executive Summary
Add user accounts and cloud file storage to dewe.dev without changing the existing UX. Visitors and logged-in users use the exact same tools. The only additions: a login/account page, save/load buttons per tool, and server-side URL fetching for Pro users. Backend powered by self-hosted Supabase on Coolify.
---
## Guiding Principles
1. **Zero UX disruption** — Tools work identically for everyone. Auth is invisible until the user wants it.
2. **Frontend stays king** — All processing remains client-side. The backend is only for storage, auth, and proxy.
3. **Progressive enhancement** — Each feature layer (auth → storage → proxy → billing) can ship independently.
4. **Privacy preserved** — File content is user-owned. RLS enforces isolation. No analytics on file content.
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────┐
│ dewe.dev (React SPA) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
│ │ MD │ │Invoice │ │ Table │ │ Object │ │
│ │ Editor │ │ Editor │ │ Editor │ │ Editor │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ └────────────┴─────┬──────┴──────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ useAuth() context │ │
│ │ useUserFiles() hook │ │
│ └───────────┬───────────┘ │
└──────────────────────────┼──────────────────────────────┘
HTTPS (Supabase JS SDK)
┌──────────────────────────┼──────────────────────────────┐
│ Self-hosted Supabase (Coolify) │
│ │ │
│ ┌───────────┐ ┌────────┴────────┐ ┌───────────────┐ │
│ │ GoTrue │ │ PostgREST │ │ Edge Functions│ │
│ │ (Auth) │ │ (REST API) │ │ (CORS Proxy) │ │
│ └───────────┘ └────────┬────────┘ └───────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ PostgreSQL │ │
│ │ │ │
│ │ • auth.users │ │
│ │ • profiles │ │
│ │ • user_files │ │
│ │ • proxy_logs │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
---
## Phase 1: Supabase Setup on Coolify
**Goal:** Get the backend running and verified before writing any frontend code.
### 1.1 Deploy Supabase on Coolify
Coolify supports Supabase as a one-click service (or via Docker Compose).
**Resource Requirements:**
| Resource | Minimum | Recommended |
|----------|---------|-------------|
| CPU | 2 cores | 4 cores |
| RAM | 4 GB | 8 GB |
| Disk | 20 GB SSD | 50 GB SSD |
**Services that spin up:**
- PostgreSQL (database)
- GoTrue (auth)
- PostgREST (auto-generated REST API)
- Realtime (websockets — can disable if unused)
- Storage API (file storage — optional for this use case)
- Edge Runtime (Deno functions)
- Studio (admin dashboard)
- Kong/Envoy (API gateway)
**Critical Coolify configuration:**
- Map persistent volumes for PostgreSQL data directory — data survives container restarts
- Set up SSL via Coolify's built-in Let's Encrypt
- Configure `SITE_URL` to `https://dewe.dev`
- Set `ADDITIONAL_REDIRECT_URLS` for localhost dev
- Expose only the API gateway port (default 8000) and Studio port (default 3000)
### 1.2 Database Schema
```sql
-- Profiles table (extends auth.users)
create table profiles (
id uuid primary key references auth.users on delete cascade,
display_name text,
tier text not null default 'free' check (tier in ('free', 'pro')),
tier_expires_at timestamptz,
storage_used_bytes bigint default 0,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Auto-create profile on signup
create or replace function handle_new_user()
returns trigger as $$
begin
insert into profiles (id, display_name)
values (new.id, coalesce(new.raw_user_meta_data->>'display_name', split_part(new.email, '@', 1)));
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute function handle_new_user();
-- Sync tier to JWT custom claims (app_metadata)
create or replace function sync_tier_to_claims()
returns trigger as $$
begin
update auth.users
set raw_app_meta_data = raw_app_meta_data || jsonb_build_object('tier', new.tier)
where id = new.id;
return new;
end;
$$ language plpgsql security definer;
create trigger on_tier_change
after update of tier on profiles
for each row execute function sync_tier_to_claims();
-- User files table
create table user_files (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users on delete cascade not null,
tool_type text not null, -- 'markdown', 'invoice', 'object', 'table', 'beautifier'
name text not null,
content jsonb not null, -- The actual file data
metadata jsonb default '{}', -- Optional: last export format, preview text, etc.
size_bytes integer generated always as (octet_length(content::text)) stored,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Indexes
create index idx_user_files_user_tool on user_files(user_id, tool_type);
create index idx_user_files_updated on user_files(user_id, updated_at desc);
-- Proxy usage logging (for rate limiting & analytics)
create table proxy_logs (
id bigserial primary key,
user_id uuid references auth.users not null,
target_url text not null,
method text not null,
status_code integer,
response_size integer,
duration_ms integer,
created_at timestamptz default now()
);
create index idx_proxy_logs_user_time on proxy_logs(user_id, created_at desc);
```
### 1.3 Row Level Security (RLS)
```sql
-- Profiles: users can read/update only their own
alter table profiles enable row level security;
create policy "Users can view own profile"
on profiles for select using (auth.uid() = id);
create policy "Users can update own profile"
on profiles for update using (auth.uid() = id);
-- User files: full CRUD, own files only
alter table user_files enable row level security;
create policy "Users can view own files"
on user_files for select using (auth.uid() = user_id);
create policy "Users can insert own files"
on user_files for insert with check (auth.uid() = user_id);
create policy "Users can update own files"
on user_files for update using (auth.uid() = user_id);
create policy "Users can delete own files"
on user_files for delete using (auth.uid() = user_id);
-- Proxy logs: users can view own logs
alter table proxy_logs enable row level security;
create policy "Users can view own proxy logs"
on proxy_logs for select using (auth.uid() = user_id);
```
### 1.4 Storage Limits
| Tier | Max files | Max file size | Total storage |
|------|-----------|---------------|---------------|
| Free | 20 | 256 KB | 5 MB |
| Pro | Unlimited | 2 MB | 100 MB |
Enforced via database function:
```sql
create or replace function check_storage_limit()
returns trigger as $$
declare
current_count integer;
current_size bigint;
user_tier text;
max_count integer;
max_total bigint;
begin
select tier into user_tier from profiles where id = new.user_id;
if user_tier = 'free' then
max_count := 20;
max_total := 5 * 1024 * 1024; -- 5 MB
else
max_count := 10000;
max_total := 100 * 1024 * 1024; -- 100 MB
end if;
select count(*), coalesce(sum(size_bytes), 0)
into current_count, current_size
from user_files where user_id = new.user_id;
if current_count >= max_count then
raise exception 'File limit reached (% files for % tier)', max_count, user_tier;
end if;
if current_size + octet_length(new.content::text) > max_total then
raise exception 'Storage limit reached (% bytes for % tier)', max_total, user_tier;
end if;
return new;
end;
$$ language plpgsql;
create trigger check_storage_before_insert
before insert on user_files
for each row execute function check_storage_limit();
```
### 1.5 Backups
Self-hosted = you own backups. Set up immediately:
```bash
# Cron job on Coolify host (daily at 3 AM)
0 3 * * * pg_dump -h localhost -U postgres -d postgres | gzip > /backups/dewe_$(date +\%Y\%m\%d).sql.gz
# Retention: keep 30 days
find /backups -name "dewe_*.sql.gz" -mtime +30 -delete
```
Optional: pipe to S3-compatible storage (MinIO on Coolify, or external).
---
## Phase 2: Auth Integration (Frontend)
**Goal:** Add authentication without changing any existing tool UX.
### 2.1 Auth Provider Setup
```
src/
├── contexts/
│ └── AuthContext.js # Supabase auth state
├── hooks/
│ ├── useAuth.js # Auth convenience hook
│ └── useUserFiles.js # File CRUD hook
├── components/
│ ├── AuthModal.js # Login/signup modal (not a page redirect)
│ ├── UserMenu.js # Header avatar/dropdown
│ └── SaveLoadBar.js # Save/Load UI for tools
├── pages/
│ └── AccountPage.js # /account - profile, files, settings
└── lib/
└── supabase.js # Supabase client init
```
### 2.2 "Ghost Auth" Pattern
The core UX principle: **tools never change behavior based on auth state.**
```
┌──────────────────────────────────────────────────┐
│ Tool Editor │
│ │
│ Visitor sees: │ Logged-in user sees: │
│ [Copy] [Download] │ [Copy] [Download] [Save] │
│ │ ↑ │
│ │ only addition │
└──────────────────────────────────────────────────┘
```
**Rules:**
- No redirects to login page from tools. Ever.
- Clicking "Save" when not logged in → opens AuthModal (login/signup) as overlay
- Current editor state is preserved in React state during login
- After successful auth, the save action proceeds automatically
- "Load" button appears in the tool's input section (alongside Create/URL/Paste/Open tabs)
### 2.3 Auth Methods
| Method | Priority | Notes |
|--------|----------|-------|
| Email + Password | P0 | Core auth, must have |
| GitHub OAuth | P1 | Target audience is developers |
| Google OAuth | P2 | Broad reach, nice to have |
| Magic Link (Email) | P2 | Passwordless option |
### 2.4 Header Changes
```
Before (visitor):
┌─────────────────────────────────────────────────┐
│ [Logo] [Tools ▾] [🌙] [☰] │
└─────────────────────────────────────────────────┘
After (visitor):
┌─────────────────────────────────────────────────┐
│ [Logo] [Tools ▾] [🌙] [Sign In] [☰]│
└─────────────────────────────────────────────────┘
After (logged in):
┌─────────────────────────────────────────────────┐
│ [Logo] [Tools ▾] [🌙] [Avatar ▾] [☰]│
└─────────────────────────────────────────────────┘
Avatar dropdown:
├── Account & Files
├── ─────────────
└── Sign Out
```
Minimal. No dashboard. The "Account & Files" link goes to `/account`.
---
## Phase 3: File Save/Load per Tool
**Goal:** Each editor tool gets save/load capability for logged-in users.
### 3.1 Which Tools Support Save/Load
| Tool | Save Content | Content Format | Priority |
|------|-------------|----------------|----------|
| Markdown Editor | MD source text | `{ source: string }` | P0 |
| Invoice Editor | Invoice data | `{ invoice: {...}, template: string }` | P0 |
| Object Editor | JSON/PHP data | `{ input: string, format: string }` | P1 |
| Table Editor | Table data | `{ headers: [...], rows: [...] }` | P1 |
| Beautifier | Code input | `{ code: string, language: string }` | P2 |
| Diff Tool | Both texts | `{ left: string, right: string }` | P2 |
URL Encoder, Base64, and Text Length are **stateless tools** — no save/load needed.
### 3.2 Save/Load UX per Tool
**Save flow:**
1. User works in editor as normal
2. Clicks "Save" → if not logged in, AuthModal opens first
3. Save dialog: enter file name (pre-filled with smart default)
4. Saves to `user_files` via Supabase SDK
5. Toast: "Saved to your account"
**Load flow (new input tab):**
1. Tool input section gets a 5th tab: `[Create] [URL] [Paste] [Open] [My Files]`
2. "My Files" tab shows a list of saved files for this tool type
3. Each entry: name, date, size, [Load] [Delete] buttons
4. Loading replaces current editor content (with unsaved changes warning)
**Smart defaults for file names:**
- Markdown: first heading or "Untitled Document"
- Invoice: "Invoice #{number} - {client_name}"
- Table: "Table - {row_count} rows"
- Object: "Object - {key_count} keys"
### 3.3 `useUserFiles` Hook API
```javascript
const {
files, // File[] for current tool_type
loading, // boolean
saving, // boolean
error, // string | null
saveFile, // (name, content, metadata?) => Promise<File>
updateFile, // (id, content, metadata?) => Promise<File>
deleteFile, // (id) => Promise<void>
loadFiles, // () => Promise<void> (refresh)
storageUsed, // { count, bytes, limit }
} = useUserFiles('markdown');
```
---
## Phase 4: Pro Proxy (Server-Side URL Fetch)
**Goal:** Pro users bypass CORS with a server-side proxy via Supabase Edge Function.
### 4.1 How It Works
```
Free user: Pro user:
Browser → Target API Browser → Edge Function → Target API
↓ ↓
CORS blocked ❌ No CORS ✅
```
### 4.2 Edge Function: `proxy-fetch`
```typescript
// supabase/functions/proxy-fetch/index.ts
import { serve } from "https://deno.land/std/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js";
const BLOCKED_HOSTS = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2\d|3[01])\./,
/^192\.168\./,
/^0\./,
/^169\.254\./, // link-local
/^::1$/,
/^fc00:/i, /^fe80:/i, // IPv6 private
];
serve(async (req) => {
// 1. Verify JWT and extract tier
const authHeader = req.headers.get("Authorization");
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: { headers: { Authorization: authHeader } }
});
const { data: { user }, error } = await supabase.auth.getUser();
if (!user || user.app_metadata?.tier !== "pro") {
return new Response(JSON.stringify({ error: "Pro subscription required" }), {
status: 403
});
}
// 2. Parse request
const { url, method, headers, body } = await req.json();
// 3. SSRF protection
const targetUrl = new URL(url);
if (BLOCKED_HOSTS.some(pattern => pattern.test(targetUrl.hostname))) {
return new Response(JSON.stringify({ error: "Blocked host" }), {
status: 400
});
}
// 4. Rate limiting check (50 req/min for Pro)
const { count } = await supabase
.from("proxy_logs")
.select("*", { count: "exact", head: true })
.eq("user_id", user.id)
.gte("created_at", new Date(Date.now() - 60000).toISOString());
if (count >= 50) {
return new Response(JSON.stringify({ error: "Rate limit exceeded (50/min)" }), {
status: 429
});
}
// 5. Forward request
const start = Date.now();
const response = await fetch(url, {
method: method || "GET",
headers: headers || {},
body: body ? JSON.stringify(body) : undefined,
});
const responseBody = await response.text();
// 6. Log usage
await supabase.from("proxy_logs").insert({
user_id: user.id,
target_url: url,
method: method || "GET",
status_code: response.status,
response_size: responseBody.length,
duration_ms: Date.now() - start,
});
// 7. Return response
return new Response(responseBody, {
status: response.status,
headers: {
"Content-Type": response.headers.get("Content-Type") || "application/json",
"Access-Control-Allow-Origin": "https://dewe.dev",
},
});
});
```
### 4.3 Frontend Integration
The existing `AdvancedURLFetch.js` component already has the full UI (method, headers, auth, body, presets). Currently hidden behind `{false && ...}`. Changes needed:
1. **Free users:** Basic fetch (client-side, GET only, CORS-dependent) — current behavior
2. **Pro users:** Toggle reveals advanced panel → requests go through Edge Function
3. Update `features.js` to read tier from `user.app_metadata.tier` instead of static value
4. Presets migration: move from localStorage to `user_files` with `tool_type: 'fetch_preset'`
### 4.4 Security Measures
| Threat | Mitigation |
|--------|-----------|
| SSRF (hitting internal services) | Block private IP ranges, resolve DNS before fetch |
| Abuse (crypto mining, spam) | Rate limiting (50 req/min), response size cap (5 MB) |
| Data exfiltration | Log all proxy requests, alert on anomalies |
| JWT forgery | Verify JWT server-side on every request |
| Cost attack (huge responses) | Stream with size limit, abort after 5 MB |
---
## Phase 5: Account Page
**Goal:** Single page at `/account` for profile, files, and settings. Minimal.
### 5.1 Page Layout
```
/account
┌─────────────────────────────────────────────────┐
│ Profile │
│ ┌─────────────────────────────────────────────┐ │
│ │ [Avatar] display_name tier_badge │ │
│ │ email │ │
│ │ Member since: date │ │
│ │ [Edit Profile] │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Storage: ████████░░░░ 2.3 MB / 5 MB (Free) │
│ [Upgrade to Pro] │
│ │
│ My Files [Filter ▾] │
│ ┌─────────────────────────────────────────────┐ │
│ │ 📝 Project README.md 12 KB 2h ago │ │
│ │ 📄 Invoice #042 - Acme 3 KB 1d ago │ │
│ │ 📊 Sales Data Q4 8 KB 3d ago │ │
│ │ { } API Response Sample 1 KB 1w ago │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Each file row: [Open in Editor] [Download] [🗑] │
│ │
│ ────────────────────────────────────────────── │
│ [Sign Out] [Delete Account] │
└─────────────────────────────────────────────────┘
```
### 5.2 "Open in Editor" Flow
Clicking "Open in Editor" on a saved file:
1. Navigates to the corresponding tool (e.g., `/markdown-editor`)
2. Passes file ID via query param: `/markdown-editor?file=uuid`
3. Tool detects `?file=` param → fetches from `user_files` → loads into editor
4. Editor shows "Editing: {filename}" indicator
5. Subsequent saves update the same file (not create new)
---
## Phase 6: localStorage Migration
**Goal:** Seamlessly migrate existing localStorage data to cloud on first login.
### 6.1 Migration Flow
```
User logs in for the first time
Check localStorage for known keys:
• urlFetchPresets
• (any other persisted tool data)
Found data? ──No──→ Done
Yes
Show toast: "We found saved data on this device.
Would you like to sync it to your account?"
┌────┴────┐
Yes No
│ │
▼ ▼
Upload Dismiss
to DB (ask again
+ clear next login)
localStorage
```
### 6.2 Known localStorage Keys to Migrate
| Key | Tool | Migration Target |
|-----|------|-----------------|
| `urlFetchPresets` | AdvancedURLFetch | `user_files` with `tool_type: 'fetch_preset'` |
Currently this is the only localStorage key with user data. As tools add auto-save to localStorage (pre-auth), add those keys here.
---
## Phase 7: Pro Tier & Billing (Future)
**Goal:** Monetize Pro features. Not in initial scope but schema is ready.
### 7.1 Pro Features Summary
| Feature | Free | Pro |
|---------|------|-----|
| All tools | Yes | Yes |
| Copy & Download | Yes | Yes |
| Save files | 20 files, 5 MB | Unlimited, 100 MB |
| URL Fetch | Client-side (CORS-dependent) | Server-side proxy (any API) |
| Fetch presets | localStorage only | Cloud-synced |
| Advanced Fetch UI | Hidden | Full (methods, headers, auth, body) |
| Ads | Yes | No |
### 7.2 Pricing (Recommendation)
**Subscription model** (recurring revenue > one-time):
| Plan | Price | Billing |
|------|-------|---------|
| Monthly | $4.99/mo | Monthly |
| Annual | $39.99/yr (~$3.33/mo) | Annual |
**Why subscription over one-time:**
- Server costs are ongoing (Coolify hosting, proxy bandwidth)
- Aligns cost with value delivered
- Predictable revenue
**Payment provider:** Stripe (or Lemon Squeezy for simpler tax handling).
### 7.3 Tier Enforcement Architecture
```
Client-side (fast):
user.app_metadata.tier → show/hide UI elements
Server-side (secure):
JWT claims → Edge Function checks tier before processing
DB triggers → sync tier changes to JWT claims
Storage limits → DB trigger on insert
```
The `sync_tier_to_claims` trigger (Phase 1 schema) ensures that when you update `profiles.tier`, the JWT custom claim is updated. The user's next token refresh picks up the new tier automatically.
---
## Implementation Order
```
Phase 1: Supabase on Coolify ░░░░░░░░ ~1 day
└─ Deploy, configure, run schema
└─ Verify connection from localhost
Phase 2: Auth Integration ░░░░░░░░░░░░ ~2 days
└─ supabase.js client setup
└─ AuthContext + useAuth hook
└─ AuthModal (login/signup overlay)
└─ Header UserMenu
└─ /account page (basic)
Phase 3: File Save/Load ░░░░░░░░░░░░░░░░ ~3 days
└─ useUserFiles hook
└─ SaveLoadBar component
└─ "My Files" tab in each tool
└─ Account page file manager
└─ Markdown Editor integration (first)
└─ Invoice Editor integration
└─ Remaining tools
Phase 4: Pro Proxy ░░░░░░░░░░░░ ~2 days
└─ Edge Function deployment
└─ SSRF protection
└─ Rate limiting
└─ AdvancedURLFetch rewire
└─ Feature flags from JWT
Phase 5: localStorage Migration ░░░░ ~0.5 day
└─ Detection + migration prompt
Phase 6: Polish & Testing ░░░░░░░░ ~1.5 days
└─ Error states, loading states
└─ Mobile responsive check
└─ Auth edge cases (expired session, etc.)
Total estimate: ~10 days
```
---
## Technical Decisions & Rationale
### Why JSONB in Postgres (not Supabase Storage)?
All saved content is text-based and < 2 MB. JSONB gives us:
- Queryable content (search inside saved files later)
- Indexable fields (filter by tool_type, sort by date)
- Single table, single query — no separate file download step
- Atomic operations (save file + update metadata in one transaction)
- Simpler backup (one pg_dump gets everything)
Supabase Storage (S3-compatible) would be better for binary files (images, PDFs > 10 MB). We don't have that use case.
### Why Edge Functions (not a separate Node.js proxy)?
- Already included in self-hosted Supabase — no extra deployment
- Direct access to Supabase Auth (JWT verification built-in)
- Deno runtime is secure by default (explicit permissions)
- Scales with the Supabase instance
- One less service to maintain on Coolify
### Why Modal Login (not a /login page)?
- User is mid-work in an editor when they hit "Save"
- Redirecting to /login would lose their unsaved editor state
- Modal keeps React state intact — after login, save proceeds immediately
- Better conversion: lower friction = more signups
### Why Custom Claims for Tier (not DB lookup per request)?
- JWT claims are verified locally — zero network latency
- Edge Function doesn't need a DB query to check tier
- DB trigger keeps claims in sync automatically
- Standard Supabase pattern, well-documented
---
## Risks & Mitigations
| Risk | Impact | Mitigation |
|------|--------|-----------|
| Supabase self-hosted instability | High | Monitor with Coolify health checks, set up alerts, daily backups |
| Proxy abuse (SSRF, scraping) | High | IP blocklist, rate limiting, URL validation, response size cap |
| Storage costs grow | Medium | Enforce limits per tier, compress JSONB, monitor `storage_used_bytes` |
| Auth session expired mid-edit | Medium | Silent token refresh, save to localStorage as fallback, retry on 401 |
| Migration from localStorage fails | Low | Non-destructive: keep localStorage as fallback, retry option |
| Coolify resource limits | Medium | Start with 4 GB RAM, monitor, scale vertically as needed |
---
## Open Questions
1. **OAuth providers** — GitHub OAuth is ideal for dev audience. Worth adding Google too for broader reach? Both are easy with Supabase Auth.
2. **File sharing** — Should saved files be shareable via public link? (e.g., share a markdown preview). Not in initial scope, but schema supports adding a `is_public` flag later.
3. **Auto-save** — Should tools auto-save to cloud every N seconds for logged-in users? Or only manual save? Recommendation: manual save only (simpler, less backend load, user controls what gets saved).
4. **Offline support** — If Supabase is down, tools still work (client-side). But save/load fails. Should we queue saves in localStorage and sync when back online?
5. **Delete account** — GDPR requires account deletion. The `on delete cascade` in the schema handles this. Need a confirmation flow in the UI.
---
## File Structure Changes
```
src/
├── lib/
│ └── supabase.js # NEW: Supabase client init
├── contexts/
│ └── AuthContext.js # NEW: Auth state provider
├── hooks/
│ ├── useAuth.js # NEW: Auth convenience hook
│ ├── useUserFiles.js # NEW: File CRUD operations
│ ├── useAnalytics.js
│ └── useNavigationGuard.js
├── components/
│ ├── AuthModal.js # NEW: Login/signup modal overlay
│ ├── UserMenu.js # NEW: Avatar dropdown in header
│ ├── SaveLoadBar.js # NEW: Save/Load buttons for tools
│ ├── MyFilesTab.js # NEW: "My Files" input tab
│ ├── StorageMeter.js # NEW: Storage usage indicator
│ ├── AdvancedURLFetch.js # MODIFY: use proxy for Pro
│ ├── ProBadge.js # KEEP: already exists
│ └── Layout.js # MODIFY: add UserMenu to header
├── pages/
│ ├── AccountPage.js # NEW: /account
│ └── ...existing tools... # MODIFY: add save/load integration
├── config/
│ ├── features.js # MODIFY: read tier from JWT claims
│ └── tools.js # KEEP
└── App.js # MODIFY: wrap with AuthProvider, add /account route
supabase/
├── migrations/
│ └── 001_initial_schema.sql # NEW: all tables, RLS, triggers
└── functions/
└── proxy-fetch/
└── index.ts # NEW: CORS proxy edge function
```

0
PROJECT_ROADMAP.md Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

0
SEO_IMPROVEMENT_PLAN.md Normal file → Executable file
View File

0
TODO.md Normal file → Executable file
View File

0
nixpacks.toml Normal file → Executable file
View File

44917
package-lock.json generated Normal file → Executable file

File diff suppressed because it is too large Load Diff

3
package.json Normal file → Executable file
View File

@@ -89,8 +89,7 @@
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
"react-app"
]
},
"browserslist": {

0
postcss.config.js Normal file → Executable file
View File

0
public/ads.txt Normal file → Executable file
View File

0
public/android-chrome-192x192.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
public/android-chrome-512x512.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

0
public/apple-touch-icon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

0
public/data/commits.json Normal file → Executable file
View File

0
public/data/currencies.json Normal file → Executable file
View File

0
public/favicon-16x16.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 962 B

After

Width:  |  Height:  |  Size: 962 B

0
public/favicon-32x32.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

0
public/favicon.ico Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

0
public/icon-192x192.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
public/icon-512x512.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

0
public/images/onidel-banner.webp Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

0
public/index.html Normal file → Executable file
View File

0
public/logo.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

0
public/manifest.json Normal file → Executable file
View File

0
public/robots.txt Normal file → Executable file
View File

0
public/sitemap.xml Normal file → Executable file
View File

0
src/App.js Normal file → Executable file
View File

BIN
src/components/._AdBlock.js Executable file

Binary file not shown.

Binary file not shown.

BIN
src/components/._OfferBlock.js Executable file

Binary file not shown.

Binary file not shown.

10
src/components/AdBlock.js Normal file → Executable file
View File

@@ -1,6 +1,10 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef } from "react";
const AdBlock = ({ className = '', adKey = 'e0ca7c61c83457f093bbc2e261b43d31', adDomain = 'solutionbiologyisle.com' }) => {
const AdBlock = ({
className = "",
adKey = "e0ca7c61c83457f093bbc2e261b43d31",
adDomain = "www.highperformanceformat.com",
}) => {
const iframeRef = useRef(null);
useEffect(() => {
@@ -39,7 +43,7 @@ const AdBlock = ({ className = '', adKey = 'e0ca7c61c83457f093bbc2e261b43d31', a
<iframe
ref={iframeRef}
className={`bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden ${className}`}
style={{ width: '300px', height: '250px', border: 'none' }}
style={{ width: "300px", height: "250px", border: "none" }}
title="Advertisement"
sandbox="allow-scripts allow-same-origin"
/>

0
src/components/AdColumn.js Normal file → Executable file
View File

0
src/components/AdvancedURLFetch.js Normal file → Executable file
View File

0
src/components/AffiliateBlock.js Normal file → Executable file
View File

0
src/components/CodeEditor.js Normal file → Executable file
View File

0
src/components/CodeMirrorEditor.js Normal file → Executable file
View File

0
src/components/ConsentBanner.js Normal file → Executable file
View File

0
src/components/CopyButton.js Normal file → Executable file
View File

0
src/components/ErrorBoundary.js Normal file → Executable file
View File

0
src/components/Layout.js Normal file → Executable file
View File

0
src/components/Loading.js Normal file → Executable file
View File

0
src/components/MindmapView.js Normal file → Executable file
View File

14
src/components/MobileAdBanner.js Normal file → Executable file
View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { X } from 'lucide-react';
import React, { useEffect, useRef, useState } from "react";
import { X } from "lucide-react";
const MobileAdBanner = () => {
const [visible, setVisible] = useState(true);
@@ -7,8 +7,8 @@ const MobileAdBanner = () => {
const iframeRef = useRef(null);
useEffect(() => {
const wasClosed = sessionStorage.getItem('mobileAdClosed');
if (wasClosed === 'true') {
const wasClosed = sessionStorage.getItem("mobileAdClosed");
if (wasClosed === "true") {
setClosed(true);
setVisible(false);
}
@@ -42,7 +42,7 @@ const MobileAdBanner = () => {
'params' : {}
};
</script>
<script type="text/javascript" src="https://solutionbiologyisle.com/2965bcf877388cafa84160592c550f5a/invoke.js"></script>
<script type="text/javascript" src="https://www.highperformanceformat.com/2965bcf877388cafa84160592c550f5a/invoke.js"></script>
</body>
</html>
`);
@@ -55,7 +55,7 @@ const MobileAdBanner = () => {
const handleClose = () => {
setVisible(false);
setClosed(true);
sessionStorage.setItem('mobileAdClosed', 'true');
sessionStorage.setItem("mobileAdClosed", "true");
};
if (!visible || closed) return null;
@@ -72,7 +72,7 @@ const MobileAdBanner = () => {
<div className="flex justify-center items-center py-2">
<iframe
ref={iframeRef}
style={{ width: '320px', height: '50px', border: 'none' }}
style={{ width: "320px", height: "50px", border: "none" }}
title="Mobile Advertisement"
sandbox="allow-scripts allow-same-origin"
/>

0
src/components/NavigationConfirmModal.js Normal file → Executable file
View File

18
src/components/OfferBlock.js Normal file → Executable file
View File

@@ -1,20 +1,16 @@
import React from 'react';
import React from "react";
const OfferBlock = () => {
return (
<div className="w-[300px] h-[250px] bg-gradient-to-br from-indigo-500 to-purple-600 rounded-lg flex flex-col items-center justify-center text-white p-6 text-center shadow-lg hover:shadow-xl transition-shadow duration-300">
<span className="bg-white/20 text-xs font-bold px-2 py-1 rounded mb-4 backdrop-blur-sm">
SPECIAL OFFER
<span className="bg-white/20 text-xs font-bold px-2 py-1 rounded mb-4 backdrop-blur-sm uppercase tracking-wider">
Coming Soon
</span>
<h3 className="text-2xl font-bold mb-2">
Upgrade to PRO
</h3>
<p className="text-indigo-100 text-sm mb-6">
Get unlimited access to all developer tools and features.
<h3 className="text-2xl font-bold mb-2">Upgrade to PRO</h3>
<p className="text-indigo-100 text-sm mb-6 leading-relaxed">
We are preparing a premium ad-free experience with exclusive developer
tools and features. Stay tuned!
</p>
<button className="bg-white text-indigo-600 font-bold py-2 px-6 rounded-full hover:bg-indigo-50 transition-colors shadow-md">
Learn More
</button>
</div>
);
};

0
src/components/PostmanTable.js Normal file → Executable file
View File

0
src/components/PostmanTreeTable.js Normal file → Executable file
View File

0
src/components/ProBadge.js Normal file → Executable file
View File

0
src/components/RelatedTools.js Normal file → Executable file
View File

0
src/components/SEO.js Normal file → Executable file
View File

0
src/components/SEOHead.js Normal file → Executable file
View File

0
src/components/StructuredEditor.js Normal file → Executable file
View File

13
src/components/TabletAdSection.js Normal file → Executable file
View File

@@ -1,7 +1,7 @@
import React from 'react';
import AdBlock from './AdBlock';
import OfferBlock from './OfferBlock';
import AffiliateBlock from './AffiliateBlock';
import React from "react";
import AdBlock from "./AdBlock";
import OfferBlock from "./OfferBlock";
import AffiliateBlock from "./AffiliateBlock";
const TabletAdSection = () => {
return (
@@ -11,7 +11,10 @@ const TabletAdSection = () => {
</h3>
<div className="flex justify-center gap-4 overflow-x-auto pb-4">
<div className="flex-shrink-0">
<AdBlock adKey="7c55aebcdd74f6e9a8dc24bd13e7d949" adDomain="solutionbiologyisle.com" />
<AdBlock
adKey="7c55aebcdd74f6e9a8dc24bd13e7d949"
adDomain="www.highperformanceformat.com"
/>
</div>
<div className="flex-shrink-0">
<OfferBlock />

0
src/components/ThemeToggle.js Normal file → Executable file
View File

0
src/components/ToolCard.js Normal file → Executable file
View File

0
src/components/ToolLayout.js Normal file → Executable file
View File

0
src/components/ToolSidebar.js Normal file → Executable file
View File

0
src/components/invoice-templates/MinimalTemplate.js Normal file → Executable file
View File

0
src/config/features.js Normal file → Executable file
View File

0
src/config/tools.js Normal file → Executable file
View File

0
src/data/faqs.js Normal file → Executable file
View File

0
src/hooks/useAnalytics.js Normal file → Executable file
View File

0
src/hooks/useNavigationGuard.js Normal file → Executable file
View File

0
src/index.css Normal file → Executable file
View File

0
src/index.js Normal file → Executable file
View File

0
src/pages/Base64Tool.js Normal file → Executable file
View File

0
src/pages/BeautifierTool.js Normal file → Executable file
View File

0
src/pages/DiffTool.js Normal file → Executable file
View File

0
src/pages/Home.js Normal file → Executable file
View File

0
src/pages/InvoiceEditor.js Normal file → Executable file
View File

0
src/pages/InvoicePreview.js Normal file → Executable file
View File

0
src/pages/InvoicePreviewMinimal.js Normal file → Executable file
View File

0
src/pages/MarkdownEditor.js Normal file → Executable file
View File

0
src/pages/NotFound.js Normal file → Executable file
View File

0
src/pages/ObjectEditor.js Normal file → Executable file
View File

0
src/pages/PrivacyPolicy.js Normal file → Executable file
View File

0
src/pages/ReleaseNotes.js Normal file → Executable file
View File

0
src/pages/TableEditor.js Normal file → Executable file
View File

0
src/pages/TermsOfService.js Normal file → Executable file
View File

0
src/pages/TextLengthTool.js Normal file → Executable file
View File

0
src/pages/UrlTool.js Normal file → Executable file
View File

0
src/pages/components/CodeInputsNew.js Normal file → Executable file
View File

0
src/pages/components/ElementEditor.js Normal file → Executable file
View File

0
src/pages/components/PreviewFrame.js Normal file → Executable file
View File

0
src/pages/components/PreviewServer.js Normal file → Executable file
View File

0
src/pages/components/SimpleToolbar.js Normal file → Executable file
View File

0
src/pages/components/Toolbar.js Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More