Story 2.8.4: Partner Contact Management

Last updated: January 2, 2026 Reading time: 22 minutes File size: 32.99 KB

Status

Story

As an organizer, I want to manage partner contacts through the Contacts tab with add/remove functionality and role assignment, so that I can maintain accurate contact information for each partner company with designated primary, billing, technical, and marketing contacts.

Domain Context

Primary Domain

Partner Analytics Domain - Partner contact management for organizer workflows

Involved Services

  • Frontend: React 19 application (TypeScript, Material-UI v5, Zustand, React Query)
  • Backend: Partner Coordination Service (REST APIs from Story 2.7)
  • Backend: User Management Service (for user autocomplete and data enrichment via HTTP)
  • Cache: React Query for client-side caching (10min TTL for contacts)

Shared-Kernel Foundation (automatically included):

  • ErrorResponse (standardized error format with correlationId)
  • HTTP client utilities (JWT propagation, error handling)

OpenAPI Code Generation:

  • Generate TypeScript types from docs/api/partners-api.openapi.yml

Cross-Domain Dependencies

  • Depends on: Story 2.8.2 (Partner Detail View) for Contacts tab container
  • Depends on: Story 1.17 (React Frontend Foundation) for shared components
  • Depends on: Story 2.7 (Partner Coordination Service Foundation) for Partner Contact APIs
  • Depends on: Story 2.1b (User Management Service) for user autocomplete and enrichment
  • Integrates with: API Gateway for authentication

Requirements Context

  • FR8: Partner strategic input (contacts manage topic voting and suggestions)
  • FR12: Partner meeting coordination (contacts receive meeting invitations)
  • NFR-Performance: Contact list load <1s, add/remove operations <500ms
  • NFR-UX: WCAG 2.1 AA accessibility, inline validation

Acceptance Criteria Source

Epic 2, Story 2.8.4 (derived from wireframe docs/wireframes/story-6.3-partner-detail.md Contacts Tab)

Architecture Context

Architecture Patterns

[Source: architecture/05-frontend-architecture.md]

  • State Management: React Query for server state (contacts list, mutations)
  • Form Management: React Hook Form with Zod validation
  • Validation: Zod schemas with business rule validation (primary contact required)
  • API Path Pattern: Use /partners/{companyName}/contacts (WITHOUT /api/v1 prefix)

[Source: architecture/04-api-design.md#partners-api]

  • List Contacts: GET /partners/{companyName}/contacts (returns enriched user data)
  • Add Contact: POST /partners/{companyName}/contacts with AddPartnerContactRequest
  • Remove Contact: DELETE /partners/{companyName}/contacts/{username}
  • HTTP Enrichment (ADR-004): Backend calls User Service to enrich contact data
  • Validation: Backend enforces at least one PRIMARY contact

[Source: architecture/03-data-architecture.md#partner-contact]

  • PartnerContact Entity: id (UUID), partnerId, username (meaningful ID - ADR-003), contactRole, isPrimary
  • Contact Roles: PRIMARY, BILLING, TECHNICAL, MARKETING
  • Business Rule: At least one PRIMARY contact required per partner
  • UNIQUE Constraint: (partnerId, username) - prevent duplicate contacts

Database Schema Reference

N/A - Frontend story, no database migrations required. Backend schema handled in Story 2.7.

Infrastructure Components

  • Frontend: Contacts tab integrated into Partner Detail view
  • CDN: User profile pictures from CloudFront

Wireframe Context

Wireframe References

Primary Wireframe: docs/wireframes/story-6.3-partner-detail.md (Contacts Tab)

Contacts Tab Implemented:

  1. Contacts Tab in Partner Detail

    • Contact list with user data (name, email, role, primary indicator)
    • [+ Add Contact] button
    • Contact cards with edit/remove actions
    • Primary contact badge (⭐)
    • Role badges (PRIMARY, BILLING, TECHNICAL, MARKETING)
    • User profile pictures (from CloudFront)
  2. Add Contact Modal

    • User autocomplete search (by username or email)
    • Contact role dropdown (PRIMARY, BILLING, TECHNICAL, MARKETING)
    • Primary contact toggle (only one primary allowed)
    • Form actions: [Cancel] [Add]
  3. Edit Contact Modal

    • User display (read-only)
    • Contact role dropdown (editable)
    • Primary contact toggle (editable, with validation)
    • Form actions: [Cancel] [Save]

Navigation Flows:

  • From Partner Detail β†’ Click Contacts tab β†’ Display contacts list
  • From Contacts tab β†’ Click [+ Add Contact] β†’ Add Contact Modal
  • From Contact card β†’ Click [Edit] β†’ Edit Contact Modal
  • From Contact card β†’ Click [Remove] β†’ Confirmation dialog β†’ Remove contact

UI Components

Feature Components:

  • PartnerContactsTab.tsx - Contacts tab container (integrated in Story 2.8.2)
  • ContactList.tsx - List of contacts with cards
  • ContactCard.tsx - Individual contact card with user data, role badge, actions
  • AddContactModal.tsx - Add new contact modal
  • EditContactModal.tsx - Edit existing contact modal
  • UserAutocomplete.tsx - User search/selection (reuse from Story 2.5.2 if exists)
  • ContactRoleSelect.tsx - Role dropdown
  • PrimaryContactToggle.tsx - Primary toggle with validation

Shared Components:

  • Modal (dialog wrapper)
  • ConfirmDialog (remove confirmation)
  • FormField, SelectField (form inputs)
  • Avatar (user profile pictures)

Material-UI Components:

  • Card, CardContent, CardActions
  • Autocomplete (user selection)
  • Select, MenuItem (role selection)
  • Switch (primary toggle)
  • Chip (role badges)
  • Badge (primary indicator ⭐)
  • Button, IconButton
  • Dialog, DialogTitle, DialogContent, DialogActions

State Management:

  • Zustand Store: useContactModalStore

    • isAddModalOpen: boolean
    • isEditModalOpen: boolean
    • contactToEdit: PartnerContactResponse | null
    • openAddModal(), openEditModal(contact), closeModals()
  • React Query:

    • usePartnerContacts(companyName) - List with enrichment, cache 10min
    • useAddPartnerContact() - Mutation for add
    • useRemovePartnerContact() - Mutation for remove
    • useUpdatePartnerContact() - Mutation for edit (Epic 8 feature if not in Story 2.7)
    • Query keys: ['partner', companyName, 'contacts']

Acceptance Criteria

  1. Contacts Tab

    • Integrated into Partner Detail view (Story 2.8.2)
    • Tab label: β€œContacts” with count badge (e.g., β€œContacts (5)”)
    • Contact list with enriched user data (name, email, role, profile picture)
    • Primary contact indicator (⭐ star icon/badge)
    • Role badges (PRIMARY, BILLING, TECHNICAL, MARKETING with color coding)
    • User profile pictures (from CloudFront CDN, fallback to initials avatar)
    • [+ Add Contact] button (top right)
    • Empty state: β€œNo contacts yet. Add the first contact.” with [+ Add Contact] button
  2. Contact Cards

    • User profile picture (avatar)
    • Full name (firstName + lastName from enriched user data)
    • Email (from enriched user data)
    • Contact role (badge with icon)
      • πŸ‘€ PRIMARY (primary color)
      • πŸ’³ BILLING (blue)
      • πŸ”§ TECHNICAL (orange)
      • πŸ“§ MARKETING (green)
    • Primary indicator (⭐ for isPrimary=true)
    • Actions: [Edit Role] [Remove] buttons
    • Tooltip on hover: β€œPrimary contact for this partnership”
  3. Add Contact Modal

    • Modal title: β€œAdd Contact”
    • User autocomplete field (search by username or email)
      • Min 2 characters to trigger search
      • Display: Username, full name, email
      • Loading indicator while searching
      • Empty state: β€œNo users found”
    • Contact role dropdown (PRIMARY, BILLING, TECHNICAL, MARKETING)
    • Primary contact toggle (switch)
      • Disabled if partner already has primary contact
      • Tooltip: β€œThis partner already has a primary contact”
    • Form actions: [Cancel] [Add]
    • Validation:
      • User required
      • Role required
      • Cannot add duplicate contact (same partner + username)
    • Error handling: Display API errors (409 for duplicate, 404 for user not found)
  4. Add Contact Operation

    • Click [Add] β†’ POST /api/v1/partners/{companyName}/contacts
    • Request body: username, contactRole, isPrimary
    • Success: Close modal, refresh contacts list, show success toast
    • Error handling:
      • 409 Conflict: β€œThis user is already a contact for this partner”
      • 404 Not Found: β€œUser not found”
      • 400 Validation: Display validation errors
    • Loading state: Disable form, show spinner on [Add] button
    • Optimistic update: Add contact to list immediately, revert on error
  5. Edit Contact Modal

    • Modal title: β€œEdit Contact”
    • User display (read-only, show username and full name)
    • Contact role dropdown (editable)
    • Primary contact toggle (editable)
      • Cannot unset primary if only primary contact
      • Warning: β€œAt least one primary contact is required”
    • Form actions: [Cancel] [Save]
    • Validation: Role required, primary validation
    • Update operation: PATCH /partners/{companyName}/contacts/{username}
  6. Remove Contact

    • Click [Remove] β†’ Confirmation dialog
      • Title: β€œRemove Contact?”
      • Message: β€œAre you sure you want to remove [Full Name] ([email]) from this partnership?”
      • Actions: [Cancel] [Remove]
    • Click [Remove] in dialog β†’ DELETE /api/v1/partners/{companyName}/contacts/{username}
    • Success: Refresh contacts list, show success toast
    • Error handling:
      • 400 Bad Request: β€œCannot remove last primary contact”
      • 404 Not Found: β€œContact not found”
    • Business rule validation: Cannot remove if only primary contact
    • Optimistic update: Remove from list immediately, revert on error
  7. Primary Contact Validation

    • At least one PRIMARY contact required per partner
    • Cannot remove last primary contact (API returns 400)
    • Cannot unset primary toggle if only primary contact
    • When adding new primary contact, automatically unset previous primary (backend handles)
    • Visual indicator: Warning message when attempting invalid operation
  8. User Autocomplete

    • Search users by username or email (debounced 300ms)
    • Call User Management Service API: GET /users/search?q={query}
    • Display: Username, full name, email, profile picture
    • Loading indicator while searching
    • Empty state: β€œNo users found matching β€˜{query}’”
    • Error handling: Display error message if search fails
    • Keyboard navigation (Arrow keys, Enter to select)
  9. Contact List Loading States

    • Initial load: Full-page skeleton (3-5 contact card skeletons)
    • Refresh: Show loading indicator (do not hide existing contacts)
    • Empty state: β€œNo contacts yet” message with illustration
    • Error state: Error message with [Retry] button
  10. Responsive Design

    • Mobile (<640px): Single column, stacked contact cards, fullscreen modals
    • Tablet (640-1024px): Two columns contact grid
    • Desktop (>1024px): Three columns contact grid
  11. Accessibility

    • Keyboard navigation (Tab, Enter, Escape)
    • Screen reader announcements (contacts added/removed, modal open)
    • ARIA labels for all buttons and form fields
    • Focus indicators visible
    • Color contrast for role badges β‰₯4.5:1
  12. Internationalization

    • All UI text translated (buttons, labels, messages)
    • Role labels translated (PRIMARY β†’ PrimΓ€r, BILLING β†’ Abrechnung, etc.)
    • Confirmation messages translated
  13. Integration Tests

    • Test list contacts API call with HTTP enrichment
    • Test add contact API call
    • Test remove contact API call
    • Test duplicate contact error (409)
    • Test primary contact validation (400)
    • Test user autocomplete API call
    • Test error scenarios (404, 500, network errors)

Test Specifications (TDD)

Test Scenarios by Acceptance Criteria

AC1 Tests: Contacts Tab

  • Test 1.1: should_renderContactsTab_when_tabActivated
  • Test 1.2: should_displayContactsList_when_contactsLoaded
  • Test 1.3: should_displayCountBadge_when_contactsExist
  • Test 1.4: should_displayEmptyState_when_noContacts
  • Test 1.5: should_displayAddButton_when_tabRendered

AC2 Tests: Contact Cards

  • Test 2.1: should_renderContactCard_when_contactProvided
  • Test 2.2: should_displayEnrichedUserData_when_contactLoaded
  • Test 2.3: should_displayRoleBadge_when_contactHasRole
  • Test 2.4: should_displayPrimaryIndicator_when_isPrimaryTrue
  • Test 2.5: should_displayProfilePicture_when_userHasPicture
  • Test 2.6: should_displayInitialsAvatar_when_noPicture

AC3 Tests: Add Contact Modal

  • Test 3.1: should_openAddModal_when_addButtonClicked
  • Test 3.2: should_searchUsers_when_autocompleteInputChanged
  • Test 3.3: should_validateRequiredFields_when_addClicked
  • Test 3.4: should_disablePrimaryToggle_when_primaryContactExists
  • Test 3.5: should_displayTooltip_when_primaryToggleDisabled

AC4 Tests: Add Contact Operation

  • Test 4.1: should_addContact_when_formValid
  • Test 4.2: should_closeModal_when_addSucceeds
  • Test 4.3: should_refreshContactsList_when_addSucceeds
  • Test 4.4: should_showSuccessToast_when_addSucceeds
  • Test 4.5: should_displayDuplicateError_when_409Returned
  • Test 4.6: should_displayUserNotFoundError_when_404Returned
  • Test 4.7: should_optimisticallyUpdate_when_addCalled

AC5 Tests: Edit Contact Modal (if implemented)

  • Test 5.1: should_openEditModal_when_editButtonClicked
  • Test 5.2: should_prefillForm_when_editModalOpened
  • Test 5.3: should_validatePrimaryToggle_when_onlyPrimaryContact
  • Test 5.4: should_displayWarning_when_cannotUnsetPrimary

AC6 Tests: Remove Contact

  • Test 6.1: should_showConfirmation_when_removeClicked
  • Test 6.2: should_removeContact_when_confirmClicked
  • Test 6.3: should_refreshContactsList_when_removeSucceeds
  • Test 6.4: should_showError_when_cannotRemoveLastPrimary
  • Test 6.5: should_optimisticallyUpdate_when_removeCalled
  • Test 6.6: should_revertOnError_when_removeFails

AC7 Tests: Primary Contact Validation

  • Test 7.1: should_enforcePrimaryRequired_when_validating
  • Test 7.2: should_preventRemovingLastPrimary_when_onlyOneExists
  • Test 7.3: should_displayWarning_when_violationAttempted
  • Test 7.4: should_unsetPreviousPrimary_when_newPrimaryAdded

AC8 Tests: User Autocomplete

  • Test 8.1: should_searchUsers_when_queryChanged
  • Test 8.2: should_debounceSearch_when_userTyping
  • Test 8.3: should_displayUserOptions_when_searchCompletes
  • Test 8.4: should_selectUser_when_optionClicked
  • Test 8.5: should_displayEmptyState_when_noResults

AC9 Tests: Loading States

  • Test 9.1: should_displaySkeleton_when_initialLoad
  • Test 9.2: should_displayLoadingIndicator_when_refreshing
  • Test 9.3: should_displayEmptyState_when_noContacts
  • Test 9.4: should_displayErrorState_when_loadFails
  • Test 9.5: should_retryLoad_when_retryClicked

AC10 Tests: Responsive Design

  • Test 10.1: should_renderSingleColumn_when_mobileViewport
  • Test 10.2: should_renderTwoColumns_when_tabletViewport
  • Test 10.3: should_renderThreeColumns_when_desktopViewport

AC11 Tests: Accessibility

  • Test 11.1: should_supportKeyboardNav_when_modalOpen
  • Test 11.2: should_announceChanges_when_screenReaderActive
  • Test 11.3: should_haveAriaLabels_when_buttonsRendered

AC12 Tests: Internationalization

  • Test 12.1: should_translateLabels_when_localeChanged
  • Test 12.2: should_translateRoles_when_localeSet

AC13 Tests: Integration

  • Test 13.1: should_callContactsAPI_when_tabActivated
  • Test 13.2: should_includeUserData_when_httpEnrichmentUsed
  • Test 13.3: should_callAddContactAPI_when_formSubmitted
  • Test 13.4: should_callRemoveContactAPI_when_confirmClicked
  • Test 13.5: should_handleDuplicateError_when_409Returned

Test File Locations

Frontend Tests:

  • web-frontend/src/components/organizer/PartnerManagement/PartnerContactsTab.test.tsx
  • web-frontend/src/components/organizer/PartnerManagement/ContactList.test.tsx
  • web-frontend/src/components/organizer/PartnerManagement/ContactCard.test.tsx
  • web-frontend/src/components/organizer/PartnerManagement/AddContactModal.test.tsx
  • web-frontend/src/components/organizer/PartnerManagement/EditContactModal.test.tsx
  • web-frontend/src/components/organizer/PartnerManagement/UserAutocomplete.test.tsx
  • web-frontend/src/hooks/usePartnerContacts.test.ts
  • web-frontend/src/hooks/useAddPartnerContact.test.ts
  • web-frontend/src/hooks/useRemovePartnerContact.test.ts

E2E Tests:

  • web-frontend/e2e/partner-contact-management.spec.ts

Test Data & Mocks

Test Data Builders:

  • PartnerContactTestDataBuilder
  • UserSearchResultBuilder
  • AddContactRequestBuilder

Mock Services:

  • Mock Partner Contact API: GET/POST/DELETE /partners/{companyName}/contacts
  • Mock User Search API: GET /users/search
  • Mock User Service HTTP enrichment responses

Tasks / Subtasks (TDD Workflow)

⚠️ CRITICAL: Tests must be written BEFORE implementation code (Red-Green-Refactor).

  • Task 1a: Partner Contact API Integration - RED Phase

    • Write failing test:
    • Write failing test:
    • Write failing test:
    • Write failing test:
    • Write failing test:
    • Write failing test:
  • Task 1b: Partner Contact API Integration - GREEN Phase

    • Add to src/services/api/partnerApi.ts
      • getPartnerContacts(companyName) using GET /partners/{companyName}/contacts
      • addPartnerContact(companyName, request) using POST /partners/{companyName}/contacts
      • removePartnerContact(companyName, username) using DELETE /partners/{companyName}/contacts/{username}
  • Task 2a: User Search API Integration - RED Phase

    • Write failing test:
    • Write failing test:
  • Task 2b: User Search API Integration - GREEN Phase

    • Add to src/services/api/userApi.ts
      • searchUsers(query) using GET /users/search?q={query}
  • Task 3a: React Query Hooks - RED Phase

    • Write failing test:
    • Write failing test:
    • Write failing test:
    • Write failing test:
    • Write failing test:
  • Task 3b: React Query Hooks - GREEN Phase

    • Create src/hooks/usePartnerContacts.ts
    • Create src/hooks/useAddPartnerContact.ts
    • Create src/hooks/useRemovePartnerContact.ts
  • Task 4a: Zustand Store - RED Phase

    • Write failing test:
    • Write failing test:
    • Write failing test:
  • Task 4b: Zustand Store - GREEN Phase

    • Create
  • Task 5a: Contact Card Component - RED Phase

  • Task 5b: Contact Card Component - GREEN Phase

    • Create
  • Task 6a: User Autocomplete Component - RED Phase

  • Task 6b: User Autocomplete Component - GREEN Phase

    • Create or reuse
  • Task 7a: Contact Role Select Component - RED Phase

  • Task 7b: Contact Role Select Component - GREEN Phase

    • Create
  • Task 8a: Add Contact Modal Component - RED Phase

  • Task 8b: Add Contact Modal Component - GREEN Phase

    • Create
  • Task 9a: Contact List Component - RED Phase

  • Task 9b: Contact List Component - GREEN Phase

    • Create
  • Task 10a: Contacts Tab Component - RED Phase

  • Task 10b: Contacts Tab Component - GREEN Phase

    • Update src/components/organizer/PartnerManagement/PartnerContactsTab.tsx
  • Task 11a: Remove Contact with Confirmation - RED Phase

  • Task 11b: Remove Contact with Confirmation - GREEN Phase

  • Task 12: Responsive Design

  • Task 13: Accessibility

  • Task 14: Internationalization

  • Task 15: Integration Testing

  • Task 16: E2E Testing

    • Create

Dev Notes - Implementation Guide

Service Setup

File Structure:

web-frontend/src/
β”œβ”€β”€ components/organizer/PartnerManagement/
β”‚   β”œβ”€β”€ PartnerContactsTab.tsx          # Tab container (from Story 2.8.2)
β”‚   β”œβ”€β”€ ContactList.tsx                 # Contact grid
β”‚   β”œβ”€β”€ ContactCard.tsx                 # Individual contact card
β”‚   β”œβ”€β”€ AddContactModal.tsx             # Add contact modal
β”‚   β”œβ”€β”€ EditContactModal.tsx            # Edit contact modal (Epic 8?)
β”‚   β”œβ”€β”€ UserAutocomplete.tsx            # User search
β”‚   β”œβ”€β”€ ContactRoleSelect.tsx           # Role dropdown
β”‚   β”œβ”€β”€ PrimaryContactToggle.tsx        # Primary toggle
β”‚   └── index.ts
β”œβ”€β”€ hooks/
β”‚   β”œβ”€β”€ usePartnerContacts.ts           # List contacts
β”‚   β”œβ”€β”€ useAddPartnerContact.ts         # Add mutation
β”‚   β”œβ”€β”€ useRemovePartnerContact.ts      # Remove mutation
β”‚   └── useSearchUsers.ts               # User autocomplete
β”œβ”€β”€ stores/
β”‚   └── contactModalStore.ts

API Specifications

API Client Extensions (partnerApi.ts):

typescript
// Add to src/services/api/partnerApi.ts

export type PartnerContactResponse = components['schemas']['PartnerContactResponse'];
export type AddPartnerContactRequest = components['schemas']['AddPartnerContactRequest'];
export type ContactRole = components['schemas']['ContactRole'];

export const getPartnerContacts = async (companyName: string): Promise<PartnerContactResponse[]> => {
  const response = await apiClient.get(`${PARTNER_API_PATH}/${companyName}/contacts`);
  // Backend returns contacts enriched with User data via HTTP call
  return response.data;
};

export const addPartnerContact = async (
  companyName: string,
  request: AddPartnerContactRequest
): Promise<PartnerContactResponse> => {
  const response = await apiClient.post(`${PARTNER_API_PATH}/${companyName}/contacts`, request);
  return response.data;
};

export const removePartnerContact = async (
  companyName: string,
  username: string
): Promise<void> => {
  await apiClient.delete(`${PARTNER_API_PATH}/${companyName}/contacts/${username}`);
};

User API Client (userApi.ts):

typescript
// Add to src/services/api/userApi.ts

export const searchUsers = async (query: string): Promise<UserSearchResult[]> => {
  const response = await apiClient.get('/users/search', { params: { q: query } });
  return response.data;
};

React Query Hooks

usePartnerContacts.ts:

typescript
import { useQuery } from '@tanstack/react-query';
import { getPartnerContacts } from '@/services/api/partnerApi';

export const usePartnerContacts = (companyName: string) => {
  return useQuery({
    queryKey: ['partner', companyName, 'contacts'],
    queryFn: () => getPartnerContacts(companyName),
    staleTime: 10 * 60 * 1000, // 10 minutes
    enabled: !!companyName,
  });
};

useAddPartnerContact.ts:

typescript
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { addPartnerContact } from '@/services/api/partnerApi';
import { toast } from 'react-toastify';

export const useAddPartnerContact = (companyName: string) => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (request: AddPartnerContactRequest) => addPartnerContact(companyName, request),
    onMutate: async (newContact) => {
      // Optimistic update
      await queryClient.cancelQueries({ queryKey: ['partner', companyName, 'contacts'] });
      const previousContacts = queryClient.getQueryData(['partner', companyName, 'contacts']);

      queryClient.setQueryData(['partner', companyName, 'contacts'], (old: any) => [
        ...old,
        { ...newContact, id: 'temp-id' },
      ]);

      return { previousContacts };
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['partner', companyName, 'contacts'] });
      toast.success('Contact added successfully');
    },
    onError: (error: any, newContact, context) => {
      // Revert optimistic update
      queryClient.setQueryData(['partner', companyName, 'contacts'], context?.previousContacts);

      const errorMessage = error.response?.data?.message || 'Failed to add contact';
      const correlationId = error.response?.data?.correlationId;
      toast.error(`${errorMessage}. Correlation ID: ${correlationId}`);
    },
  });
};

Contact Role Mapping

typescript
const roleConfig: Record<ContactRole, { icon: string; color: string; label: string }> = {
  PRIMARY: { icon: 'πŸ‘€', color: 'primary', label: 'Primary' },
  BILLING: { icon: 'πŸ’³', color: 'info', label: 'Billing' },
  TECHNICAL: { icon: 'πŸ”§', color: 'warning', label: 'Technical' },
  MARKETING: { icon: 'πŸ“§', color: 'success', label: 'Marketing' },
};

Primary Contact Validation

typescript
// In AddContactModal.tsx
const hasPrimaryContact = contacts?.some(c => c.isPrimary);

<FormControlLabel
  control={
    <Switch
      checked={isPrimary}
      onChange={(e) => setIsPrimary(e.target.checked)}
      disabled={hasPrimaryContact}
    />
  }
  label={
    <Tooltip title={hasPrimaryContact ? 'This partner already has a primary contact' : ''}>
      <span>Primary Contact</span>
    </Tooltip>
  }
/>

Confirmation Dialog

typescript
const [confirmRemove, setConfirmRemove] = useState<PartnerContactResponse | null>(null);

<ConfirmDialog
  open={!!confirmRemove}
  title="Remove Contact?"
  message={`Are you sure you want to remove ${confirmRemove?.firstName} ${confirmRemove?.lastName} (${confirmRemove?.email}) from this partnership?`}
  onConfirm={() => {
    removeContactMutation.mutate(confirmRemove.username);
    setConfirmRemove(null);
  }}
  onCancel={() => setConfirmRemove(null)}
/>

Definition of Done Checklist

Development Complete

Frontend Complete

Review Ready

Change Log

Date Version Description Author
2025-01-09 1.0 Initial story creation Bob (Scrum Master)

Dev Agent Record

Agent Model Used

To be filled by dev agent

Implementation Approach

To be filled by dev agent during implementation

Debug Log References

To be filled by dev agent

Completion Notes

To be filled by dev agent

Files Changed

To be filled by dev agent

Deployment Notes

No special deployment considerations - standard React frontend deployment