Utility Library Documentation
This directory contains core utility functions for the WorkForce Pro application. All utilities are exported through utils.ts for convenient imports.
Overview
import {
cn, // Tailwind class merger
TIMEOUTS, LIMITS, // Constants
handleError, // Error handling
sanitizeEmail, // Input sanitization
isValidEmail, // Type guards
validateEmail, // Validation
} from '@/lib/utils'
Modules
1. utils.ts (Core)
The main utility file that exports the cn function for merging Tailwind classes.
import { cn } from '@/lib/utils'
<div className={cn('base-class', isActive && 'active-class')} />
2. constants.ts
Application-wide constants to eliminate magic numbers and strings.
Available Constants:
TIMEOUTS
TIMEOUTS.LOGIN_DELAY // 800ms
TIMEOUTS.TOAST_DURATION // 3000ms
TIMEOUTS.POLLING_INTERVAL // 30000ms
TIMEOUTS.DEBOUNCE_DELAY // 300ms
TIMEOUTS.AUTO_SAVE_DELAY // 2000ms
FORMATS
FORMATS.DATE // 'yyyy-MM-dd'
FORMATS.DATE_DISPLAY // 'MMM dd, yyyy'
FORMATS.DATETIME // 'yyyy-MM-dd HH:mm:ss'
FORMATS.TIME // 'HH:mm'
DURATIONS
DURATIONS.INVOICE_DUE_DAYS // 30
DURATIONS.SESSION_TIMEOUT_MINUTES // 60
DURATIONS.PASSWORD_RESET_HOURS // 24
LIMITS
LIMITS.MAX_FILE_SIZE_MB // 10
LIMITS.MAX_BATCH_SIZE // 100
LIMITS.PAGE_SIZE // 20
LIMITS.MIN_PASSWORD_LENGTH // 8
LIMITS.INVOICE_NUMBER_PADDING // 5
Usage Example:
import { TIMEOUTS, LIMITS } from '@/lib/constants'
setTimeout(handleRefresh, TIMEOUTS.POLLING_INTERVAL)
if (file.size > LIMITS.MAX_FILE_SIZE_MB * 1024 * 1024) {
toast.error('File too large')
}
3. error-handler.ts
Centralized error handling with custom error types and utilities.
Error Classes:
import {
AppError,
ValidationError,
AuthenticationError,
NetworkError
} from '@/lib/error-handler'
throw new ValidationError('Invalid email format', { email })
throw new AuthenticationError('Session expired')
throw new NetworkError('Failed to fetch data')
Functions:
handleError
import { handleError } from '@/lib/error-handler'
try {
await riskyOperation()
} catch (error) {
handleError(error, 'Operation Name')
}
handleAsyncError
import { handleAsyncError } from '@/lib/error-handler'
const data = await handleAsyncError(
fetchUserData(id),
'Fetch User'
)
if (data === null) {
// Error was handled, show fallback UI
}
withErrorHandler
import { withErrorHandler } from '@/lib/error-handler'
const safeFunction = withErrorHandler(riskyFunction, 'Risky Operation')
logError
import { logError } from '@/lib/error-handler'
logError(error, 'Context', { userId, action: 'delete' })
4. sanitize.ts
Input sanitization to prevent XSS and ensure data integrity.
Available Functions:
Text Sanitization
import { sanitizeHTML, sanitizeSearchQuery, stripHTML } from '@/lib/sanitize'
const safe = sanitizeHTML(userInput)
const query = sanitizeSearchQuery(searchTerm)
const text = stripHTML(htmlContent)
Email & URLs
import { sanitizeEmail, sanitizeURL } from '@/lib/sanitize'
const email = sanitizeEmail(input) // Lowercase, trim, limit length
const url = sanitizeURL(input) // Validate protocol, return safe URL
Numeric Input
import { sanitizeNumericInput, sanitizeInteger } from '@/lib/sanitize'
const num = sanitizeNumericInput(value) // Returns number or null
const int = sanitizeInteger(value, 0, 100) // Clamps to min/max
Filenames & Special Fields
import {
sanitizeFilename,
sanitizePhoneNumber,
sanitizeUsername,
sanitizePostalCode
} from '@/lib/sanitize'
const filename = sanitizeFilename(upload.name)
const phone = sanitizePhoneNumber(phoneInput)
const username = sanitizeUsername(userInput)
Data Export
import { sanitizeCSVValue, truncateString } from '@/lib/sanitize'
const csvSafe = sanitizeCSVValue(cellValue)
const short = truncateString(longText, 100, '...')
5. type-guards.ts
Runtime type checking and validation guards.
Basic Guards:
import { isNotNull, isDefined, isValidDate } from '@/lib/type-guards'
if (isNotNull(value)) {
// TypeScript knows value is not null/undefined
}
if (isValidDate(dateInput)) {
// TypeScript knows it's a Date
}
Validation Guards:
import {
isValidEmail,
isValidPhoneNumber,
isValidURL,
isPositiveNumber,
isValidCurrency
} from '@/lib/type-guards'
if (isValidEmail(email)) {
// Email format is correct
}
if (isPositiveNumber(amount)) {
// Number is > 0 and finite
}
Entity Guards:
import {
isValidTimesheet,
isValidInvoice,
isValidWorker
} from '@/lib/type-guards'
if (isValidTimesheet(data)) {
// data has all required Timesheet properties
console.log(data.workerName)
}
Collection Guards:
import { isArrayOf, isRecordOf } from '@/lib/type-guards'
if (isArrayOf(data, isValidTimesheet)) {
// data is Timesheet[]
}
if (isRecordOf(data, isPositiveNumber)) {
// data is Record<string, number>
}
Property Guards:
import { hasProperty, hasProperties } from '@/lib/type-guards'
if (hasProperty(obj, 'email')) {
// obj.email exists
}
if (hasProperties(obj, ['id', 'name', 'email'])) {
// All properties exist
}
6. validation.ts
Form validation with detailed error messages.
Basic Validation:
import {
validateEmail,
validatePassword,
validateUsername,
validatePhoneNumber,
validateURL
} from '@/lib/validation'
const result = validateEmail(email)
if (!result.isValid) {
console.log(result.errors) // Array of error messages
}
Number & Date Validation:
import {
validateNumber,
validateDate,
validateDateRange
} from '@/lib/validation'
const ageValidation = validateNumber(age, 18, 120, 'Age')
const dateValidation = validateDate(startDate, 'Start Date')
const rangeValidation = validateDateRange(start, end)
File Validation:
import { validateFileSize, validateFileType } from '@/lib/validation'
const sizeCheck = validateFileSize(file, 10) // Max 10MB
const typeCheck = validateFileType(file, ['pdf', 'doc', 'docx'])
String Validation:
import { validateLength, validatePattern, validateRequired } from '@/lib/validation'
const lengthCheck = validateLength(input, 5, 50, 'Description')
const patternCheck = validatePattern(code, /^[A-Z]{3}$/, 'Invalid format')
const requiredCheck = validateRequired(value, 'Field Name')
Form Validation:
import { validateFormData, combineValidations } from '@/lib/validation'
const { isValid, errors } = validateFormData(formData, {
email: validateEmail,
password: validatePassword,
age: (value) => validateNumber(value, 18, 120, 'Age'),
})
if (!isValid) {
// errors is Record<string, string[]>
console.log(errors.email) // ['Email format is invalid']
}
Usage Patterns
Pattern 1: Form Input Handling
import { sanitizeEmail, validateEmail } from '@/lib/utils'
function handleEmailChange(e: React.ChangeEvent<HTMLInputElement>) {
const sanitized = sanitizeEmail(e.target.value)
setEmail(sanitized)
const validation = validateEmail(sanitized)
setErrors(validation.errors)
}
Pattern 2: Safe Data Processing
import { isValidTimesheet, handleError } from '@/lib/utils'
async function processData(data: unknown[]) {
try {
const validTimesheets = data.filter(isValidTimesheet)
// TypeScript knows validTimesheets is Timesheet[]
return validTimesheets
} catch (error) {
handleError(error, 'Process Data')
return []
}
}
Pattern 3: API Error Handling
import { handleAsyncError, NetworkError } from '@/lib/utils'
async function fetchData() {
const response = await handleAsyncError(
fetch('/api/data'),
'Fetch Data'
)
if (!response) {
// Error was already handled and shown to user
return null
}
return response.json()
}
Pattern 4: Complex Validation
import {
validateFormData,
validateEmail,
validateNumber,
validateRequired
} from '@/lib/utils'
const { isValid, errors } = validateFormData(formData, {
name: (v) => validateRequired(v, 'Name'),
email: validateEmail,
age: (v) => validateNumber(v, 18, undefined, 'Age'),
phone: validatePhoneNumber,
})
Pattern 5: Search Query Handling
import { sanitizeSearchQuery, LIMITS } from '@/lib/utils'
function handleSearch(query: string) {
const sanitized = sanitizeSearchQuery(query)
const results = data.filter(item =>
item.name.toLowerCase().includes(sanitized.toLowerCase())
).slice(0, LIMITS.MAX_SEARCH_RESULTS)
setResults(results)
}
Best Practices
1. Always Sanitize User Input
// ❌ Bad
setEmail(e.target.value)
// ✅ Good
setEmail(sanitizeEmail(e.target.value))
2. Validate Before Submission
// ❌ Bad
async function handleSubmit() {
await api.createUser(formData)
}
// ✅ Good
async function handleSubmit() {
const { isValid, errors } = validateFormData(formData, validators)
if (!isValid) {
setFormErrors(errors)
return
}
await api.createUser(formData)
}
3. Use Type Guards for Runtime Safety
// ❌ Bad
function processData(data: any) {
return data.map(item => item.name)
}
// ✅ Good
function processData(data: unknown) {
if (!isArrayOf(data, isValidWorker)) {
throw new ValidationError('Invalid data format')
}
return data.map(worker => worker.name)
}
4. Handle All Errors
// ❌ Bad
async function fetchData() {
const response = await fetch('/api/data')
return response.json()
}
// ✅ Good
async function fetchData() {
try {
const response = await fetch('/api/data')
if (!response.ok) {
throw new NetworkError(`Failed to fetch: ${response.statusText}`)
}
return response.json()
} catch (error) {
handleError(error, 'Fetch Data')
return null
}
}
5. Use Constants Instead of Magic Values
// ❌ Bad
setTimeout(refresh, 30000)
if (password.length < 8) { ... }
// ✅ Good
import { TIMEOUTS, LIMITS } from '@/lib/constants'
setTimeout(refresh, TIMEOUTS.POLLING_INTERVAL)
if (password.length < LIMITS.MIN_PASSWORD_LENGTH) { ... }
Testing Utilities
Each utility module should be tested independently:
// Example test
import { sanitizeEmail, validateEmail } from '@/lib/utils'
describe('Email utilities', () => {
it('sanitizes email correctly', () => {
expect(sanitizeEmail(' USER@EXAMPLE.COM ')).toBe('user@example.com')
})
it('validates email format', () => {
const result = validateEmail('invalid-email')
expect(result.isValid).toBe(false)
expect(result.errors).toContain('Email format is invalid')
})
})
Performance Considerations
- Sanitization: Called on every input change - kept lightweight
- Validation: Called on blur/submit - can be more comprehensive
- Type Guards: Used in filters/maps - optimized for speed
- Error Handlers: Include logging - may be async
Migration Guide
If you're updating existing code to use these utilities:
- Replace magic numbers with constants
- Wrap API calls in error handlers
- Add sanitization to all form inputs
- Use type guards instead of
anytypes - Replace custom validation with standard validators
Contributing
When adding new utilities:
- Add to appropriate module (or create new one)
- Export from
utils.ts - Update this README
- Add JSDoc comments
- Write tests
- Consider performance implications
Last updated: January 2025