Skip to content

Commit e758e56

Browse files
authored
Merge pull request #223 from ITBooster-practice/feature/teams-hooks-and-services-222
feat(web):API-сервис команд и хуки (#222)
2 parents 1557530 + 581aaa3 commit e758e56

25 files changed

Lines changed: 1903 additions & 150 deletions
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2+
3+
import {
4+
teamInvitationsService,
5+
type SendInvitation,
6+
type Team,
7+
type TeamInvitation,
8+
} from '@/shared/lib/api/team-invitations-service'
9+
import type { ApiError } from '@/shared/lib/api/types'
10+
11+
import { teamsKeys } from './use-teams'
12+
13+
export const teamInvitationsKeys = {
14+
all: ['teams', 'invitations'] as const,
15+
teamLists: () => [...teamInvitationsKeys.all, 'team-list'] as const,
16+
teamList: (teamId: string) => [...teamInvitationsKeys.teamLists(), teamId] as const,
17+
send: (teamId: string) => [...teamInvitationsKeys.all, 'send', teamId] as const,
18+
revoke: (teamId: string) => [...teamInvitationsKeys.all, 'revoke', teamId] as const,
19+
myLists: () => [...teamInvitationsKeys.all, 'my-list'] as const,
20+
myList: () => [...teamInvitationsKeys.myLists()] as const,
21+
accept: () => [...teamInvitationsKeys.all, 'accept'] as const,
22+
decline: () => [...teamInvitationsKeys.all, 'decline'] as const,
23+
} as const
24+
25+
export const useTeamInvitations = (teamId: string) => {
26+
return useQuery({
27+
queryKey: teamInvitationsKeys.teamList(teamId),
28+
queryFn: () => teamInvitationsService.getTeamInvitations(teamId),
29+
enabled: Boolean(teamId),
30+
})
31+
}
32+
33+
export const useSendTeamInvitation = (teamId: string) => {
34+
const queryClient = useQueryClient()
35+
36+
return useMutation<TeamInvitation, ApiError, SendInvitation>({
37+
mutationKey: teamInvitationsKeys.send(teamId || 'mutation'),
38+
mutationFn: (data) => teamInvitationsService.sendInvitation(teamId, data),
39+
onSuccess: () =>
40+
queryClient.invalidateQueries({
41+
queryKey: teamInvitationsKeys.teamList(teamId),
42+
}),
43+
})
44+
}
45+
46+
export const useRevokeTeamInvitation = (teamId: string) => {
47+
const queryClient = useQueryClient()
48+
49+
return useMutation<TeamInvitation, ApiError, string>({
50+
mutationKey: teamInvitationsKeys.revoke(teamId || 'mutation'),
51+
mutationFn: (invitationId) =>
52+
teamInvitationsService.revokeInvitation(teamId, invitationId),
53+
onSuccess: () =>
54+
queryClient.invalidateQueries({
55+
queryKey: teamInvitationsKeys.teamList(teamId),
56+
}),
57+
})
58+
}
59+
60+
export const useMyInvitations = () => {
61+
return useQuery({
62+
queryKey: teamInvitationsKeys.myList(),
63+
queryFn: teamInvitationsService.getMyInvitations,
64+
})
65+
}
66+
67+
export const useAcceptInvitation = () => {
68+
const queryClient = useQueryClient()
69+
70+
return useMutation<Team, ApiError, string>({
71+
mutationKey: teamInvitationsKeys.accept(),
72+
mutationFn: teamInvitationsService.acceptInvitation,
73+
onSuccess: async (team) => {
74+
queryClient.setQueryData(teamsKeys.detail(team.id), team)
75+
76+
await Promise.all([
77+
queryClient.invalidateQueries({ queryKey: teamsKeys.lists() }),
78+
queryClient.invalidateQueries({ queryKey: teamInvitationsKeys.myList() }),
79+
])
80+
},
81+
})
82+
}
83+
84+
export const useDeclineInvitation = () => {
85+
const queryClient = useQueryClient()
86+
87+
return useMutation<TeamInvitation, ApiError, string>({
88+
mutationKey: teamInvitationsKeys.decline(),
89+
mutationFn: teamInvitationsService.declineInvitation,
90+
onSuccess: () =>
91+
queryClient.invalidateQueries({
92+
queryKey: teamInvitationsKeys.myList(),
93+
}),
94+
})
95+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2+
3+
import {
4+
teamMembersService,
5+
type ChangeRole,
6+
type DeleteTeamResponse,
7+
type TeamMember,
8+
} from '@/shared/lib/api/team-members-service'
9+
import type { ApiError } from '@/shared/lib/api/types'
10+
11+
import { teamsKeys } from './use-teams'
12+
13+
export const teamMembersKeys = {
14+
all: ['teams', 'members'] as const,
15+
lists: () => [...teamMembersKeys.all, 'list'] as const,
16+
list: (teamId: string) => [...teamMembersKeys.lists(), teamId] as const,
17+
changeRole: (teamId: string) =>
18+
[...teamMembersKeys.all, 'change-role', teamId] as const,
19+
remove: (teamId: string) => [...teamMembersKeys.all, 'remove', teamId] as const,
20+
} as const
21+
22+
export const useTeamMembers = (teamId: string) => {
23+
return useQuery({
24+
queryKey: teamMembersKeys.list(teamId),
25+
queryFn: () => teamMembersService.getMembers(teamId),
26+
enabled: Boolean(teamId),
27+
})
28+
}
29+
30+
export const useChangeMemberRole = (teamId: string) => {
31+
const queryClient = useQueryClient()
32+
33+
return useMutation<TeamMember, ApiError, { userId: string; data: ChangeRole }>({
34+
mutationKey: teamMembersKeys.changeRole(teamId || 'mutation'),
35+
mutationFn: ({ userId, data }) => teamMembersService.changeRole(teamId, userId, data),
36+
onSuccess: async () => {
37+
await Promise.all([
38+
queryClient.invalidateQueries({ queryKey: teamMembersKeys.list(teamId) }),
39+
queryClient.invalidateQueries({ queryKey: teamsKeys.detail(teamId) }),
40+
queryClient.invalidateQueries({ queryKey: teamsKeys.lists() }),
41+
])
42+
},
43+
})
44+
}
45+
46+
export const useRemoveTeamMember = (teamId: string) => {
47+
const queryClient = useQueryClient()
48+
49+
return useMutation<DeleteTeamResponse, ApiError, string>({
50+
mutationKey: teamMembersKeys.remove(teamId || 'mutation'),
51+
mutationFn: (userId) => teamMembersService.removeMember(teamId, userId),
52+
onSuccess: async () => {
53+
await Promise.all([
54+
queryClient.invalidateQueries({ queryKey: teamMembersKeys.list(teamId) }),
55+
queryClient.invalidateQueries({ queryKey: teamsKeys.detail(teamId) }),
56+
queryClient.invalidateQueries({ queryKey: teamsKeys.lists() }),
57+
])
58+
},
59+
})
60+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { MyInvitation, SendInvitation, Team, TeamInvitation } from '@repo/types'
2+
3+
import { client } from './client'
4+
import {
5+
normalizeMyInvitation,
6+
normalizeTeam,
7+
normalizeTeamInvitation,
8+
type MyInvitationApiResponse,
9+
type TeamApiResponse,
10+
type TeamInvitationApiResponse,
11+
} from './teams-normalizers'
12+
13+
export type { MyInvitation, SendInvitation, Team, TeamInvitation }
14+
15+
const TEAMS_ENDPOINT = '/teams'
16+
const INVITATIONS_ENDPOINT = '/invitations'
17+
const buildTeamInvitationsEndpoint = (teamId: string) =>
18+
`${TEAMS_ENDPOINT}/${teamId}/invitations`
19+
const buildInvitationEndpoint = (token: string) => `${INVITATIONS_ENDPOINT}/${token}`
20+
21+
export const teamInvitationsService = {
22+
sendInvitation: async (
23+
teamId: string,
24+
body: SendInvitation,
25+
): Promise<TeamInvitation> => {
26+
const response = await client.post<TeamInvitationApiResponse>(
27+
buildTeamInvitationsEndpoint(teamId),
28+
body,
29+
)
30+
31+
return normalizeTeamInvitation(response.data)
32+
},
33+
34+
getTeamInvitations: async (teamId: string): Promise<TeamInvitation[]> => {
35+
const response = await client.get<TeamInvitationApiResponse[]>(
36+
buildTeamInvitationsEndpoint(teamId),
37+
)
38+
39+
return response.data.map(normalizeTeamInvitation)
40+
},
41+
42+
revokeInvitation: async (
43+
teamId: string,
44+
invitationId: string,
45+
): Promise<TeamInvitation> => {
46+
const response = await client.delete<TeamInvitationApiResponse>(
47+
`${buildTeamInvitationsEndpoint(teamId)}/${invitationId}`,
48+
)
49+
50+
return normalizeTeamInvitation(response.data)
51+
},
52+
53+
getMyInvitations: async (): Promise<MyInvitation[]> => {
54+
const response = await client.get<MyInvitationApiResponse[]>(
55+
`${INVITATIONS_ENDPOINT}/me`,
56+
)
57+
58+
return response.data.map(normalizeMyInvitation)
59+
},
60+
61+
acceptInvitation: async (token: string): Promise<Team> => {
62+
const response = await client.post<TeamApiResponse>(
63+
`${buildInvitationEndpoint(token)}/accept`,
64+
)
65+
66+
return normalizeTeam(response.data)
67+
},
68+
69+
declineInvitation: async (token: string): Promise<TeamInvitation> => {
70+
const response = await client.post<TeamInvitationApiResponse>(
71+
`${buildInvitationEndpoint(token)}/decline`,
72+
)
73+
74+
return normalizeTeamInvitation(response.data)
75+
},
76+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { ChangeRole, DeleteTeamResponse, TeamMember } from '@repo/types'
2+
3+
import { client } from './client'
4+
import { normalizeTeamMember, type TeamMemberApiResponse } from './teams-normalizers'
5+
6+
export type { ChangeRole, DeleteTeamResponse, TeamMember }
7+
8+
const TEAMS_ENDPOINT = '/teams'
9+
const buildTeamMembersEndpoint = (teamId: string) => `${TEAMS_ENDPOINT}/${teamId}/members`
10+
11+
export const teamMembersService = {
12+
getMembers: async (teamId: string): Promise<TeamMember[]> => {
13+
const response = await client.get<TeamMemberApiResponse[]>(
14+
buildTeamMembersEndpoint(teamId),
15+
)
16+
17+
return response.data.map(normalizeTeamMember)
18+
},
19+
20+
changeRole: async (
21+
teamId: string,
22+
userId: string,
23+
body: ChangeRole,
24+
): Promise<TeamMember> => {
25+
const response = await client.patch<TeamMemberApiResponse>(
26+
`${buildTeamMembersEndpoint(teamId)}/${userId}/role`,
27+
body,
28+
)
29+
30+
return normalizeTeamMember(response.data)
31+
},
32+
33+
removeMember: async (teamId: string, userId: string): Promise<DeleteTeamResponse> => {
34+
const response = await client.delete<DeleteTeamResponse>(
35+
`${buildTeamMembersEndpoint(teamId)}/${userId}`,
36+
)
37+
38+
return response.data
39+
},
40+
}

0 commit comments

Comments
 (0)