URL shortener with admin console, campaigns, tags, and click analytics.
| Path | Purpose |
|---|---|
/ |
Landing + shorten form (persists through auth) |
/signup /login |
Auth with ?next= return path |
/terms /privacy /abuse |
Legal + abuse report |
/app |
Overview |
/app/links |
Links (filter by campaign / tag) |
/app/links/:id |
Link analytics + edit tags/campaign |
/app/campaigns |
Campaigns list |
/app/campaigns/:id |
Campaign detail + its links |
/app/analytics |
Global analytics |
/app/create |
Create short link |
/app/profile |
Profile, password, delete account |
/:code |
Public redirect |
- 50 new links per user per UTC day
- 10 creates per minute (client + server)
- Languages: English, Bengali, Spanish, French, German, Portuguese, Chinese, Japanese, Arabic (RTL)
- Marketing: globe chip + soft language grid (not a heavy
<select>) - Dark mode is admin-only — sun/moon icon toggle
One migration, three commands:
| Command | What it does |
|---|---|
npm run db:migrate |
Apply supabase/migrations/20260722130000_init.sql |
npm run db:rollback |
Drop app schema (supabase/rollbacks/…) — keeps auth.users |
npm run db:fresh |
Rollback + migrate |
With DATABASE_URL / SUPABASE_DB_URL set (Postgres URI from Supabase → Settings → Database), those scripts run via psql. Without it, they print the file paths to paste into the SQL Editor.
Lists (links, campaign links, click streams) use server-side .range() pagination with exact counts.
Legacy incremental SQL lives under supabase/migrations_archive/ (reference only).
After deploy, set the real origin in public/sitemap.xml.
Build: npm run build · Publish: dist (see netlify.toml).
SPA routes (/:code, /app/*) need the /* → /index.html rewrite — without it Netlify returns its own 404 before React loads.
Env (Site settings → Environment variables), required at build time:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYVITE_SITE_NAME(defaulttnyl)VITE_SITE_URL(defaulthttps://tnyl.netlify.app)- Optional:
VITE_SITE_TAGLINE,VITE_SITE_DESCRIPTION
Optional for the guest-create function: same Supabase values as SUPABASE_URL / SUPABASE_ANON_KEY.