Next.js frontend for the Proposly proposal management platform.
- Framework: Next.js 16 (App Router, TypeScript)
- Styling: Tailwind CSS v4
- Server state: TanStack Query v5
- Forms: React Hook Form + Zod
- Rich text: TipTap v3 (proposal editor with AI inline actions)
- HTTP: Axios via a typed request gateway
- Payments: Stripe.js + Paystack inline
- Node.js 20+
- Proposly backend running on
http://localhost:3001
npm installcp .env.example .env.local| Variable | Description |
|---|---|
NEXT_PUBLIC_API_URL |
Backend API base URL e.g. http://localhost:3001/api |
npm run dev # starts on http://localhost:3000
npm run build # production build
npm run lint # ESLint- Email + password registration with email verification
- Login / logout
- Forgot + reset password
- Google OAuth (one-click sign-in)
- Resend verification email from account settings
- Create, edit, delete proposals
- Link a proposal to a client
- AI generation — Claude writes the full proposal from a structured form (free tier: limited, Pro: unlimited)
- TipTap rich text editor with auto-save (1.5 s debounce)
- Inline AI editing — select any text → BubbleMenu → Rewrite / Shorten / Formal / Fix grammar (Pro)
- Polish with AI — rewrites the full document in one click (Pro)
- Send to client — emails the client a proposal summary and marks status as sent
- Export as PDF
- Status management — Draft → Sent → Accepted / Rejected
- Full CRUD — name, email, company, phone
- Client detail page with linked proposals
- Profile — name, profession, country (persisted to DB)
- Account — email verification status + resend, change password, Google link status
- Billing — plan status, AI generation usage, upgrade via Stripe (international) or Paystack (Nigeria, auto-detected by country)
- Notifications — per-preference toggles, persisted to DB
src/
├── app/
│ ├── (auth)/ # Login, register, forgot/reset password, verify email
│ ├── (protected)/ # Authenticated routes
│ │ ├── dashboard/
│ │ ├── clients/
│ │ ├── proposals/
│ │ │ ├── new/ # Multi-step proposal creation wizard
│ │ │ └── [id]/ # Proposal detail with TipTap editor
│ │ ├── settings/
│ │ └── billing/ # Stripe/Paystack callbacks
│ └── page.tsx # Landing page
├── components/
│ ├── layout/ # Sidebar, topbar
│ └── ui/ # Button, Input, Select, Modal, ProposalEditor, ...
├── hooks/
│ └── services/ # TanStack Query hooks grouped by domain
│ ├── auth/
│ ├── proposals/
│ ├── clients/
│ ├── billing/
│ ├── users/
│ └── ai/
├── interfaces/ # TypeScript types (User, Proposal, Client, ...)
├── lib/ # queryClient, localStorage helpers
├── services/
│ └── client/ # Axios gateway + one file per domain
│ ├── client-request-gateway.ts
│ ├── auth-api.ts
│ ├── proposals-api.ts
│ ├── clients-api.ts
│ ├── billing-api.ts
│ ├── users-api.ts
│ ├── ai-api.ts
│ └── index.ts # Single `clientRequest` export
└── validations/ # Zod schemas shared with React Hook Form
validations/ (Zod schemas)
↓
services/client/ (typed Axios calls, no React)
↓
hooks/services/ (TanStack Query mutations + queries)
↓
components / pages
All API calls go through clientRequest from @/services/client. Hooks are the only thing components import — never raw service calls.
The billing page shows two explicit upgrade buttons — users choose their preferred gateway:
- Pay with Stripe — card payments, international
- Pay with Paystack — Nigerian cards, bank transfer, USSD