Skip to content

Refactor 2: [Phase 1] CRUDModalTemplate Foundation #5292

@palisadoes

Description

@palisadoes

Problem Statement

30+ modals across the codebase implement repetitive CRUD (Create, Read, Update, Delete) patterns with:

  • Duplicate modal structure (header, body, footer, close button)
  • Inconsistent form submission and error handling
  • Repetitive loading and success states
  • Varying modal sizes and styling approaches
  • Examples: VolunteerGroupModal, CampaignModal, VenueModal, PledgeModal, ActionItemModal

Proposed Solution

  1. Create flexible CRUD modal templates:

    • CRUDModalTemplate: Base template with common structure
    • CreateModal: Template for create operations
    • EditModal: Template for edit operations with data loading
    • DeleteModal: Confirmation modal with warning UI
    • ViewModal: Read-only detail modal
  2. Create a linter to prevent new code reverting to the unapproved way.

    1. This command was used to identify files:
      rg -n --glob 'src/screens/**' -e '<Modal\b' -e "from\\s+'react-bootstrap'.*Modal" -e "from\\s+\"react-bootstrap\".*Modal" | cut -d: -f1 | sort -u
      
    2. The linter must run as a lint staged check. Look at the .husky directory for details.
    3. It must also be part of our Pull Request GitHub action file .github/workflows/pull-request.yml as a job that is a prerequisite to the Pre-Test-Checks-Pass job.

    Implementation Plan

Example Starter Code

src/types/CRUDModalTemplate/interface.ts

export interface CrudModalBaseProps {
  open: boolean; title: string; onClose: ()=>void;
  primaryText?: string; secondaryText?: string; loading?: boolean; error?: string;
}

src/shared-components/CRUDModalTemplate/CRUDModalTemplate.tsx

import React from 'react';
import { Modal, Button, Spinner, Alert } from 'react-bootstrap';
import type { CrudModalBaseProps } from '../../types/CRUDModalTemplate/interface';

export const CRUDModalTemplate: React.FC<CrudModalBaseProps & { children?: React.ReactNode; onPrimary?: ()=>void; }> = ({ open, title, onClose, onPrimary, loading, error, children, primaryText='Save', secondaryText='Cancel' }) => (
  <Modal show={open} onHide={onClose} aria-labelledby="crud-modal-title" centered>
    <Modal.Header closeButton><Modal.Title id="crud-modal-title">{title}</Modal.Title></Modal.Header>
    <Modal.Body>
      {loading && <Spinner role="status" animation="border" className="me-2" />}
      {error && <Alert variant="danger">{error}</Alert>}
      {!loading && children}
    </Modal.Body>
    <Modal.Footer>
      <Button variant="secondary" onClick={onClose}>{secondaryText}</Button>
      {onPrimary && <Button variant="primary" onClick={onPrimary} disabled={!!loading}>{primaryText}</Button>}
    </Modal.Footer>
  </Modal>
);

src/shared-components/CRUDModalTemplate/CRUDModalTemplate.spec.tsx

import { render, screen } from '@testing-library/react';
import { CRUDModalTemplate } from './CRUDModalTemplate';
test('renders title', () => {
  render(<CRUDModalTemplate open title="Edit Item" onClose={()=>{}} />);
  expect(screen.getByText(/edit item/i)).toBeInTheDocument();
});

Learning Resources

Files to Modify

  • src/shared-components/CRUDModalTemplate/CRUDModalTemplate.tsx (create)
  • src/shared-components/CRUDModalTemplate/CreateModal.tsx (create)
  • src/shared-components/CRUDModalTemplate/EditModal.tsx (create)
  • src/shared-components/CRUDModalTemplate/DeleteModal.tsx (create)
  • src/shared-components/CRUDModalTemplate/ViewModal.tsx (create)
  • src/shared-components/CRUDModalTemplate/CRUDModalTemplate.spec.tsx (create)
  • src/shared-components/CRUDModalTemplate/types.ts (create)
  • src/shared-components/CRUDModalTemplate/index.ts (create)
  • src/shared-components/CRUDModalTemplate/CRUDModalTemplate.module.css (create)

Implementation Approach

Step 1: Design Modal Template Interface

Define props for:

  • Modal state (isOpen, onClose, onSave/onDelete)
  • Content (title, children/render prop for body)
  • Loading and error states
  • Action buttons (customize text, icons, colors)
  • Size variants (sm, md, lg, xl, full)

Step 2: Implement Base CRUDModalTemplate

  • Wrap react-bootstrap Modal with consistent structure
  • Header with title and close button
  • Body with loading/error state handling
  • Footer with action buttons
  • Keyboard shortcuts (Esc to close, Enter to submit)

Step 3: Implement Specialized Templates

  • CreateModal: Form submission with validation feedback
  • EditModal: Data fetching, pre-population, and update logic
  • DeleteModal: Warning UI, confirmation input, destructive action styling
  • ViewModal: Read-only display with tabbed sections support

Step 4: Integration Hooks

  • useModalState: Custom hook for modal open/close management
  • useFormModal: Hook combining modal state with form handling
  • useMutationModal: Hook for GraphQL mutation modals

Step 5: Testing & Documentation

  • Unit tests for all modal variants
  • Integration tests with form submission
  • Accessibility testing (focus trapping, keyboard nav)
  • Migration examples from existing modals

Dependencies

Blocked by: (none)
Blocks: (Modal rollout issues in later phases)

Blocking Issues

  • None

Acceptance Criteria

  • All 5 modal templates implemented with TypeScript
  • Support for custom content via children or render props
  • Loading, error, and success states handled consistently
  • Focus trap and keyboard navigation working
  • Consistent button placement and styling
  • Test coverage > 90%
  • Migration guide with before/after examples
  • Storybook stories for all modal types

Effort Estimate

  • Template architecture design: 3 hours
  • Base CRUDModalTemplate implementation: 5 hours
  • Specialized templates (4 types): 6 hours
  • Integration hooks: 2 hours
  • Testing & documentation: 2 hours
  • Total: 18 hours

Testing Requirements

  • Unit tests: Modal opens/closes correctly
  • Unit tests: Action buttons trigger callbacks
  • Unit tests: Loading states display correctly
  • Integration tests: Form submission flow
  • Integration tests: Error handling and display
  • Accessibility tests: Focus trap on modal open
  • Accessibility tests: Keyboard navigation (Esc, Tab, Enter)
  • Visual regression tests: All modal sizes and states

Success Criteria

  • Modal templates reduce modal code by 70%+
  • All modals have consistent UX (loading, errors, actions)
  • Migration from existing modals is straightforward
  • Focus management and keyboard navigation work correctly in all modals

Other information

Related past PRs.

Sub-issues

Metadata

Metadata

Assignees

Labels

ci/cdPull requests that update GitHub Actions coderefactorRefactoring tasksui/uxissue related and being worked with the figma file of the Admin UI

Type

No type

Projects

Status

Backlog

Status

Backlog

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions