Figuro is a full-stack SaaS platform that lets users instantly transform their Figma designs into production-ready frontend code (React, Vue, Angular, Svelte, or plain HTML). It includes a user-facing app with authentication, project management, and conversion history, plus a full admin panel with analytics.
- Features
- Tech Stack
- Project Structure
- Getting Started
- Environment Variables
- Authentication
- Database
- API Reference
- Pages & Routes
- Admin Panel
- Development Workflow
- Deployment
- GitHub Integration
- Troubleshooting
- Figma-to-Code Conversion — Submit any Figma project URL and convert it to React, Vue, Angular, Svelte, or HTML in seconds.
- Framework & Styling Options — Choose your target framework and styling approach (Tailwind CSS, CSS Modules, Styled Components, or plain CSS).
- Project Management — Organize conversions into named projects, archive old ones, track conversion history.
- Code Viewer — Syntax-highlighted output with one-click copy and download.
- Regeneration — Re-run any conversion to get a fresh output.
- Dashboard — Real-time stats (total projects, conversions, success rate) plus a recent-activity feed.
- Authentication — Email/password, Google OAuth, and GitHub OAuth via Better Auth.
- Platform Analytics — Daily conversions chart (last 30 days), framework breakdown pie chart, live KPI cards.
- User Management — View all users, change plans (Free/Pro/Enterprise), change roles (user/admin), ban or delete accounts.
- Conversion Overview — Full table of all platform conversions with status filtering.
| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite, TypeScript, Tailwind CSS v4, shadcn/ui, Wouter (routing), React Query |
| Backend | Express 5, Node.js 24, TypeScript |
| Auth | Better Auth (email/password + Google OAuth + GitHub OAuth) |
| Database | PostgreSQL (Neon DB or any Postgres) via Drizzle ORM |
| Validation | Zod (v4), drizzle-zod |
| API Contract | OpenAPI 3.1 → Orval codegen (React Query hooks + Zod schemas) |
| Charts | Recharts |
| Build | esbuild (server), Vite (client) |
| Monorepo | pnpm workspaces |
figuro/
├── artifacts/
│ ├── api-server/ # Express 5 backend
│ │ └── src/
│ │ ├── app.ts # Express app, CORS, Better Auth mount
│ │ ├── index.ts # Server entry point
│ │ ├── lib/
│ │ │ ├── auth.ts # Better Auth configuration
│ │ │ ├── session.ts # Session helper
│ │ │ └── logger.ts # Pino structured logger
│ │ └── routes/
│ │ ├── health.ts
│ │ ├── user.ts
│ │ ├── projects.ts
│ │ ├── conversions.ts
│ │ ├── dashboard.ts
│ │ └── admin.ts
│ └── figuro/ # React + Vite frontend
│ └── src/
│ ├── App.tsx # Router setup
│ ├── index.css # Theme variables + Tailwind
│ ├── contexts/
│ │ └── AuthContext.tsx
│ └── pages/
│ ├── landing.tsx
│ ├── login.tsx
│ ├── register.tsx
│ ├── dashboard.tsx
│ ├── projects.tsx
│ ├── project-detail.tsx
│ ├── convert.tsx
│ ├── conversion-result.tsx
│ ├── pricing.tsx
│ ├── settings.tsx
│ ├── admin/
│ │ ├── index.tsx
│ │ ├── users.tsx
│ │ └── conversions.tsx
│ └── not-found.tsx
├── lib/
│ ├── api-spec/
│ │ └── openapi.yaml # API contract (source of truth)
│ ├── api-client-react/ # Generated React Query hooks
│ ├── api-zod/ # Generated Zod validation schemas
│ └── db/
│ └── src/
│ └── schema/
│ ├── users.ts
│ ├── projects.ts
│ ├── conversions.ts
│ └── activity.ts
└── scripts/ # Utility scripts
- Node.js 20+ (22+ recommended)
- pnpm 9+
- PostgreSQL database (local or Neon DB for hosted)
git clone https://github.com/your-username/figuro.git
cd figuropnpm installCopy the example env file and fill in your values:
cp .env.example .envSee Environment Variables for full details.
Push the schema to your PostgreSQL database:
pnpm --filter @workspace/db run pushpnpm --filter @workspace/scripts run seedIn two separate terminals:
# Terminal 1 — API server (port 5000)
pnpm --filter @workspace/api-server run dev
# Terminal 2 — Frontend (port from .env)
pnpm --filter @workspace/figuro run devOpen http://localhost:5173 (or whatever port Vite assigns).
Create a .env file in the project root (or set these in your hosting environment):
# ─── Database ───────────────────────────────────────────────────────────────
# Full PostgreSQL connection string (Neon, Supabase, or local)
DATABASE_URL=postgresql://user:password@host:5432/figuro
# ─── Better Auth ─────────────────────────────────────────────────────────────
# Required: random 32+ character secret key
# Generate one: https://generate-secret.vercel.app/32
BETTER_AUTH_SECRET=your-super-secret-key-change-this-in-production
# The base URL where the auth callbacks are served (no trailing slash)
# In development: http://localhost:5000
# In production: https://your-domain.com
BETTER_AUTH_URL=http://localhost:5000
# ─── Google OAuth ────────────────────────────────────────────────────────────
# Create credentials at: https://console.cloud.google.com/apis/credentials
# Authorized redirect URI: {BETTER_AUTH_URL}/api/auth/callback/google
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# ─── GitHub OAuth ────────────────────────────────────────────────────────────
# Create an OAuth App at: https://github.com/settings/developers
# Callback URL: {BETTER_AUTH_URL}/api/auth/callback/github
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# ─── Server ──────────────────────────────────────────────────────────────────
# Port for the API server (set automatically in Replit)
PORT=5000- Go to Google Cloud Console
- Create a new project (or select an existing one)
- Enable the Google+ API or People API
- Click Create Credentials → OAuth client ID
- Choose Web application
- Add to Authorized redirect URIs:
- Development:
http://localhost:5000/api/auth/callback/google - Production:
https://your-domain.com/api/auth/callback/google
- Development:
- Copy the Client ID and Client Secret into your
.env
- Go to GitHub Developer Settings
- Click New OAuth App
- Fill in:
- Homepage URL:
http://localhost:5173(or your production URL) - Authorization callback URL:
http://localhost:5000/api/auth/callback/github
- Homepage URL:
- Copy the Client ID and generate a Client Secret
- Add them to your
.env
Authentication is powered by Better Auth, a modern full-stack auth library.
| Method | Description |
|---|---|
| Email + Password | Standard registration and login |
| Google OAuth | One-click sign-in with Google |
| GitHub OAuth | One-click sign-in with GitHub |
- The Better Auth handler is mounted at
/api/auth/*in the Express app. - The frontend
AuthContext(src/contexts/AuthContext.tsx) wraps the entire app and exposes:user— current user profile (ornull)isLoading— true while session is being fetchedisAuthenticated— convenience booleanlogin(email, password)— email/password sign-inregister(name, email, password)— new account creationloginWithGoogle()— redirect to Google OAuth flowloginWithGithub()— redirect to GitHub OAuth flowlogout()— sign out and clear session
- Protected routes redirect unauthenticated users to
/login. - Admin routes additionally check
user.role === 'admin'.
After creating your account, run this SQL to promote yourself:
UPDATE users SET role = 'admin' WHERE email = 'your@email.com';Or use the Replit database tool / any SQL client connected to your DATABASE_URL.
The project uses Drizzle ORM with PostgreSQL.
| Table | Purpose |
|---|---|
users |
User accounts (extended Better Auth schema with plan, role, banned) |
sessions |
Active login sessions (managed by Better Auth) |
accounts |
OAuth account links (managed by Better Auth) |
verifications |
Email verification tokens (managed by Better Auth) |
projects |
User Figma projects |
conversions |
Individual conversion jobs |
activity |
User activity feed events |
All schema lives in lib/db/src/schema/. Each table is a separate file, re-exported from index.ts.
This project uses push-based schema management (Drizzle Kit push):
# Apply schema changes to the database
pnpm --filter @workspace/db run push
# Force push (resets conflicting columns — use carefully)
pnpm --filter @workspace/db run push-forceProduction note: On Replit, the production database is automatically migrated when you publish. Drizzle diffs the dev schema against prod and applies changes through the Publish UI.
The API is contract-first: all endpoints are defined in lib/api-spec/openapi.yaml and React Query hooks are auto-generated from it.
pnpm --filter @workspace/api-spec run codegenAll endpoints are prefixed with /api.
| Method | Path | Description |
|---|---|---|
| GET | /api/user/me |
Get current user profile |
| PATCH | /api/user/me |
Update profile (name, avatar) |
| Method | Path | Description |
|---|---|---|
| GET | /api/projects |
List user's projects |
| POST | /api/projects |
Create a project |
| GET | /api/projects/:id |
Get project by ID |
| PATCH | /api/projects/:id |
Update project |
| DELETE | /api/projects/:id |
Delete project |
| Method | Path | Description |
|---|---|---|
| GET | /api/conversions |
List user's conversions |
| POST | /api/conversions |
Submit a new conversion |
| GET | /api/conversions/:id |
Get conversion by ID |
| DELETE | /api/conversions/:id |
Delete conversion |
| POST | /api/conversions/:id/regenerate |
Re-run a conversion |
| Method | Path | Description |
|---|---|---|
| GET | /api/dashboard/stats |
User stats (projects, conversions, success rate) |
| GET | /api/dashboard/activity |
Recent activity feed (last 20 events) |
| Method | Path | Description |
|---|---|---|
| GET | /api/admin/stats |
Platform-wide stats |
| GET | /api/admin/users |
All users |
| PATCH | /api/admin/users/:id |
Update user (plan, role, banned) |
| DELETE | /api/admin/users/:id |
Delete user |
| GET | /api/admin/conversions |
All conversions |
| GET | /api/admin/analytics/daily |
Daily conversion chart data (30 days) |
| GET | /api/admin/analytics/frameworks |
Framework usage breakdown |
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/sign-in/email |
Email/password login |
| POST | /api/auth/sign-up/email |
Register with email |
| GET | /api/auth/sign-in/social?provider=google |
Start Google OAuth |
| GET | /api/auth/sign-in/social?provider=github |
Start GitHub OAuth |
| POST | /api/auth/sign-out |
Log out |
| GET | /api/auth/session |
Get current session |
| Route | Auth Required | Description |
|---|---|---|
/ |
No | Marketing landing page |
/login |
No | Login (email/Google/GitHub) |
/register |
No | Register new account |
/dashboard |
Yes | User dashboard with stats |
/projects |
Yes | Projects list |
/projects/:id |
Yes | Project detail + conversions |
/convert |
Yes | New conversion form |
/conversions/:id |
Yes | Conversion result + code viewer |
/pricing |
No | Pricing plans |
/settings |
Yes | User settings |
/admin |
Admin only | Admin analytics dashboard |
/admin/users |
Admin only | User management table |
/admin/conversions |
Admin only | All conversions table |
The admin panel is accessible at /admin and requires role = 'admin'.
UPDATE users SET role = 'admin' WHERE email = 'admin@yourcompany.com';- Dashboard (
/admin): KPI cards, daily conversions line chart, framework breakdown pie chart, recent signups - Users (
/admin/users): Full searchable user table, inline plan/role editing, ban/unban, delete - Conversions (
/admin/conversions): All platform conversions with status and framework filters
pnpm run typecheckpnpm run build- Add it to the spec: Edit
lib/api-spec/openapi.yaml - Re-run codegen:
pnpm --filter @workspace/api-spec run codegen - Implement the route: Add handler in
artifacts/api-server/src/routes/ - Register the route: Import it in
artifacts/api-server/src/routes/index.ts - Use the hook: Import the generated hook from
@workspace/api-client-reactin your React component
- Create a new file in
lib/db/src/schema/(e.g.lib/db/src/schema/invoices.ts) - Export it from
lib/db/src/schema/index.ts - Run
pnpm --filter @workspace/db run push
# Install all workspace dependencies
pnpm install
# Run the API server in development
pnpm --filter @workspace/api-server run dev
# Run the frontend in development
pnpm --filter @workspace/figuro run dev
# Full typecheck (all packages)
pnpm run typecheck
# Build everything
pnpm run build
# Push DB schema changes
pnpm --filter @workspace/db run push
# Re-generate API client hooks from OpenAPI spec
pnpm --filter @workspace/api-spec run codegen- Click the Deploy button in the Replit UI
- Replit will automatically:
- Build the frontend (
vite build) - Build the API server (
esbuild) - Diff and apply any schema changes to the production database
- Build the frontend (
- The app will be live at your
.replit.appdomain (or a custom domain if configured)
- Set all environment variables listed in Environment Variables
- Build:
pnpm install --frozen-lockfile pnpm run build
- Start the API server:
node artifacts/api-server/dist/index.mjs
- Serve the frontend from
artifacts/figuro/dist/publicvia a static file server (nginx, Caddy, etc.) or configure the Express server to serve it. - Apply DB schema:
pnpm --filter @workspace/db run push
When deploying to production, update your OAuth app callback URLs:
- Google: Add
https://your-domain.com/api/auth/callback/google - GitHub: Update callback URL to
https://your-domain.com/api/auth/callback/github - BETTER_AUTH_URL: Set to
https://your-domain.com
To push this project to GitHub:
git init
git add .
git commit -m "Initial commit: Figuro SaaS platform"
git branch -M main
git remote add origin https://github.com/your-username/figuro.git
git push -u origin maingit add .
git commit -m "Your commit message"
git pushMake sure your .gitignore includes:
.env
node_modules/
dist/
.DS_Store
*.tsbuildinfo
Never commit your
.envfile. Store secrets in your hosting platform's environment variable manager (Replit Secrets, Railway Variables, etc.).
Ensure DATABASE_URL is in your environment. On Replit, it's auto-provisioned. Locally, add it to .env.
The callback URL registered in Google/GitHub must exactly match {BETTER_AUTH_URL}/api/auth/callback/{provider}. Double-check BETTER_AUTH_URL has no trailing slash and matches the URL your server is actually running on.
Generate a strong secret: openssl rand -base64 32 or use https://generate-secret.vercel.app/32.
pnpm --filter @workspace/db run push-forceAlways re-run codegen after spec changes:
pnpm --filter @workspace/api-spec run codegenThe API server reads PORT from the environment. Make sure nothing else is running on the same port. Vite's port is separately configured in vite.config.ts.
Your user account needs role = 'admin'. Run:
UPDATE users SET role = 'admin' WHERE email = 'your@email.com';MIT — feel free to use, modify, and distribute this project.