Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
The all-in-one workspace for solo freelancers.
Manage clients, projects, proposals, contracts, invoices, tasks, and time tracking — all in one place. Share a client portal with a single link.README 전문 보기 ↗The all-in-one workspace for solo freelancers.
Manage clients, projects, proposals, contracts, invoices, tasks, and time tracking — all in one place. Share a client portal with a single link.
| Module | What it does |
|---|---|
| Contacts | CRM — manage leads, active clients, archived contacts; CSV import/export; bulk actions |
| Projects | Track projects with status, stages, file uploads, notes, and comments |
| Proposals | Rich-text proposal builder; send via email; track accepted/declined status |
| Contracts | Clause-based contract builder; send for e-signature |
| Invoices | Line-item invoice builder; Stripe payment link; PDF export |
| Finance | Revenue, expenses, profit overview; recurring expenses; expense categories |
| Tasks | Task management with subtasks, priority, assignees, AI generation |
| Time Tracker | Log billable/non-billable time per project |
| Scheduler | Public booking page; availability rules; calendar view |
| Services | Reusable service library with pricing |
| Team | Invite team members; role-based permissions |
| Client Portal | One-link portal for clients to view files, pay invoices, leave comments |
| PDF Templates | Customisable PDF templates for invoices, proposals, and contracts |
| Notifications | In-app notification bell for portal views, invoice payments, and more |
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, React 19) |
| Styling | Tailwind CSS v4 |
| Database | PostgreSQL via Prisma ORM |
| Auth | NextAuth.js v4 — JWT strategy, credentials provider |
| Payments | Stripe Checkout + Stripe Connect |
| Resend | |
| Rich Text | TipTap (ProseMirror) |
| PDF Generation | @react-pdf/renderer |
| AI | Vercel AI SDK + OpenAI |
| Icons | Lucide React |
| Validation | Zod |
| File Storage | Local filesystem /uploads/ (dev); swap to S3/R2 for production |
app/
page.jsx Landing / marketing page
layout.js Root layout — wraps app in SessionProvider
(app)/ Authenticated app shell
layout.jsx Sidebar (Navbar) + TopBar + page slot
dashboard/ KPI overview + recent activity
contacts/ CRM module
projects/ Project management
proposals/ Proposal builder
contracts/ Contract builder
invoices/ Invoice management (also accessible via /finance)
finance/ Tabbed: Overview · Invoices · Payments · Expenses
tasks/ Task management
time-tracker/ Time entries
scheduler/ Availability + calendar
services/ Service library
settings/ Profile · Business · Team · Stripe · Plan · PDF Templates
p/[token]/ Public client portal (no auth)
book/[userId]/ Public booking page (no auth)
login/ Login page
signup/ Signup page
join/ Team invite acceptance page
api/ All API routes (see API Reference)
components/
shared/ Layout-level components (Navbar, TopBar, Modal, etc.)
ui/ Primitive UI components (Button, Input, Select, Card)
lib/
db.js Prisma client singleton
auth.js NextAuth authOptions
session.js getSession() — server-side session helper
utils.js cn(), formatCurrency(), formatDate(), STATUS_* constants
pdf-templates/ PDF rendering logic
services.js Shared service helpers
prisma/
schema.prisma Full database schema
migrations/ Migration history
Request lifecycle (authenticated page):
/projectsproxy.js (middleware) checks for valid NextAuth JWT cookie → redirects to /login if absentgetServerSession(authOptions) to get user contextbusinessId (tenant isolation)next-auth.session-token)POST /api/auth/register
→ validate email/name/password (Zod)
→ hash password with bcrypt
→ create User + Business records in a DB transaction
→ return success → client calls signIn("credentials")
NextAuth credentials provider
→ look up user by email
→ bcrypt.compare(password, user.password)
→ if valid: return user object
→ jwt() callback: embed id, email, name, plan, role, businessId in JWT
→ session() callback: re-fetch fresh user data from DB on each request
{
"id": "user_id",
"email": "[email protected]",
"name": "Jane Smith",
"plan": "solo",
"role": "owner",
"businessId": "biz_id",
"companyName": "Jane's Studio",
"timezone": "Europe/London"
}
proxy.js (Next.js middleware) protects all /dashboard, /projects, /settings, /contacts, /invoices, /finance, /tasks, /contracts, /proposals, /services, /time-tracker, /scheduler, /calendar routes. Unauthenticated requests are redirected to /login.
Public routes (no auth required): /, /login, /signup, /join, /p/[token], /book/[userId], /payment/*
Every protected API route follows this pattern:
const session = await getSession(req);
if (!session?.user) return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
const filter = getTenantFilter(session); // { businessId } or { userId }
const data = await db.model.findMany({ where: { ...filter } });
This ensures users can only read and write their own tenant's data.
| Role | Description |
|---|---|
owner | Full access; created automatically on signup |
member | Invited team member; access gated by permissions JSON array |
Team member permissions are checked in the relevant API routes before allowing create/edit/delete operations.
Every data model has either a userId or businessId (or both) foreign key. The getTenantFilter() helper returns the appropriate scope based on the session:
businessIduserIdThis means a user can never read, update, or delete another tenant's records — every database query is automatically scoped.
Purpose: CRM for managing leads and clients.
Frontend: app/(app)/contacts/
page.jsx — Server component; fetches contacts with search/filter/pagination; renders ContactsClientContactsClient.jsx — Client; table view; filter by status (lead/active/archived); search by name/email/companyContactFormModal.jsx — Create/edit contact modalContactsImportModal.jsx — CSV bulk import with field mappingContactsExportModal.jsx — Export contacts as CSVBackend: app/api/contacts/
GET /api/contacts — Returns paginated contacts; supports q, status, page query paramsPOST /api/contacts — Creates contact; validates required fields; normalises phone/emailGET /api/contacts/[id] — Fetch single contact with linked projectsPATCH /api/contacts/[id] — Update contact fieldsDELETE /api/contacts/[id] — Delete contactPOST /api/contacts/bulk — Bulk status update or bulk deletePOST /api/contacts/import — Parse CSV; validate rows; insert in batchPOST /api/contacts/export — Stream CSV response with all contact dataData collected: name, email, phone, company, company address, entity type (individual/company), status, estimated value, notes, tags.
Purpose: Track work for clients from start to finish.
Frontend: app/(app)/projects/
page.jsx — Project list with search, filter by status, archive toggle[id]/page.jsx — Project detail: header, files, notes, comments, tasks, invoices, time entriesProjectHeader.jsx — Status/stage controls; portal link; archive/deleteProjectTasksSection.jsx — Embedded tasks list with same UI as the Tasks modulenew/page.jsx — Create project form (title, contact, description, deadline)Backend: app/api/projects/
GET /api/projects — Filtered, paginated listPOST /api/projects — Create project; generates unique portalToken via nanoidPATCH /api/projects/[id] — Update status, stage, title, deadline, archived flagDELETE /api/projects/[id] — Cascade deletes files, comments, invoices, tasksData flow: Project creation generates a portalToken used to build the client portal URL (/p/[token]). Files are stored at /uploads/[projectId]/[filename] on the server.
Purpose: Create and send professional proposals to clients.
Frontend: app/(app)/proposals/
ProposalsClient.jsx — List; filter by status (draft/sent/accepted/declined)new/ProposalBuilderClient.jsx — Multi-section rich-text builder; pricing table; validity date[id]/page.jsx — Proposal detail view with status controls[id]/edit/ — Edit existing proposalProposalRichTextEditor.jsx — TipTap editor (headings, bold, lists, tables, links, images)[id]/ProposalActions.jsx — Send, download PDF, mark accepted/declined[id]/DownloadPdfButton.jsx — Triggers GET /api/pdf/proposal/[id]Backend:
GET/POST /api/proposals — List/createPATCH/DELETE /api/proposals/[id] — Update/deletePOST /api/proposals/draft — AI-assisted draft generationPOST /api/proposals/[id]/send — Send proposal email via Resend; sets sentAt and status: "sent"GET /api/pdf/proposal/[id] — Render @react-pdf/renderer PDF responseData collected: title, client name/email, intro text, rich-text sections (JSON array), pricing line items (JSON array), total, currency, valid-until date.
Purpose: Create binding contracts and collect e-signatures.
Frontend: app/(app)/contracts/
ContractsClient.jsx — List; filter by statusnew/ContractBuilderClient.jsx — Clause-based editor; each clause has a title and rich-text body[id]/page.jsx — Contract detail; signature collection UI[id]/DownloadPdfButton.jsx — PDF downloadBackend:
GET/POST /api/contracts — List/createPATCH/DELETE /api/contracts/[id] — Update/delete; includes signature name + signedAtPOST /api/contracts/draft — AI-assisted clause generationPOST /api/contracts/[id]/send — Email contract; sets status: "sent"GET /api/pdf/contract/[id] — PDF renderData collected: title, client name/email, clauses array (JSON), status, signature name, signed timestamp.
Purpose: Create line-item invoices and collect payment via Stripe.
Frontend: app/(app)/invoices/ and app/(app)/finance/?tab=invoices
InvoicesClient.jsx — List; filter by status (draft/sent/paid/overdue/cancelled); searchnew/InvoiceBuilderClient.jsx — Line item builder; tax rate; discount (flat/percent); currency; due date; payment plan option[id]/page.jsx — Invoice detail view[id]/InvoiceActions.jsx — Send, mark paid manually, download PDF, copy payment link[id]/edit/ — Edit invoiceBackend:
GET/POST /api/invoices — List/create; lineItems stored as JSON stringPATCH/DELETE /api/invoices/[id] — Update/deletePOST /api/invoices/bulk-send — Send multiple invoice emails at oncePOST /api/invoices/checkout — Create Stripe Checkout session (see Stripe Integration)GET /api/pdf/invoice/[id] — PDF renderPOST /api/webhooks/stripe — Mark invoice paid; create notification; send payment confirmation emailInvoice status lifecycle:
draft → sent → paid
→ overdue (if past due date)
→ cancelled
Data collected: invoice number, project, line items (description, quantity, unit price), subtotal, tax rate, discount type/value, total, currency, due date, notes, payment plan instalments.
Purpose: Financial overview — revenue, expenses, profit. Track individual expenses and recurring costs.
Frontend: app/(app)/finance/
page.jsx — Tabbed layout: Overview · Invoices · Payments · ExpensesExpensesClient.jsx — Expense list; filter by category/date; link to projectAddExpenseForm.jsx — Create/edit expenseExpenseCategoriesManager.jsx — Custom expense category managementBackend:
GET /api/finance — Aggregated revenue/expenses/profit with time period filterGET/POST /api/expenses — List/create expensesPATCH/DELETE /api/expenses/[id] — Update/delete expensePOST /api/expense-categories — Create custom categoryGET/POST /api/recurring-expenses — Manage recurring expenses; auto-creates expense entries on load if nextDate has passedData collected: description, amount, category, date, receipt (file path), project link, recurring flag, frequency.
Purpose: Task management across projects or standalone.
Frontend: app/(app)/tasks/TasksClient.jsx
Shared utilities: app/(app)/tasks/taskUtils.js
PRIORITY_DOT, STATUS_OPTIONS, STATUS_PILL, makeSubtask(), cleanSubtasks(), isOverdue()TasksClient and ProjectTasksSection for consistent UIBackend:
GET/POST /api/tasks — List/create tasks; subtasks stored as JSON stringPATCH /api/tasks/[id] — Update status, priority, assignee, subtasksDELETE /api/tasks/[id] — Delete taskPOST /api/tasks/ai-generate — Stream AI-generated task list from project description (OpenAI)Data collected: title, description, project, assignee (team member), priority (low/medium/high), due date, status (todo/in_progress/done), subtasks (JSON array with id, title, done).
Purpose: Log time spent on projects; track billable hours.
Frontend: app/(app)/time-tracker/TimeTrackerClient.jsx
Backend:
GET/POST /api/time-entries — List/create entriesPATCH/DELETE /api/time-entries/[id] — Update/deleteData collected: project, description, start time, end time, duration (minutes), billable (boolean), hourly rate.
Purpose: Let clients book meetings by sharing a public booking page.
Frontend:
app/(app)/scheduler/SchedulerClient.jsx — Set availability rules (days + hours); view upcoming bookingsapp/(app)/calendar/page.jsx — Calendar view of all bookings and entriesapp/book/[userId]/BookingForm.jsx — Public booking form (3-column layout: info panel · calendar · time slots/form)Public booking flow:
/book/[userId] linkPOST /api/bookings creates booking; sends confirmation emailBackend:
GET/POST /api/bookings — List/create bookingsPATCH/DELETE /api/bookings/[id] — Update/cancel bookingPOST /api/scheduler/availability — Save availability rules per day of weekSlot generation logic (BookingForm.jsx → generateSlots()):
Data collected: client name, client email, start time, end time, title, notes, status (confirmed/cancelled).
Purpose: Reusable service catalogue for quick invoice/proposal line-item population.
Frontend: app/(app)/services/ServicesManager.jsx
Backend:
GET/POST /api/services — List/createPATCH/DELETE /api/services/[id] — Update/delete; status field (active/archived)Data collected: name, description, default rate, unit (flat/hourly), status.
Purpose: Invite collaborators to the business workspace.
Frontend: app/(app)/settings/page.jsx — Team section
Backend:
GET/POST /api/settings/team — List members; send invite email with unique tokenPATCH /api/settings/team/[id] — Update role/permissionsDELETE /api/settings/team/[id] — Remove memberPOST /api/join — Accept invite; creates user account linked to businessInvite flow:
POST /api/settings/team/join?token=[inviteToken]app/join/page.jsx — sets password, creates accountTeamMember.status updated to "active"Roles: collaborator, admin, contractor
Permissions (JSON array): granular per-module flags e.g. "canViewInvoices", "canManageTasks"
Purpose: Share a single read-only link with clients so they can track progress, download files, and pay invoices — without needing a login.
URL: /p/[portalToken]
Auth: None — access is controlled by the unique, unguessable portal token (nanoid)
Frontend: app/p/[token]/ClientPortal.jsx
visibleToClient: truevisibleToClient: trueBackend behaviour on portal load:
portalTokenviewCount on projectlastViewedAtSecurity: Portal tokens are generated with nanoid (21 characters, ~126 bits of entropy). Without the token, no project data is accessible. Tokens can be regenerated by the freelancer to revoke access.
Purpose: Alert freelancers to important events in real time.
Frontend: components/shared/TopBar.jsx
Backend:
GET /api/notifications — Returns { unreadCount, notifications[] }PATCH /api/notifications — Marks all as readNotification triggers:
| Event | Type |
|---|---|
| Client viewed portal | portal_viewed |
| Invoice paid via Stripe | invoice_paid |
| Team invite sent | team_invite |
| Proposal accepted | proposal_accepted |
| Contract signed | contract_signed |
Purpose: Customise the look of exported PDFs (invoices, proposals, contracts).
Frontend: app/(app)/settings/pdf-templates/
Backend:
GET/POST /api/pdf-templates — List/create templatesPATCH/DELETE /api/pdf-templates/[id] — Update/deletePOST /api/pdf-templates/[id]/set-default — Sets isDefault: true for this type, unsets othersTemplate fields: paper size, orientation, margins, accent colour, logo, font family/size, header style, footer text, page numbers, tax column visibility, watermark, terms text, signature block.
Settings → Payments supports Stripe payments, direct bank transfer, or both. New Stripe accounts use Express onboarding; existing Standard accounts use a user-bound OAuth flow. Direct bank transfers use saved bank details and unique invoice references, with confirmed partial receipts recorded by the freelancer.
See freelancer payment setup, migrations, verification and rollback. The OAuth callback is /api/settings/stripe/oauth/callback and must be registered in the matching Stripe Connect application. STRIPE_CONNECT_CLIENT_ID and the legacy STRIPE_CLIENT_ID environment variable are supported.
A private portal token authorizes checkout for an invoice or milestone. Checkout locks the invoice, expires superseded payment links and charges only the remaining balance in currency minor units. Payments route to the connected freelancer account with the plan-based SoloPad platform fee.
A verified paid Stripe webhook (or an exact paid-session lookup on return) records one receipt and updates the balance. Delayed payments remain pending until confirmed. Bank transfers use the same receipt history after the freelancer confirms funds arrived. Repeated requests and webhook deliveries do not credit a payment twice.
All transactional emails are sent via Resend.
| Trigger | Template |
|---|---|
| Invoice sent | Invoice with payment link |
| Payment received | Payment confirmation to freelancer |
| Proposal sent | Proposal with view link |
| Contract sent | Contract with signing link |
| Team invite | Invite with join link |
| Booking confirmed | Booking details to client |
Environment variable: RESEND_API_KEY=re_...
Powered by the Vercel AI SDK with OpenAI as the provider.
| Feature | Route | Description |
|---|---|---|
| AI Task Generator | POST /api/tasks/ai-generate | Streams a list of tasks from a project description |
| Proposal Drafter | POST /api/proposals/draft | Generates proposal sections from project brief |
| Contract Drafter | POST /api/contracts/draft | Generates contract clauses from project description |
Environment variable: OPENAI_API_KEY=sk-...
Business ─┬─ User (owner)
├─ User[] (members via TeamMember)
├─ Contact[]
├─ Project[]
├─ Invoice[]
├─ Task[]
├─ Expense[]
├─ Service[]
├─ Booking[]
└─ Notification[]
Project ──┬─ Contact (optional)
├─ File[]
├─ Note[]
├─ Comment[]
├─ Invoice[]
├─ Task[]
├─ TimeEntry[]
├─ Proposal[]
└─ Contract[]
Invoice ──┬─ Project
└─ PaymentPlan[]
Task ─────┬─ Project (optional)
└─ TeamMember (assignee, optional)
These fields are stored as JSON strings in the database and parsed on read:
| Model | Field | Shape |
|---|---|---|
| Invoice | lineItems | [{ description, quantity, unitPrice }] |
| Task | subtasks | [{ id, title, done }] |
| Proposal | sections | [{ title, body (HTML) }] |
| Proposal | pricing | [{ description, quantity, unitPrice }] |
| Contract | clauses | [{ title, body (HTML) }] |
| TeamMember | permissions | ["canViewInvoices", ...] |
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/register | Register new user + business |
| POST | /api/auth/[...nextauth] | NextAuth sign-in / sign-out / session |
| Method | Path | Description |
|---|---|---|
| GET | /api/contacts | List contacts (q, status, page) |
| POST | /api/contacts | Create contact |
| GET | /api/contacts/[id] | Get contact + projects |
| PATCH | /api/contacts/[id] | Update contact |
| DELETE | /api/contacts/[id] | Delete contact |
| POST | /api/contacts/bulk | Bulk update status / bulk delete |
| POST | /api/contacts/import | CSV import |
| POST | /api/contacts/export | CSV export |
| Method | Path | Description |
|---|---|---|
| GET | /api/projects | List projects |
| POST | /api/projects | Create project |
| PATCH | /api/projects/[id] | Update project |
| DELETE | /api/projects/[id] | Delete project (cascades) |
| Method | Path | Description |
|---|---|---|
| GET | /api/invoices | List invoices |
| POST | /api/invoices | Create invoice |
| PATCH | /api/invoices/[id] | Update invoice |
| DELETE | /api/invoices/[id] | Delete invoice |
| POST | /api/invoices/checkout | Create Stripe checkout session |
| POST | /api/invoices/bulk-send | Send multiple invoice emails |
| POST | /api/webhooks/stripe | Stripe payment webhook |
| Method | Path | Description |
|---|---|---|
| GET/POST | /api/proposals | List / create proposal |
| PATCH/DELETE | /api/proposals/[id] | Update / delete |
| POST | /api/proposals/draft | AI draft generation |
| POST | /api/proposals/[id]/send | Send proposal email |
| GET/POST | /api/contracts | List / create contract |
| PATCH/DELETE | /api/contracts/[id] | Update / delete |
| POST | /api/contracts/draft | AI draft generation |
| POST | /api/contracts/[id]/send | Send contract email |
| Method | Path | Description |
|---|---|---|
| GET/POST | /api/tasks | List / create task |
| PATCH/DELETE | /api/tasks/[id] | Update / delete task |
| POST | /api/tasks/ai-generate | Stream AI task generation |
| GET/POST | /api/time-entries | List / create time entry |
| PATCH/DELETE | /api/time-entries/[id] | Update / delete |
| Method | Path | Description |
|---|---|---|
| GET | /api/finance | Revenue / expense / profit aggregation |
| GET/POST | /api/expenses | List / create expense |
| PATCH/DELETE | /api/expenses/[id] | Update / delete |
| POST | /api/expense-categories | Create category |
| GET/POST | /api/recurring-expenses | List / create recurring expense |
| Method | Path | Description |
|---|---|---|
| GET/PATCH | /api/settings/profile | User profile |
| GET/PATCH | /api/settings/business | Business details |
| GET/PATCH | /api/settings/payments | Payment method settings |
| GET/PATCH | /api/settings/plan | Plan management |
| GET/POST | /api/settings/team | Team members |
| PATCH/DELETE | /api/settings/team/[id] | Update / remove team member |
| GET | /api/settings/stripe/connect | Start Stripe Connect OAuth |
| GET | /api/settings/stripe/callback | Stripe Connect OAuth callback |
| GET | /api/settings/stripe/status | Check connection status |
| POST | /api/settings/stripe/disconnect | Disconnect Stripe account |
| Method | Path | Description |
|---|---|---|
| GET | /api/pdf/invoice/[id] | Generate invoice PDF |
| GET | /api/pdf/proposal/[id] | Generate proposal PDF |
| GET | /api/pdf/contract/[id] | Generate contract PDF |
| GET/POST | /api/pdf-templates | List / create PDF template |
| POST | /api/pdf-templates/[id]/set-default | Set default template |
| GET | /api/notifications | Fetch notifications |
| PATCH | /api/notifications | Mark all as read |
| Method | Path | Description |
|---|---|---|
| GET/POST | /api/bookings | List / create booking |
| PATCH/DELETE | /api/bookings/[id] | Update / cancel booking |
| POST | /api/scheduler/availability | Save availability rules |
session.user exists before processing; returns 401 if notNEXTAUTH_SECRET; tampering invalidates the tokenbusinessId or userId via getTenantFilter()POST /api/webhooks/stripe verifies the Stripe-Signature header using stripe.webhooks.constructEvent()400visibleToClient: true are exposedstripeOnboarded flag is set only after charges_enabled is confirmed from StripeNever commit these to version control:
NEXTAUTH_SECRET — must be a random 32+ character string in productionSTRIPE_SECRET_KEY — server-side only; never exposed to the clientSTRIPE_WEBHOOK_SECRET — used to verify webhook authenticityDATABASE_URL — contains DB credentialsnpm install
Copy the example and fill in your values:
cp .env.local.example .env.local
See Environment Variables below for all required values.
npx prisma migrate dev --name init
npm run dev
npx prisma studio
# App
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="change-me-to-a-random-32-char-secret"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# Database (PostgreSQL — Neon, Supabase, Railway, or local)
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DB?sslmode=require&pgbouncer=true"
DIRECT_URL="postgresql://USER:PASSWORD@HOST:5432/DB?sslmode=require"
DATABASE_URL_UNPOOLED="postgresql://USER:PASSWORD@HOST:5432/DB?sslmode=require"
# Stripe — https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY=sk_test_...
# Stripe Connect Client ID — https://dashboard.stripe.com/settings/connect
STRIPE_CLIENT_ID=ca_...
# Webhook secret — run: stripe listen --forward-to localhost:3000/api/webhooks/stripe
STRIPE_WEBHOOK_SECRET=whsec_...
# Email — https://resend.com/api-keys
RESEND_API_KEY=re_...
# AI (optional — required for AI task/proposal generation)
OPENAI_API_KEY=sk-...
npx vercel
Or connect your GitHub repo to Vercel and set environment variables in the dashboard.
Important: Update these for production:
NEXTAUTH_URL → your production domainNEXT_PUBLIC_APP_URL → your production domainSTRIPE_WEBHOOK_SECRET → re-run stripe listen or set up a production webhook endpoint in Stripe DashboardUse a managed PostgreSQL provider:
For production, replace local /uploads/ with S3 or Cloudflare R2. Update the file upload and download routes in app/api/files/.
npm run dev # Start dev server
npm run build # Production build
npm run lint # Lint check
npx prisma studio # Browse database
npx prisma migrate dev --name <name> # Create migration
npx prisma db push # Push schema without migration
npx prisma generate # Regenerate Prisma client
stripe listen --forward-to localhost:3000/api/webhooks/stripe # Forward Stripe webhooks locally