This document outlines the refactoring improvements made to enhance code quality, maintainability, and developer experience.
Common string manipulation functions used across the application.
Functions:
slugify(text)- Converts text to URL-friendly slugstruncate(text, maxLength)- Truncates text with ellipsiscapitalize(text)- Capitalizes first letter
Usage:
import { slugify } from '../utils/stringUtils'
const slug = slugify("My Blog Post") // "my-blog-post"Centralized error handling with consistent logging and user notifications.
Functions:
handleError(error, context, showToast?)- Logs errors and shows toast notificationshandleSuccess(message, details?)- Shows success notificationswithErrorHandling(operation, context)- Wraps async operations with error handlinggetErrorMessage(error)- Extracts readable error messages
Usage:
import { handleError, handleSuccess } from '../utils/errorHandler'
try {
await someOperation()
handleSuccess('Operation completed!')
} catch (error) {
handleError(error, {
operation: 'Save Entry',
component: 'EntryEditor'
})
}Application-wide constants to replace magic strings and numbers.
Constants:
API_BASE_PATH- Base path for API endpointsCACHE_BUSTER_PARAM- Query parameter for cache bustingDEFAULT_IMAGE_SIZES- Default image size configurationsUPLOAD_TIMEOUT_MS- Upload timeoutALLOWED_IMAGE_TYPES- Allowed image MIME typesS3_REGIONS- Available S3 regionsFIELD_TYPES- Schema field typesAPP_TABS- Application tab types
Usage:
import { CACHE_BUSTER_PARAM, ALLOWED_IMAGE_TYPES } from '../constants/app'
const url = `${baseUrl}/api/data.json?${CACHE_BUSTER_PARAM}=${Date.now()}`Type-safe API response handling with type guards.
Types & Functions:
APIResponse<T>- Standard API response interfaceisSuccessResponse(response)- Type guard for successful responsesisErrorResponse(response)- Type guard for error responsesunwrapResponse(response)- Extracts result or throws errortryUnwrapResponse(response)- Safely extracts result or returns undefined
Usage:
import { isSuccessResponse, unwrapResponse } from '../types/api'
const result = await api.getData()
if (isSuccessResponse(result)) {
// TypeScript knows result.result exists
console.log(result.result)
}
// Or use unwrap for cleaner code
try {
const data = unwrapResponse(result)
console.log(data)
} catch (error) {
console.error(error)
}Consolidates async operation states (replaces multiple boolean states).
Before:
const [isLoading, setIsLoading] = useState(false)
const [isCreating, setIsCreating] = useState(false)
const [isUpdating, setIsUpdating] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
// ... many moreAfter:
import { useOperationState } from '../hooks/useOperationState'
const operation = useOperationState()
// Set states
operation.setLoading('collection')
operation.setCreating('entry', entryId)
operation.setIdle()
// Check states
if (operation.isLoading) { /* ... */ }
if (operation.isOperationInProgress('updating', 'entry', entryId)) { /* ... */ }Manages modal dialogs with type-safe state handling.
Before:
const [showCreateModal, setShowCreateModal] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
const [editingEntry, setEditingEntry] = useState<Entry | null>(null)
// ... many moreAfter:
import { useModalState } from '../hooks/useModalState'
const modal = useModalState<Entry, Collection>()
// Open modals
modal.openCreateEntry()
modal.openEditEntry(entry)
modal.openEditCollection(collection)
modal.close()
// Check state
if (modal.isType('editEntry')) {
const entry = modal.getEntry()
// entry is type-safe
}Centralizes all content API calls with consistent error handling.
Usage:
import { useContentAPI } from '../hooks/useContentAPI'
import { handleError } from '../utils/errorHandler'
const api = useContentAPI(bucket)
// All methods have consistent error handling
try {
const collections = await api.listCollections()
const entry = await api.createEntry(collectionId, entryData)
await api.publishChanges()
} catch (error) {
handleError(error, { operation: 'Load Content' })
}Available Methods:
- Collections:
listCollections,createCollection,updateCollection,deleteCollection - Entries:
listEntries,createEntry,updateEntry,deleteEntry - API:
publishChanges,getLastGenerationDate - Order:
updateCollectionOrder,updateEntryOrder
-
src/renderer/App.tsx- Now uses
isSuccessResponsetype guard instead of type assertions - Cleaner, type-safe API response handling
- Now uses
-
src/renderer/components/EntryEditor.tsx- Uses shared
slugifyfromstringUtils - Removed duplicate slugify function
- Uses shared
-
src/renderer/components/ContentManager.tsx- Uses shared
slugifyfromstringUtils - Removed duplicate slugify function
- Ready to integrate
useOperationState,useModalState, anduseContentAPI
- Uses shared
-
src/renderer/components/CollectionEditModal.tsx- Uses shared
slugifyfromstringUtils - Removed duplicate slugify function
- Uses shared
-
src/renderer/components/ApiPreview.tsx- Uses
CACHE_BUSTER_PARAMconstant - More maintainable cache-busting implementation
- Uses
slugifyfunction was duplicated in 3 files → now in 1 shared utility- Error handling patterns standardized across components
- API calls centralized in single hook
- Type guards eliminate unsafe type assertions
(result as any).something - TypeScript can properly infer types after type guard checks
- Modal and operation states are fully type-safe
- Constants in one place → easy to update application-wide values
- Consistent error handling → easier debugging and better UX
- Fewer state variables → simpler component logic
- Clear, documented utility functions
- Reusable custom hooks for common patterns
- IntelliSense support with JSDoc comments
- Utilities can be tested independently
- Custom hooks can be tested with React Testing Library
- Mocking is simplified with centralized API calls
The ContentManager.tsx component can now be refactored to use the new hooks:
// Replace ~10 boolean states with:
const operation = useOperationState()
const modal = useModalState<ContentEntry, ContentCollection>()
const api = useContentAPI(bucket)
// Simplify operations:
const handleCreateEntry = async (entry) => {
operation.setCreating('entry')
try {
await api.createEntry(selectedCollection.id, entry)
await loadEntries(selectedCollection.id)
modal.close()
} catch (error) {
handleError(error, { operation: 'Create Entry', component: 'ContentManager' })
} finally {
operation.setIdle()
}
}This would reduce ContentManager.tsx from 837 lines to ~600 lines with clearer logic.
Move SCHEMA_TEMPLATES from SchemaBuilder.tsx to a JSON file:
src/data/schema-templates.json
Organize components by feature:
src/renderer/components/
content/
- ContentManager.tsx
- CollectionList.tsx
- EntryList.tsx
modals/
- CollectionCreateModal.tsx
- CollectionEditModal.tsx
- EntryEditor.tsx
api/
- ApiPreview.tsx
- ApiDocumentation.tsx
Before:
const slugify = (text: string): string => {
return text.toLowerCase()...
}After:
import { slugify } from '../utils/stringUtils'Before:
if (result.success && (result.result || (result as any).schemas)) {
const schemas = result.result || (result as any).schemas
setSchemas(schemas)
}After:
import { isSuccessResponse } from '../types/api'
if (isSuccessResponse(result)) {
setSchemas(result.result) // TypeScript knows result.result exists
}Before:
const url = `${baseUrl}/data.json?_=${Date.now()}`After:
import { CACHE_BUSTER_PARAM } from '../constants/app'
const url = `${baseUrl}/data.json?${CACHE_BUSTER_PARAM}=${Date.now()}`These refactorings create a more maintainable, type-safe, and developer-friendly codebase. The new utilities, constants, type guards, and custom hooks can be adopted incrementally without breaking existing functionality.
Quick Wins Implemented:
- ✅ Shared string utilities (5 min to implement, saves time forever)
- ✅ Type-safe API responses (15 min, prevents runtime errors)
- ✅ Consistent error handling (20 min, better UX)
- ✅ Application constants (15 min, easier maintenance)
- ✅ Custom hooks for state management (1 hour, massive simplification)
Total Time Invested: ~2 hours Long-term Benefits: Easier onboarding, faster feature development, fewer bugs