A modern, mobile-first PDF signing application built for the real world.
SignFlow is a full-stack prototype that lets users upload PDFs, sign them digitally, and download the signed document—all through a clean, responsive interface. Built as a monorepo with a React + TypeScript frontend (powered by Vite) and a lightweight Express.js backend, it's designed to showcase solid architecture, thoughtful UX, and production-ready patterns.
- Vite - Lightning-fast dev server and build times. HMR that actually works.
- TypeScript - Type safety across the entire codebase. Fewer bugs, better DX.
- Recoil - Lightweight state management that scales. No Redux boilerplate.
- Tailwind CSS - Utility-first styling with a custom theme. Consistent spacing, colors, and responsive design without writing CSS files.
- Framer Motion - Declarative animations that feel natural, not gimmicky.
- Jest + Testing Library - Comprehensive test coverage for components and hooks.
- Express.js - Lightweight backend for PDF signing.
SignFlow/
├── .husky/ # Git hooks for code quality
│ ├── pre-commit # Runs lint-staged before commits
│ └── pre-push # Runs tests before pushing
│
├── api/ # Serverless API endpoints (Vercel)
│ ├── health.ts # Health check endpoint
│ ├── index.js # Main API handler
│ ├── middleware/ # Express middleware
│ └── tmp/ # Temporary file storage
│ ├── uploads/ # Uploaded PDFs
│ └── signed/ # Signed PDFs
│
├── public/ # Static assets
│ └── vite.svg # App favicon
│
├── src/
│ ├── assets/ # Images, fonts, etc.
│ │
│ ├── components/
│ │ ├── common/ # Reusable UI components
│ │ │ ├── Button/
│ │ │ │ ├── Button.tsx
│ │ │ │ ├── Button.test.tsx
│ │ │ │ └── index.ts
│ │ │ │
│ │ │ ├── FallBackLoader/
│ │ │ │ ├── FallBackLoader.tsx
│ │ │ │ ├── FallBackLoader.test.tsx
│ │ │ │ └── index.ts
│ │ │ │
│ │ │ ├── FileUpload/
│ │ │ │ ├── FileUpload.tsx
│ │ │ │ ├── FileUpload.test.tsx
│ │ │ │ └── index.ts
│ │ │ │
│ │ │ ├── ProgressIndicator/
│ │ │ │ ├── ProgressIndicator.tsx
│ │ │ │ ├── ProgressIndicator.test.tsx
│ │ │ │ └── index.ts
│ │ │ │
│ │ │ ├── TextField/
│ │ │ │ ├── TextField.tsx
│ │ │ │ ├── TextField.test.tsx
│ │ │ │ └── index.ts
│ │ │ │
│ │ │ └── ThemeToggle/
│ │ │ ├── ThemeToggle.tsx
│ │ │ ├── ThemeToggle.test.tsx
│ │ │ └── index.ts
│ │ │
│ │ └── layout/ # Layout components
│ │ ├── Header/
│ │ │ ├── Header.tsx
│ │ │ ├── Header.test.tsx
│ │ │ └── index.ts
│ │ │
│ │ ├── MainContent/
│ │ │ ├── MainContent.tsx
│ │ │ ├── MainContent.test.tsx
│ │ │ └── index.ts
│ │ │
│ │ └── index.tsx # Main layout wrapper with lazy loading
│ │
│ ├── hooks/ # Custom React hooks
│ │ └── useSignPdf.ts # PDF signing logic with progress tracking
│ │
│ ├── recoil/ # Recoil state management
│ │ └── atoms.ts # Global state atoms and selectors
│ │
│ ├── App.css # App-specific styles
│ ├── App.tsx # Root component with lazy loading
│ ├── App.test.tsx # App integration tests
│ ├── index.css # Global styles and Tailwind imports
│ ├── main.tsx # React entry point with Recoil provider
│ └── services.ts # API service layer (axios calls)
│
├── types/ # TypeScript type definitions
│ ├── api.ts # API request/response types
│ ├── pdf.ts # PDF metadata types
│ └── index.d.ts # Type exports
│
├── .editorconfig # Editor configuration
├── .env # Environment variables (gitignored)
├── .env.example # Example environment variables
├── .eslintrc.js # ESLint configuration
├── .gitignore # Git ignore rules
├── .nvmrc # Node version specification
├── .prettierrc # Prettier configuration
├── .vercel.json # Vercel deployment config
├── index.html # HTML entry point
├── jest.config.ts # Jest test configuration
├── jest.setup.ts # Jest setup file with mocks
├── LICENSE # CC0 1.0 Universal license
├── package.json # Dependencies and scripts
├── postcss.config.js # PostCSS configuration
├── README.md # Project documentation
├── tailwind.config.js # Tailwind CSS configuration
├── tsconfig.json # TypeScript base config
├── tsconfig.app.json # TypeScript app config
├── tsconfig.node.json # TypeScript Node config
├── vite-env.d.ts # Vite environment types
└── vite.config.ts # Vite configuration
- Lazy Loading & Code Splitting: Components are dynamically imported to reduce initial bundle size
- Responsive Design: Mobile-first approach with Tailwind CSS
- Dark Mode Support: System preference detection with manual toggle
- Drag & Drop File Upload: Intuitive file selection with visual feedback
- Progress Tracking: Real-time progress indicator during PDF signing
- Type-Safe: Full TypeScript coverage with strict mode enabled
- PDF Watermarking: Adds signer name and timestamp to every page
- File Validation: Only accepts PDF files up to 10MB
- Temporary Storage: Storing uploaded and signed files in temp folder
- CORS Enabled: Supports cross-origin requests
- Health Check:
/healthendpoint for monitoring
- Unit Tests: All common components (
Button,TextField,FileUpload, etc.) - Integration Tests: Layout components (
Header,MainContent)
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report- Route-level: Main layout lazy loaded
- Component-level: Heavy components (Framer Motion) dynamically imported
- Clone the repository
- Copy
.env.exampleto.envand configure:VITE_API_URL=http://localhost:5050
- Node version: 25.1.0
- Install dependencies:
npm install
npm run dev # Start both frontend and backend
npm run dev:frontend # Frontend only (port 5173)
npm run dev:backend # Backend only (port 5050)npm run lint # ESLint
npm run format # Prettier
npm run typecheck # TypeScript validation- Pre-commit: Runs
lint-staged(formats and lints staged files) - Pre-push: Runs full test suite to prevent broken code
userNameState; // User's name input
pdfFileState; // Uploaded PDF file
pdfFileNameState; // Derived: PDF filename
isUploadReadyState; // Derived: Ready to upload flag- Minimal boilerplate compared to Redux
- Built-in selectors for derived state
- Easy testing with
RecoilRoot - TypeScript support out of the box
- Custom Colors:
primary,primaryAccent,primaryDark, etc. - Dark Mode: Class-based (
class="dark") - Responsive: Mobile-first breakpoints
- Utilities: Extended with custom spacing and shadows
