Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/apis/map/votePlace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { instance } from '../instance';
import type { ApiEnvelope } from '@/types/api.type';
import type { AddCandidatePlaceRequest, AddCandidatePlaceResponse, GetCandidatePlacesResponse } from '@/types/map/votePlace.type';
import type { ApiEnvelope, ApiEnvelopeNullable } from '@/types/api.type';
import type { AddCandidatePlaceRequest, AddCandidatePlaceResponse, GetCandidatePlacesResponse, PostVoteRequest } from '@/types/map/votePlace.type';

// 투표 후보지 목록 조회
export const getCandidatePlaces = async (promiseId: string): Promise<ApiEnvelope<GetCandidatePlacesResponse>> => {
Expand All @@ -18,4 +18,16 @@ export const addCandidatePlace = async (promiseId: string, body: AddCandidatePla
export const deleteCandidatePlace = async (promiseId: string, candidateId: number): Promise<ApiEnvelope<void>> => {
const response = await instance.delete(`/promises/${promiseId}/candidates/${candidateId}`);
return response.data;
}
}

// 장소 투표
export const postVote = async (promiseId: string, body: PostVoteRequest): Promise<ApiEnvelopeNullable<null>> => {
const response = await instance.post(`/promises/${promiseId}/votes`, body);
return response.data;
};

// 장소 투표 취소
export const deleteVote = async (promiseId: string, candidateId: number): Promise<ApiEnvelopeNullable<null>> => {
const response = await instance.delete(`/promises/${promiseId}/votes/${candidateId}`);
return response.data;
};
19 changes: 16 additions & 3 deletions src/pages/map/components/LocationBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PlusIcon from '@/assets/images/plusIcon.svg';
import NomineeMinusIcon from '@/assets/images/map/nomineeMinusIcon.svg';
import VoteIcon from '@/assets/images/map/voteIcon.svg';
import WarningIcon from '@/assets/images/warningIcon.svg';
import { useAddCandidatePlace, useDeleteCandidatePlace } from '../services/useVotePalce';
import { useAddCandidatePlace, useDeleteCandidatePlace, useDeleteVote, usePostVote } from '../services/useVotePalce';
import type { CandidatePlace } from '@/types/map/votePlace.type';

interface LocationBottomSheetProps {
Expand Down Expand Up @@ -38,7 +38,7 @@ const LocationBottomSheet = ({
const height = isError ? 'h-60' : isConfirmed ? 'h-60' : 'h-45';

const isAdded = candidatesPlaces.some(candidatePlace => candidatePlace.name === selectedPlace.placeName && candidatePlace.address === selectedPlace.address);
const isVoted = candidatesPlaces.some(candidatePlace => candidatePlace.name === selectedPlace.placeName && candidatePlace.address === selectedPlace.address);
const isVoted = candidatesPlaces.find(candidatePlace => candidatePlace.name === selectedPlace.placeName && candidatePlace.address === selectedPlace.address)?.voteInfo.isMyVote;
const selectedPlaceId = candidatesPlaces.find(candidatePlace => candidatePlace.name === selectedPlace.placeName && candidatePlace.address === selectedPlace.address)?.id;

const {mutate: addCandidatePlaceMutate} = useAddCandidatePlace(promiseId);
Expand Down Expand Up @@ -69,8 +69,21 @@ const LocationBottomSheet = ({
});
}

const {mutate: postVoteMutate} = usePostVote(promiseId);
const postVoteHandler = () => {
if (!selectedPlaceId) return;
console.log('selectedPlaceId: ', selectedPlaceId);
console.log("Clicked");

postVoteMutate({
candidateId: selectedPlaceId,
});
};

const {mutate: deleteVoteMutate} = useDeleteVote(promiseId, selectedPlaceId);

console.log(selectedPlace);
console.log(candidatesPlaces)

return (
<BottomSheet
Expand Down Expand Up @@ -131,7 +144,7 @@ const LocationBottomSheet = ({
className={`w-9 h-9 p-1.5 rounded-full cursor-pointer ${
isVoted ? 'bg-[#C6C6C6]' : 'bg-[#00408E]'
}`}
onClick={() => {}}
onClick={() => isVoted ? deleteVoteMutate() : postVoteHandler()}
>
<img src={VoteIcon} />
</div>
Expand Down
57 changes: 54 additions & 3 deletions src/pages/map/services/useVotePalce.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { addCandidatePlace, deleteCandidatePlace, getCandidatePlaces } from '@/apis/map/votePlace';
import type { AddCandidatePlaceRequest } from '@/types/map/votePlace.type';
import { addCandidatePlace, deleteCandidatePlace, deleteVote, getCandidatePlaces, postVote } from '@/apis/map/votePlace';
import type { AddCandidatePlaceRequest, PostVoteRequest } from '@/types/map/votePlace.type';
import axios from 'axios';

// 투표 후보지 목록 조회
export const useGetCandidatePlaces = (promiseId?: string) => {
Expand Down Expand Up @@ -56,4 +57,54 @@ export const useDeleteCandidatePlace = (promiseId?: string, candidateId?: number
console.error('투표 후보지 제거 실패: ', error);
},
})
}
};

// 장소 투표
export const usePostVote = (promiseId?: string) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (body: PostVoteRequest) => {
if (!promiseId) {
return Promise.reject(new Error("Promise ID is required"));
}
return postVote(promiseId, body);
},
onSuccess: () => {
alert("투표가 완료되었습니다.");
queryClient.invalidateQueries({ queryKey: ['candidatePlaces', promiseId] });
},
onError: (error) => {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
if (status === 409) {
const message = error.response?.data.message;
if (message) {
alert(message);
}
}
}
},
});
};

// 장소 투표 취소
export const useDeleteVote = (promiseId?: string, candidateId?: number) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: () => {
if (!promiseId || !candidateId) {
return Promise.reject(new Error("Promise ID is required"));
}
return deleteVote(promiseId, candidateId);
},
onSuccess: () => {
alert("투표가 취소되었습니다.");
queryClient.invalidateQueries({ queryKey: ['candidatePlaces', promiseId] });
},
onError: (error) => {
console.error('투표 취소 실패: ', error);
}
})
};
18 changes: 18 additions & 0 deletions src/types/map/votePlace.type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
// 투표 후보지 리스트 반환
export interface VoteInfo {
creator: {
userId: number;
nickname: string;
};
voteCount: number;
voters: { userId: number; nickname: string }[];
isMyVote: boolean;
isMyCandidate: boolean;
}

export interface CandidatePlace {
id: number;
name: string;
Expand All @@ -8,11 +19,13 @@ export interface CandidatePlace {
address: string;
distance: number;
isConfirmed: boolean;
voteInfo: VoteInfo;
};

export interface GetCandidatePlacesResponse {
candidates: CandidatePlace[];
candidateCount: number;
totalMemberCount: number;
};

// 투표 후보지 추가
Expand All @@ -34,3 +47,8 @@ export interface AddCandidatePlaceResponse {
distance: number;
isConfirmed: boolean;
};

// 장소 투표
export interface PostVoteRequest {
candidateId: number;
};
Loading