Skip to content

Latest commit

 

History

History
378 lines (286 loc) · 9.7 KB

File metadata and controls

378 lines (286 loc) · 9.7 KB

Code Quality Improvements

This document outlines the refactoring improvements made to enhance code quality, maintainability, and developer experience.

📁 New Files Created

Utilities

src/renderer/utils/stringUtils.ts

Common string manipulation functions used across the application.

Functions:

  • slugify(text) - Converts text to URL-friendly slugs
  • truncate(text, maxLength) - Truncates text with ellipsis
  • capitalize(text) - Capitalizes first letter

Usage:

import { slugify } from '../utils/stringUtils'

const slug = slugify("My Blog Post") // "my-blog-post"

src/renderer/utils/errorHandler.ts

Centralized error handling with consistent logging and user notifications.

Functions:

  • handleError(error, context, showToast?) - Logs errors and shows toast notifications
  • handleSuccess(message, details?) - Shows success notifications
  • withErrorHandling(operation, context) - Wraps async operations with error handling
  • getErrorMessage(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'
  })
}

Constants

src/renderer/constants/app.ts

Application-wide constants to replace magic strings and numbers.

Constants:

  • API_BASE_PATH - Base path for API endpoints
  • CACHE_BUSTER_PARAM - Query parameter for cache busting
  • DEFAULT_IMAGE_SIZES - Default image size configurations
  • UPLOAD_TIMEOUT_MS - Upload timeout
  • ALLOWED_IMAGE_TYPES - Allowed image MIME types
  • S3_REGIONS - Available S3 regions
  • FIELD_TYPES - Schema field types
  • APP_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 Guards

src/types/api.ts

Type-safe API response handling with type guards.

Types & Functions:

  • APIResponse<T> - Standard API response interface
  • isSuccessResponse(response) - Type guard for successful responses
  • isErrorResponse(response) - Type guard for error responses
  • unwrapResponse(response) - Extracts result or throws error
  • tryUnwrapResponse(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)
}

Custom Hooks

src/renderer/hooks/useOperationState.ts

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 more

After:

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)) { /* ... */ }

src/renderer/hooks/useModalState.ts

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 more

After:

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
}

src/renderer/hooks/useContentAPI.ts

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

🔄 Files Updated

Components Updated to Use Shared Utilities

  1. src/renderer/App.tsx

    • Now uses isSuccessResponse type guard instead of type assertions
    • Cleaner, type-safe API response handling
  2. src/renderer/components/EntryEditor.tsx

    • Uses shared slugify from stringUtils
    • Removed duplicate slugify function
  3. src/renderer/components/ContentManager.tsx

    • Uses shared slugify from stringUtils
    • Removed duplicate slugify function
    • Ready to integrate useOperationState, useModalState, and useContentAPI
  4. src/renderer/components/CollectionEditModal.tsx

    • Uses shared slugify from stringUtils
    • Removed duplicate slugify function
  5. src/renderer/components/ApiPreview.tsx

    • Uses CACHE_BUSTER_PARAM constant
    • More maintainable cache-busting implementation

📊 Benefits

1. Reduced Code Duplication

  • slugify function was duplicated in 3 files → now in 1 shared utility
  • Error handling patterns standardized across components
  • API calls centralized in single hook

2. Improved Type Safety

  • 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

3. Better Maintainability

  • Constants in one place → easy to update application-wide values
  • Consistent error handling → easier debugging and better UX
  • Fewer state variables → simpler component logic

4. Enhanced Developer Experience

  • Clear, documented utility functions
  • Reusable custom hooks for common patterns
  • IntelliSense support with JSDoc comments

5. Easier Testing

  • Utilities can be tested independently
  • Custom hooks can be tested with React Testing Library
  • Mocking is simplified with centralized API calls

🚀 Next Steps (Optional)

Integrate New Hooks into ContentManager

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.

Extract Schema Templates

Move SCHEMA_TEMPLATES from SchemaBuilder.tsx to a JSON file:

src/data/schema-templates.json

Create Component Subdirectories

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

📝 Migration Guide

Using Shared Slugify

Before:

const slugify = (text: string): string => {
  return text.toLowerCase()...
}

After:

import { slugify } from '../utils/stringUtils'

Using Type Guards

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
}

Using Constants

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()}`

🎯 Summary

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