A modern, production-ready React application for managing customer data with bulk import capabilities. Built with React 19, Vite, Tailwind CSS, and Axios.
- Node.js 16+
- npm or yarn
- Backend API running on
http://localhost:8080
# 1. Clone and navigate to project
cd cms-frontend
# 2. Install dependencies
npm install
# 3. Start development server
npm run devThe app will open at http://localhost:5173 (or 5174 if 5173 is busy).
cms-frontend/
βββ src/
β βββ components/
β β βββ AddCustomer.jsx # Add/Edit/Search customer form
β β βββ CustomerList.jsx # Display, search, pagination
β β βββ BulkUpload.jsx # File upload & template download
β βββ App.jsx # Main router & state management
β βββ main.jsx # React entry point
β βββ index.css # Global styles
β βββ App.css # App-specific styles
βββ public/ # Static files
βββ package.json # Dependencies
βββ vite.config.js # Build configuration
βββ eslint.config.js # Linting rules
βββ index.html # HTML entry point
- β Add new customers with complete data
- β Edit existing customers by NIC
- β Search customers by NIC
- β View all customers with pagination
- β Delete customers
- π± Multiple mobile numbers per customer
- π¨βπ©βπ§ Family members support (name, DOB, NIC)
- π Multiple addresses with city/country selection
- π Cascading dropdowns (Country β City)
- βοΈ Form validation & error handling
- β±οΈ Auto-dismissing success messages
- π€ Drag-and-drop file upload
- π Support for
.xlsx,.xls,.csvformats - π Template download (CSV format)
- π Progress tracking during upload
- π No file size limits (backend configurable)
- πΎ Batch import thousands of records
- π¨ Clean, modern Tailwind CSS UI
- π± Responsive design
- β‘ Real-time validation
- π Loading states
- π’ Clear error messages
- π CORS-enabled for cross-origin requests
npm run dev- Hot Module Replacement (HMR) enabled
- http://localhost:5173
npm run build
npm run previewnpm run lintBase URL: http://localhost:8080/api/v1
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /customers/GetAll?page=0&size=10 |
Fetch all customers with pagination |
| GET | /customers/GetByNic/{NIC} |
Get customer by NIC |
| POST | /customers/Add |
Create new customer |
| PUT | /customers/Update/{NIC} |
Update customer by NIC |
| DELETE | /customers/Delete/{NIC} |
Delete customer |
| POST | /customers/bulk-upload |
Bulk upload customers (multipart form-data) |
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /master/countries |
List all countries |
| GET | /master/cities/{countryName} |
Get cities by country name |
For proper file upload functionality, configure your Spring Boot backend:
server:
port: 8080
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
tomcat:
max-http-post-size: 1000000000
max-http-request-header-size: 1000000
spring:
web:
cors:
allowed-origins: http://localhost:5173,http://localhost:5174
allowed-methods: GET,POST,PUT,DELETE,OPTIONS,PATCH
allowed-headers: '*'
allow-credentials: true
max-age: 3600- Enter valid NIC (not existing)
- Fill name, DOB
- Add multiple mobile numbers
- Select country & city
- Add address details
- Add family member
- Submit form
- Verify success message
- Enter existing NIC
- Click search
- Verify data loads
- Edit a field
- Submit update
- Verify NIC field is read-only during edit
- View all customers
- Test pagination (page size dropdown)
- Search by name/NIC/mobile
- Click Edit button (should navigate to AddCustomer)
- Click Delete button (should remove from list)
- Download CSV template
- Create test CSV with sample data
- Drag-drop file
- Verify progress bar shows
- Confirm file accepted
{
"nic": "199901234567",
"name": "John Doe",
"dob": "1999-01-15",
"mobileNumbers": ["0771234567", "0712345678"],
"familyMembers": [
{
"name": "Jane Doe",
"nic": "199903214567",
"dob": "1999-03-20",
"mobileNumbers": ["0779876543"],
"addresses": []
}
],
"addresses": [
{
"id": null,
"addressLine1": "123 Main Street",
"addressLine2": "Apt 4",
"city": {
"id": 1,
"name": "Colombo",
"country": {
"id": 1,
"name": "Sri Lanka",
"code": "LK"
}
},
"country": {
"id": 1,
"name": "Sri Lanka",
"code": "LK"
}
}
]
}Important Notes:
β οΈ cityobject must include nestedcountryobjectβ οΈ Cities endpoint usescountry.name(string), notcountry.id(number)β οΈ Family members are full CustomerDTO objects, not just stringsβ οΈ Mobile numbers must be string array, not objects
Purpose: Add new or edit existing customer
Props:
{
selectedCustomer: Object|null, // Customer object when editing
onCloseEdit: Function // Callback to close edit mode
}State Management:
step: Controls SEARCH or FORM displayisEditMode: Boolean flag for edit vs. addcustomerData: Form state objectcountries: Dropdown options
Key Methods:
fetchCountries()- Load country master datahandleSearch()- Search customer by NIChandleAddressCountryChange()- Cascading city dropdownhandleSubmit()- Save/update customer
Purpose: Display customers with pagination and search
Props:
{
onEditCustomer: Function // Called when Edit button clicked
}Features:
- Pagination (10 items per page, configurable)
- Search across name, NIC, mobile number
- Edit/Delete action buttons
- Mock data fallback when API unavailable
Key Methods:
fetchCustomers()- API call to /GetAllhandleSearch()- Client-side filteringhandleEdit()- Emit selected customer to parent
Purpose: Upload multiple customers from Excel/CSV
Accepted Formats:
.xlsx- Excel 2007+.xls- Legacy Excel.csv- Comma-separated values
Features:
- Drag-and-drop upload zone
- File validation (type, not size)
- Progress bar during upload
- Template CSV download
- Error/success notifications
Expected CSV Columns:
NIC,Name,DOB,MobileNumbers,AddressLine1,AddressLine2,City,Country
199901234567,John Doe,1999-01-15,"0771234567,0712345678",123 Main St,Apt 4,Colombo,Sri Lanka
Edit in each component (AddCustomer.jsx, CustomerList.jsx, BulkUpload.jsx):
const API_BASE_URL = 'http://localhost:8080/api/v1';Edit in CustomerList.jsx:
const PAGE_SIZE = 10; // Items per pageEdit in components:
setTimeout(() => { /* action */ }, 2000); // 2 secondsCross-Origin Request Blocked: CORS header missing
Solution: Enable CORS in backend application.yml (see Backend Configuration section)
SizeLimitExceededException: size exceeds configured maximum
Solution:
- Update
server.servlet.multipart.max-file-sizein backend - Restart backend service
- Try again
Cause: Backend cities endpoint needs country NAME, not ID
Already Fixed: Frontend sends country.name to /master/cities/{countryName}
Cause: Backend endpoint doesn't exist or returns wrong structure
Check:
curl http://localhost:8080/api/v1/master/countries- react (19.2.5) - UI framework
- axios (1.15.2) - HTTP client
- tailwindcss (4.2.4) - Utility CSS
- vite (8.0.10) - Build tool
- react-router-dom (7.14.2) - Routing (if needed)
- Create component in
src/components/ - Import in
App.jsx - Add route/navigation
- Test with mock data first
- Integrate with API endpoints
- Update
API_BASE_URLconstant - Update
axioscalls with new URL - Adjust error handling if response structure changes
- Uses Tailwind CSS v4 utility classes
- Custom colors defined in component className
- Mobile-first responsive design
- Backend running on http://localhost:8080
- Frontend running on http://localhost:5173
- Dependencies installed:
npm install - No CORS errors in browser console
- Can view customer list
- Can add new customer
- Can search and edit customer
- Can upload file
- Pagination works
- Search filter works
For issues or questions:
- Check browser console for errors
- Check backend logs
- Verify API endpoints are accessible
- Confirm database is running
- Review Backend Configuration section
This project is part of the CMS Frontend initiative.
Built with β€οΈ using React + Vite + Tailwind CSS