Skip to content

Commit 1234359

Browse files
committed
feat: add W-2 and Offer Letter document types, update README
Document Types (now 5): - Payslip (employee/contractor) - Tax Form (withholding statement) - W-2 (wage and tax statement) - Employment Letter (verification) - Offer Letter (job offer) README Updates: - Added all new features documentation - Added Employee/Contractor modes info - Updated usage guide - Added Student ID Generator link
1 parent cfc73c5 commit 1234359

File tree

3 files changed

+199
-32
lines changed

3 files changed

+199
-32
lines changed

README.md

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,47 @@
66
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support%20my%20work-FFDD00?style=flat&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/thanhnguyxn)
77
![License](https://img.shields.io/badge/license-MIT-blue.svg)
88

9-
**A professional, dynamic payslip generator built with React and Vite.**
10-
Create, customize, and print payslips instantly.
9+
**A professional, dynamic payslip & employment document generator built with React and Vite.**
10+
Create, customize, and print payslips, tax forms, and employment letters instantly.
1111

12-
[**🚀 Live Demo**](https://thanhnguyxn.github.io/payslip-generator/)
12+
[**🚀 Live Demo**](https://thanhnguyxn.github.io/payslip-generator/) | [**🎓 Student ID Generator**](https://thanhnguyxn.github.io/student-card-generator/)
1313

1414
</div>
1515

1616
---
1717

1818
> [!WARNING]
19-
> **Disclaimer**: This project is intended for **educational and research purposes only**. The payslips generated by this tool are mock documents and must not be used for official financial, legal, or employment verification purposes. The author assumes no responsibility for any misuse of this application.
19+
> **Disclaimer**: This project is intended for **educational and research purposes only**. The documents generated by this tool are mock documents and must not be used for official financial, legal, or employment verification purposes.
2020
2121
## ✨ Features
2222

23-
- **📝 Dynamic Editor**: Easily update company, employee, and pay period details with a user-friendly interface.
24-
- **💰 Flexible Earnings & Deductions**: Add unlimited rows for earnings and deductions. Totals are calculated automatically!
25-
- **👀 Real-time Preview**: See the payslip update instantly as you type. No need to refresh.
26-
- **🖨️ Print & PDF Export**: Built-in functionality to print or save the payslip as a professional PDF.
27-
- **🎨 Professional Design**: Clean, modern layout suitable for official-looking mockups.
28-
- **🎲 Random Data Generator**: Quickly fill the form with sample data for testing purposes.
23+
### 📄 Multiple Document Types
24+
- **Payslip** - Standard employee pay statement
25+
- **Tax Form** - Tax withholding statement
26+
- **W-2 Form** - Wage and tax statement (US format)
27+
- **Employment Letter** - Employment verification letter
28+
- **Offer Letter** - Job offer letter with terms
29+
30+
### 👥 Employee & Contractor Modes
31+
- **Employee Mode** - Full-time employee documents with payslip, benefits
32+
- **Contractor Mode** - Independent contractor invoices with service agreements
33+
34+
### 🎨 Modern UI/UX
35+
- **Dark Sidebar** - Professional dark theme editor panel
36+
- **Gradient Preview** - Beautiful purple gradient preview area
37+
- **Zoom Controls** - 30%-150% zoom with smooth transitions
38+
- **Drag & Pan** - Freely drag documents in preview
39+
- **Company Logo** - Upload and display company logo on all documents
40+
- **Responsive Design** - Works on desktop, tablet, and mobile
41+
42+
### 💰 Dynamic Calculations
43+
- **Flexible Earnings & Deductions** - Add unlimited rows
44+
- **Automatic Totals** - Net pay calculated in real-time
45+
- **Tax Calculations** - Federal, state, Social Security, Medicare
46+
47+
### 🖨️ Export Options
48+
- **Print/PDF** - Built-in print dialog for saving as PDF
49+
- **Professional Layout** - Clean, A4-sized documents
2950

3051
## 🛠️ Tech Stack
3152

@@ -35,42 +56,40 @@ Create, customize, and print payslips instantly.
3556

3657
## 📖 Usage Guide
3758

38-
### 1. Editing Details
39-
- **🏢 Company Information**: Enter your company name, address, and contact details in the "Company Details" section.
40-
- **👤 Employee Information**: Fill in the employee's name, ID, department, and designation.
41-
- **📅 Pay Period**: Select the start and end dates for the pay period.
59+
### Quick Start
60+
1. **Select Mode** - Choose Employee or Contractor in the nav bar
61+
2. **Select Document Type** - Click Payslip, Tax Form, W-2, Employment, or Offer Letter
62+
3. **Fill Details** - Use the sidebar to enter company, employee, and pay info
63+
4. **Upload Logo** - Add your company logo (optional)
64+
5. **Random Data** - Click 🎲 for sample data
65+
6. **Download** - Click 📥 Download PDF to save
4266

43-
### 2. Managing Earnings & Deductions
44-
- **➕ Add Item**: Click the "+ Add Item" button under Earnings or Deductions to add a new row.
45-
- **🗑️ Remove Item**: Click the "Remove" button next to a row to delete it.
46-
- **🧮 Automatic Calculation**: The Total Earnings, Total Deductions, and Net Pay are calculated automatically as you modify the values.
47-
48-
### 3. Generating the Payslip
49-
- **👁️ Preview**: The right side of the screen (or bottom on mobile) shows the real-time preview of the payslip.
50-
- **🎲 Random Data**: Click the **"Fill Random Data"** button to populate the form with sample data for testing.
51-
- **💾 Print/Save**: Click the **"Download PDF / Print"** button. This will open your browser's print dialog where you can select "Save as PDF" or print to a physical printer.
67+
### Navigation
68+
- **Document Tabs** - Switch between 5 document types
69+
- **Mode Toggle** - Employee documents vs Contractor invoices
70+
- **Student ID Link** - Quick access to Student ID Generator app
5271

5372
## 💻 Development
5473

55-
### Install Dependencies
5674
```bash
75+
# Install dependencies
5776
npm install
58-
```
5977

60-
### Start Development Server
61-
```bash
78+
# Start dev server
6279
npm run dev
63-
```
6480

65-
### Build for Production
66-
```bash
81+
# Build for production
6782
npm run build
6883
```
6984

7085
## 🤝 Support
7186

72-
If you find this project useful or interesting, please consider buying me a coffee! It helps keep the motivation high 🚀
87+
If you find this project useful, please consider:
7388

7489
<div align="center">
7590
<a href="https://buymeacoffee.com/thanhnguyxn" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
7691
</div>
92+
93+
## 📝 License
94+
95+
MIT License - feel free to use for educational purposes.

src/App.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,23 @@ function App() {
9595
>
9696
Tax Form
9797
</button>
98+
<button
99+
className={`tab-btn ${docType === 'w2' ? 'active' : ''}`}
100+
onClick={() => setDocType('w2')}
101+
>
102+
W-2
103+
</button>
98104
<button
99105
className={`tab-btn ${docType === 'employment' ? 'active' : ''}`}
100106
onClick={() => setDocType('employment')}
101107
>
102-
Employment Letter
108+
Employment
109+
</button>
110+
<button
111+
className={`tab-btn ${docType === 'offer' ? 'active' : ''}`}
112+
onClick={() => setDocType('offer')}
113+
>
114+
Offer Letter
103115
</button>
104116
</div>
105117

@@ -116,6 +128,15 @@ function App() {
116128
>
117129
📥 Download PDF
118130
</button>
131+
<a
132+
href="https://thanhnguyxn.github.io/student-card-generator/"
133+
target="_blank"
134+
rel="noopener noreferrer"
135+
className="action-btn"
136+
style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)', textDecoration: 'none' }}
137+
>
138+
🎓 Student ID
139+
</a>
119140
</div>
120141
</nav>
121142

src/components/Preview.jsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,130 @@ const Preview = ({ state, docType = 'payslip', mode = 'employee', companyLogo })
235235
</>
236236
);
237237

238+
// W-2 Tax Form
239+
const renderW2 = () => (
240+
<>
241+
<header className="payslip-header-centered">
242+
{companyLogo && <img src={companyLogo} alt="Logo" className="company-logo" />}
243+
<h1 style={{ fontSize: '1.5rem', color: '#0f4c81' }}>Form W-2 Wage and Tax Statement</h1>
244+
<p style={{ fontSize: '0.8rem', color: '#666' }}>Copy B — To Be Filed With Employee's FEDERAL Tax Return</p>
245+
</header>
246+
247+
<hr className="divider-blue" />
248+
249+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginBottom: '30px' }}>
250+
<div style={{ border: '1px solid #ccc', padding: '15px' }}>
251+
<div style={{ fontSize: '0.7rem', color: '#666', marginBottom: '5px' }}>a. Employee's social security number</div>
252+
<div style={{ fontWeight: 'bold' }}>XXX-XX-{employee.employeeId?.slice(-4) || '1234'}</div>
253+
</div>
254+
<div style={{ border: '1px solid #ccc', padding: '15px' }}>
255+
<div style={{ fontSize: '0.7rem', color: '#666', marginBottom: '5px' }}>b. Employer identification number (EIN)</div>
256+
<div style={{ fontWeight: 'bold' }}>XX-XXXXXXX</div>
257+
</div>
258+
</div>
259+
260+
<div style={{ border: '1px solid #ccc', padding: '15px', marginBottom: '20px' }}>
261+
<div style={{ fontSize: '0.7rem', color: '#666', marginBottom: '5px' }}>c. Employer's name, address, and ZIP code</div>
262+
<div style={{ fontWeight: 'bold' }}>{company.name}</div>
263+
<div>{company.address}</div>
264+
</div>
265+
266+
<div style={{ border: '1px solid #ccc', padding: '15px', marginBottom: '20px' }}>
267+
<div style={{ fontSize: '0.7rem', color: '#666', marginBottom: '5px' }}>e. Employee's name, address, and ZIP code</div>
268+
<div style={{ fontWeight: 'bold' }}>{employee.name}</div>
269+
<div>{employee.address}</div>
270+
</div>
271+
272+
<table className="payslip-table-modern" style={{ marginBottom: '20px' }}>
273+
<thead><tr><th>Box</th><th>Description</th><th className="col-right">Amount</th></tr></thead>
274+
<tbody>
275+
<tr><td>1</td><td>Wages, tips, other compensation</td><td className="col-right">{formatCurrency(totalEarnings * 12)}</td></tr>
276+
<tr><td>2</td><td>Federal income tax withheld</td><td className="col-right">{formatCurrency(totalEarnings * 12 * 0.22)}</td></tr>
277+
<tr><td>3</td><td>Social security wages</td><td className="col-right">{formatCurrency(totalEarnings * 12)}</td></tr>
278+
<tr><td>4</td><td>Social security tax withheld</td><td className="col-right">{formatCurrency(totalEarnings * 12 * 0.062)}</td></tr>
279+
<tr><td>5</td><td>Medicare wages and tips</td><td className="col-right">{formatCurrency(totalEarnings * 12)}</td></tr>
280+
<tr><td>6</td><td>Medicare tax withheld</td><td className="col-right">{formatCurrency(totalEarnings * 12 * 0.0145)}</td></tr>
281+
</tbody>
282+
</table>
283+
284+
<div style={{ fontSize: '0.75rem', color: '#666', marginTop: '30px' }}>
285+
<p>This information is being furnished to the Internal Revenue Service.</p>
286+
<p style={{ marginTop: '10px' }}>Tax Year: {new Date().getFullYear()}</p>
287+
</div>
288+
</>
289+
);
290+
291+
// Offer Letter
292+
const renderOfferLetter = () => (
293+
<>
294+
<header style={{ marginBottom: '30px' }}>
295+
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}>
296+
{companyLogo && <img src={companyLogo} alt="Logo" className="company-logo" style={{ marginRight: '20px' }} />}
297+
<div>
298+
<h2 style={{ margin: 0, color: '#0f4c81' }}>{company.name.toUpperCase()}</h2>
299+
<div style={{ fontSize: '0.85rem', color: '#555' }}>{company.address}</div>
300+
</div>
301+
</div>
302+
</header>
303+
304+
<div style={{ textAlign: 'right', marginBottom: '30px', fontSize: '0.9rem' }}>
305+
Date: {today}
306+
</div>
307+
308+
<div style={{ marginBottom: '20px' }}>
309+
<strong>{employee.name}</strong><br />
310+
{employee.address}
311+
</div>
312+
313+
<h1 style={{ textAlign: 'center', fontSize: '1.3rem', marginBottom: '30px', color: '#0f4c81' }}>
314+
OFFER OF EMPLOYMENT
315+
</h1>
316+
317+
<div style={{ lineHeight: 1.8, fontSize: '0.95rem' }}>
318+
<p>Dear {employee.name},</p>
319+
320+
<p style={{ marginTop: '20px' }}>
321+
We are pleased to offer you the position of <strong>{employee.position}</strong> at <strong>{company.name}</strong>.
322+
We believe your skills and experience will be a valuable asset to our team.
323+
</p>
324+
325+
<p style={{ marginTop: '20px' }}><strong>Position Details:</strong></p>
326+
<ul style={{ marginLeft: '20px' }}>
327+
<li><strong>Title:</strong> {employee.position}</li>
328+
<li><strong>Start Date:</strong> {meta.payPeriodStart}</li>
329+
<li><strong>Compensation:</strong> {formatCurrency(employee.payRate * 38 * 52)} per year ({formatCurrency(employee.payRate)}/hour)</li>
330+
<li><strong>Employment Type:</strong> Full-time</li>
331+
<li><strong>Benefits:</strong> Health insurance, 401(k), Paid time off</li>
332+
</ul>
333+
334+
<p style={{ marginTop: '20px' }}>
335+
This offer is contingent upon successful completion of a background check and reference verification.
336+
</p>
337+
338+
<p style={{ marginTop: '20px' }}>
339+
Please sign and return this letter within <strong>7 business days</strong> to confirm your acceptance.
340+
</p>
341+
342+
<p style={{ marginTop: '40px' }}>We look forward to welcoming you to our team!</p>
343+
344+
<div style={{ marginTop: '40px', display: 'flex', justifyContent: 'space-between' }}>
345+
<div>
346+
<div style={{ borderTop: '1px solid #333', width: '200px', paddingTop: '5px', marginTop: '50px' }}>
347+
<strong>HR Manager</strong><br />
348+
{company.name}
349+
</div>
350+
</div>
351+
<div>
352+
<div style={{ borderTop: '1px solid #333', width: '200px', paddingTop: '5px', marginTop: '50px' }}>
353+
<strong>Acceptance Signature</strong><br />
354+
{employee.name}
355+
</div>
356+
</div>
357+
</div>
358+
</div>
359+
</>
360+
);
361+
238362
return (
239363
<div
240364
className="preview-panel"
@@ -259,10 +383,13 @@ const Preview = ({ state, docType = 'payslip', mode = 'employee', companyLogo })
259383
>
260384
{docType === 'payslip' && renderPayslip()}
261385
{docType === 'tax' && renderTaxForm()}
386+
{docType === 'w2' && renderW2()}
262387
{docType === 'employment' && renderEmploymentLetter()}
388+
{docType === 'offer' && renderOfferLetter()}
263389
</div>
264390
</div>
265391
);
266392
};
267393

268394
export default Preview;
395+

0 commit comments

Comments
 (0)