All files / src/hooks useSpeakerPool.ts

100% Statements 33/33
100% Branches 0/0
100% Functions 22/22
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229                                                11x   62x 61x                       23x   10x                                   21x   21x   12x     7x       7x                                     25x   25x                 3x   2x                                 4x   4x                 2x   1x     1x         1x               5x   5x   3x     2x                                           75x   75x   10x     8x       8x                                           6x   6x             4x     3x            
/**
 * Speaker Pool React Query Hooks (Story 5.2 - Task 7b)
 *
 * Custom hooks for speaker pool data fetching and mutations
 * Features:
 * - React Query for server state caching
 * - Automatic cache invalidation on updates
 * - Per-event caching strategy
 */
 
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { speakerPoolService } from '@/services/speakerPoolService';
import type {
  AddSpeakerToPoolRequest,
  PatchSpeakerPoolRequest,
  PromoteSpeakerRequest,
  SendInvitationRequest,
  SendReminderRequest,
} from '@/types/speakerPool.types';
 
/**
 * Query key factory for speaker pool
 * Centralizes query key management for cache invalidation
 */
export const speakerPoolKeys = {
  all: ['speakerPool'] as const,
  lists: () => [...speakerPoolKeys.all, 'list'] as const,
  list: (eventCode: string) => [...speakerPoolKeys.lists(), eventCode] as const,
};
 
/**
 * Hook to fetch speaker pool for an event
 *
 * @param eventCode Event code (e.g., "BATbern56")
 * @returns Query result with list of speaker pool entries
 * @example
 * const { data: speakerPool, isLoading } = useSpeakerPool('BATbern56');
 */
export function useSpeakerPool(eventCode: string) {
  return useQuery({
    queryKey: speakerPoolKeys.list(eventCode),
    queryFn: () => speakerPoolService.getSpeakerPool(eventCode),
    enabled: !!eventCode,
    staleTime: 1000 * 60 * 2, // 2 minutes - pool changes frequently during brainstorming
  });
}
 
/**
 * Hook to add speaker to event pool (AC9-12, ORGANIZER only)
 *
 * @returns Mutation object with mutate function
 * @example
 * const addMutation = useAddSpeakerToPool();
 * addMutation.mutate({
 *   eventCode: 'BATbern56',
 *   request: { speakerName: 'John Doe', company: 'ACME', expertise: 'Cloud', ... }
 * });
 */
export function useAddSpeakerToPool() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({ eventCode, request }: { eventCode: string; request: AddSpeakerToPoolRequest }) =>
      speakerPoolService.addSpeakerToPool(eventCode, request),
    onSuccess: (_data, variables) => {
      // Invalidate speaker pool list for this event to refetch
      queryClient.invalidateQueries({
        queryKey: speakerPoolKeys.list(variables.eventCode),
      });
      // Also invalidate status summary since counts have changed
      queryClient.invalidateQueries({
        queryKey: ['speakerStatusSummary', variables.eventCode],
      });
    },
  });
}
 
/**
 * Hook to delete speaker from event pool (ORGANIZER only)
 *
 * @returns Mutation object with mutate function
 * @example
 * const deleteMutation = useDeleteSpeakerFromPool();
 * deleteMutation.mutate({ eventCode: 'BATbern56', speakerId: 'uuid-here' });
 */
/**
 * Hook to patch a speaker pool entry (e.g. reassign organizer).
 */
export function usePatchSpeakerPool() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({
      eventCode,
      speakerId,
      request,
    }: {
      eventCode: string;
      speakerId: string;
      request: PatchSpeakerPoolRequest;
    }) => speakerPoolService.patchSpeakerPool(eventCode, speakerId, request),
    onSuccess: (_data, variables) => {
      queryClient.invalidateQueries({
        queryKey: speakerPoolKeys.list(variables.eventCode),
      });
    },
  });
}
 
/**
 * Hook to promote a CONTACTED speaker to READY (Story 11.D.1).
 *
 * Drives the workflow transition + User provisioning server-side. On success,
 * invalidates the speaker pool list AND the speaker status summary so the kanban + the
 * dashboard pick up the speaker's new state.
 *
 * @returns Mutation object with mutate function
 */
export function usePromoteSpeakerToReady() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({
      eventCode,
      speakerId,
      request,
    }: {
      eventCode: string;
      speakerId: string;
      request: PromoteSpeakerRequest;
    }) => speakerPoolService.promoteToSpeaker(eventCode, speakerId, request),
    onSuccess: (_data, variables) => {
      queryClient.invalidateQueries({
        queryKey: speakerPoolKeys.list(variables.eventCode),
      });
      queryClient.invalidateQueries({
        queryKey: ['speakerStatusSummary', variables.eventCode],
      });
      // Epic 11 bug fix 2026-05-19 — promote writes a CONTACTED→READY status_history row
      // server-side; invalidate the history feed so the drawer's History tab refreshes.
      queryClient.invalidateQueries({
        queryKey: ['speakerStatusHistory', variables.eventCode, variables.speakerId],
      });
    },
  });
}
 
export function useDeleteSpeakerFromPool() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({ eventCode, speakerId }: { eventCode: string; speakerId: string }) =>
      speakerPoolService.deleteSpeakerFromPool(eventCode, speakerId),
    onSuccess: (_data, variables) => {
      // Invalidate speaker pool list for this event to refetch
      queryClient.invalidateQueries({
        queryKey: speakerPoolKeys.list(variables.eventCode),
      });
    },
  });
}
 
/**
 * Hook to send invitation to speaker (Story 6.1c, ORGANIZER only)
 *
 * Transitions speaker from IDENTIFIED to INVITED state and sends invitation email.
 *
 * @param eventCode Event code (e.g., "BATbern56")
 * @returns Mutation object with mutate function
 * @example
 * const sendInvitation = useSendInvitation('BATbern56');
 * sendInvitation.mutate({
 *   username: 'speaker-uuid',
 *   options: { personalMessage: 'Looking forward to your talk!' }
 * });
 */
export function useSendInvitation(eventCode: string) {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({ username, options }: { username: string; options?: SendInvitationRequest }) =>
      speakerPoolService.sendInvitation(eventCode, username, options),
    onSuccess: () => {
      // Invalidate speaker pool list for this event to refetch (speaker moves to INVITED)
      queryClient.invalidateQueries({
        queryKey: speakerPoolKeys.list(eventCode),
      });
      // Also invalidate status summary since counts have changed
      queryClient.invalidateQueries({
        queryKey: ['speakerStatusSummary', eventCode],
      });
    },
  });
}
 
/**
 * Hook to send reminder to speaker (Story 6.5, ORGANIZER only)
 *
 * Sends a deadline reminder email to an invited or accepted speaker.
 *
 * @param eventCode Event code (e.g., "BATbern56")
 * @returns Mutation object with mutate function
 * @example
 * const sendReminder = useSendReminder('BATbern56');
 * sendReminder.mutate({
 *   speakerPoolId: 'uuid-here',
 *   request: { reminderType: 'RESPONSE' }
 * });
 */
export function useSendReminder(eventCode: string) {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({
      speakerPoolId,
      request,
    }: {
      speakerPoolId: string;
      request: SendReminderRequest;
    }) => speakerPoolService.sendReminder(eventCode, speakerPoolId, request),
    onSuccess: () => {
      // Invalidate speaker pool list to refetch updated data
      queryClient.invalidateQueries({
        queryKey: speakerPoolKeys.list(eventCode),
      });
    },
  });
}