skyderby/skyderby

View on GitHub
app/javascript/api/speedSkydivingCompetitions/competitors.ts

Summary

Maintainability
C
1 day
Test Coverage
import client, { AxiosError, AxiosResponse } from 'api/client'
import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryFunction,
  UseQueryOptions,
  UseQueryResult,
  UseMutationResult
} from '@tanstack/react-query'
import { parseISO } from 'date-fns'
import queryClient from 'components/queryClient'
import { cacheProfiles, ProfileRecord } from 'api/profiles'
import { cacheCountries, CountryRecord } from 'api/countries'
import { standingsQuery } from './standings'
import { Competitor } from './types'

type SerializedCompetitor = {
  [K in keyof Competitor]: Competitor[K] extends Date ? string : Competitor[K]
}

interface IndexResponse {
  items: SerializedCompetitor[]
  relations: {
    profiles: ProfileRecord[]
    countries: CountryRecord[]
  }
}

interface CompetitorVariables {
  categoryId: number | null
  assignedNumber: number | null
  profileId?: number | null
  profileAttributes: {
    name: string
    countryId: number | null
  } | null
}

type MutationOptions = {
  onSuccess?: (arg: Competitor) => unknown
}

type QueryKey = ['speedSkydivingCompetitions', number, 'competitors']

const deserialize = (competitor: SerializedCompetitor): Competitor => ({
  ...competitor,
  createdAt: parseISO(competitor.createdAt),
  updatedAt: parseISO(competitor.updatedAt)
})

const collectionEndpoint = (eventId: number) =>
  `/api/v1/speed_skydiving_competitions/${eventId}/competitors`
const elementEndpoint = (eventId: number, id: number) =>
  `${collectionEndpoint(eventId)}/${id}`

const getCompetitors = (eventId: number) =>
  client
    .get<never, AxiosResponse<IndexResponse>>(collectionEndpoint(eventId))
    .then(response => response.data)

const createCompetitor = (eventId: number, competitor: CompetitorVariables) =>
  client.post<{ competitor: CompetitorVariables }, AxiosResponse<SerializedCompetitor>>(
    collectionEndpoint(eventId),
    { competitor }
  )

const updateCompetitor = (eventId: number, id: number, competitor: CompetitorVariables) =>
  client.put<{ competitor: CompetitorVariables }, AxiosResponse<SerializedCompetitor>>(
    elementEndpoint(eventId, id),
    { competitor }
  )

const deleteCompetitor = (eventId: number, id: number) =>
  client.delete<never, AxiosResponse<SerializedCompetitor>>(elementEndpoint(eventId, id))

const collectionKey = (eventId: number): QueryKey => [
  'speedSkydivingCompetitions',
  eventId,
  'competitors'
]

const queryFn: QueryFunction<Competitor[], QueryKey> = async ctx => {
  const [_key, eventId] = ctx.queryKey
  const { relations, items } = await getCompetitors(eventId)

  cacheProfiles(relations.profiles)
  cacheCountries(relations.countries)

  return items.map(deserialize)
}

export const competitorsQuery = <Type = Competitor[]>(
  eventId: number
): UseQueryOptions<Competitor[], Error, Type, QueryKey> => ({
  queryKey: collectionKey(eventId),
  queryFn
})

export const preloadCompetitors = (eventId: number): Promise<void> =>
  queryClient.prefetchQuery(competitorsQuery(eventId))

export const useCompetitorsQuery = <Type = Competitor[]>(
  eventId: number,
  options?: Omit<
    UseQueryOptions<Competitor[], Error, Type, QueryKey>,
    'queryKey' | 'queryFn'
  >
): UseQueryResult<Type> => useQuery({ ...competitorsQuery<Type>(eventId), ...options })

export const useCompetitorQuery = (
  eventId: number,
  id: number
): UseQueryResult<Competitor | undefined> =>
  useCompetitorsQuery<Competitor | undefined>(eventId, {
    select: data => data.find(competitor => competitor.id === id)
  })

export type NewCompetitorMutation = UseMutationResult<
  AxiosResponse<SerializedCompetitor>,
  AxiosError<Record<string, string[]>>,
  CompetitorVariables
>

export const useNewCompetitorMutation = (
  eventId: number,
  options: MutationOptions = {}
): NewCompetitorMutation => {
  const queryClient = useQueryClient()

  const mutationFn = (variables: CompetitorVariables) =>
    createCompetitor(eventId, variables)

  return useMutation({
    mutationFn,
    async onSuccess(response) {
      const data: Competitor[] = queryClient.getQueryData(collectionKey(eventId)) ?? []
      const competitor = deserialize(response.data)
      queryClient.setQueryData(collectionKey(eventId), [...data, competitor])
      await queryClient.refetchQueries(standingsQuery(eventId))
      options?.onSuccess?.(competitor)
    }
  })
}

export type EditCompetitorMutation = UseMutationResult<
  AxiosResponse<SerializedCompetitor>,
  AxiosError<Record<string, string[]>>,
  CompetitorVariables
>

export const useEditCompetitorMutation = (
  eventId: number,
  competitorId: number,
  options: MutationOptions = {}
): EditCompetitorMutation => {
  const queryClient = useQueryClient()

  const mutationFn = (variables: CompetitorVariables) =>
    updateCompetitor(eventId, competitorId, variables)

  return useMutation({
    mutationFn,
    async onSuccess(response) {
      const data: Competitor[] = queryClient.getQueryData(collectionKey(eventId)) ?? []
      const updatedCompetitor = deserialize(response.data)
      queryClient.setQueryData(
        collectionKey(eventId),
        data.map(competitor =>
          competitor.id === competitorId ? updatedCompetitor : competitor
        )
      )
      await queryClient.refetchQueries(standingsQuery(eventId))
      options?.onSuccess?.(updatedCompetitor)
    }
  })
}

export const useDeleteCompetitorMutation = (
  eventId: number,
  competitorId: number,
  options: MutationOptions = {}
) => {
  const queryClient = useQueryClient()

  const mutationFn = () => deleteCompetitor(eventId, competitorId)

  return useMutation<
    AxiosResponse<SerializedCompetitor>,
    AxiosError<Record<string, string[]>>
  >({
    mutationFn,
    async onSuccess(response) {
      const data: Competitor[] = queryClient.getQueryData(collectionKey(eventId)) ?? []
      const competitor = deserialize(response.data)
      await queryClient.refetchQueries(standingsQuery(eventId))
      queryClient.setQueryData(
        collectionKey(eventId),
        data.filter(competitor => competitor.id !== competitorId)
      )
      options?.onSuccess?.(competitor)
    }
  })
}