Skip to content

Commit

Permalink
feat: 아이템 선택 api 추가 (#24)
Browse files Browse the repository at this point in the history
* fix: item select count 0으로 나오는 버그 수정

* feat: 아이템 선택 api 추가

* feat: 게임 만들때 text 빈값일때 에러처리
  • Loading branch information
Hellol77 authored Sep 30, 2024
1 parent ec1f1da commit 505ae52
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 10 deletions.
6 changes: 5 additions & 1 deletion backend/src/game/game.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export class GameService {
): Promise<void> {
const { firstItemText, secondItemText } = createGameDto;

if (!firstItemText || !secondItemText) {
throw new Error('게임을 생성하기 위해서는 두 개의 아이템이 필요합니다.');
}

// 1. 사용자 조회
const user = await this.usersRepository.findOneBy({ user_id: userId });
if (!user) {
Expand Down Expand Up @@ -171,7 +175,7 @@ export class GameService {
itemDto.item_text = item.item_text;
itemDto.game_id = item.game.game_id;
itemDto.isSelected = isSelected;
itemDto.selected_count = item.comments ? item.comments.length : 0;
itemDto.selected_count = item.selected_count;
return itemDto;
}

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/apis/selectItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { axiosInstance } from '.';
import END_POINTS from '../constants/api';

export default async function selectItem(gameId, itemId) {
const { data } = await axiosInstance.post(END_POINTS.SELECT_ITEM(gameId, itemId));
return data;
}
39 changes: 37 additions & 2 deletions frontend/src/components/choice/Choice.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
import useSelectItem from '../../hooks/queries/useSelectItem';
import calculatePercent from '../../utils/calculatePercent';
import * as S from './Choice.styled';

export default function Choice(props) {
const { game } = props;
console.log(game?.firstItem.item_id);
const { mutate: selectItemMutate } = useSelectItem(game?.firstItem.game_id);
const isColored = (firstItemSelected, secondItemSelected) => {
return !firstItemSelected && !secondItemSelected ? true : firstItemSelected ? true : false;
};

return (
<S.ItemContainer>
<S.ItemList>{game?.firstItem.item_text}</S.ItemList>
<S.ItemList2>{game?.secondItem.item_text}</S.ItemList2>
<S.ItemList
onClick={() => selectItemMutate(game?.firstItem?.item_id)}
iscolored={isColored(game?.firstItem.isSelected, game?.secondItem.isSelected)}
>
<S.itemText>{game?.firstItem.item_text}</S.itemText>
<S.percentBox
first
percent={calculatePercent(
game?.firstItem.selected_count,
game?.firstItem.selected_count + game?.secondItem.selected_count,
)}
isselected={game?.firstItem.isSelected}
isshow={game?.firstItem.isSelected || game?.secondItem.isSelected}
/>
</S.ItemList>
<S.ItemList2
onClick={() => selectItemMutate(game?.secondItem?.item_id)}
iscolored={isColored(game?.secondItem.isSelected, game?.firstItem.isSelected)}
>
<S.itemText>{game?.secondItem.item_text}</S.itemText>
<S.percentBox
percent={calculatePercent(
game?.secondItem.selected_count,
game?.firstItem.selected_count + game?.secondItem.selected_count,
)}
isselected={game?.secondItem.isSelected}
isshow={game?.firstItem.isSelected || game?.secondItem.isSelected}
/>
</S.ItemList2>
</S.ItemContainer>
);
}
55 changes: 49 additions & 6 deletions frontend/src/components/choice/Choice.styled.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import styled from 'styled-components';
import styled, { keyframes } from 'styled-components';

export const ItemContainer = styled.div`
width: 100%;
Expand All @@ -12,8 +12,9 @@ export const ItemContainer = styled.div`
`;

export const ItemList = styled.div`
position: relative;
word-break: keep-all;
width: 100%;
width: 50%;
padding: 5rem;
text-align: center;
display: flex;
Expand All @@ -25,12 +26,15 @@ export const ItemList = styled.div`
border-top: 0.2rem solid ${(props) => props.theme.colors.black};
border-left: 0.2rem solid ${(props) => props.theme.colors.black};
border-bottom: 0.2rem solid ${(props) => props.theme.colors.black};
background-color: ${(props) => props.theme.colors.primaryBlue};
border-right: 0.1rem solid ${(props) => props.theme.colors.black};
background-color: ${(props) =>
props.iscolored ? props.theme.colors.primaryBlue : props.theme.colors.gray500};
`;

export const ItemList2 = styled.div`
position: relative;
word-break: keep-all;
width: 100%;
width: 50%;
padding: 5rem;
text-align: center;
display: flex;
Expand All @@ -39,6 +43,45 @@ export const ItemList2 = styled.div`
border-top-right-radius: 2rem; /* 오른쪽 위 */
font-size: 3.4rem;
height: 100%;
border: 0.2rem solid ${(props) => props.theme.colors.black};
background-color: ${(props) => props.theme.colors.primaryGreen};
border-top: 0.2rem solid ${(props) => props.theme.colors.black};
border-bottom: 0.2rem solid ${(props) => props.theme.colors.black};
border-right: 0.2rem solid ${(props) => props.theme.colors.black};
border-left: 0.1rem solid ${(props) => props.theme.colors.black};
background-color: ${(props) =>
props.iscolored ? props.theme.colors.primaryGreen : props.theme.colors.gray500};
z-index: 0;
`;

export const itemText = styled.div`
font-size: 3.4rem;
position: absolute;
z-index: 2;
`;

// keyframes을 동적으로 생성
const growHeight = (percent) => keyframes`
0% {
height: 0;
}
100% {
height: ${percent}%;
}
`;

export const percentBox = styled.div`
display: ${(props) => (props.isshow ? 'block' : 'none')};
background-color: ${(props) =>
props.first
? props.isselected
? props.theme.colors.percentBoxBlue
: props.theme.colors.gray350
: props.isselected
? props.theme.colors.percentBoxGreen
: props.theme.colors.gray350};
width: 100%;
bottom: 0;
position: absolute;
z-index: 1;
border-radius: ${(props) => (props.first ? '2rem 0 0 0' : '0 2rem 0 0')};
animation: ${(props) => growHeight(props.percent)} 0.7s ease-in-out forwards;
`;
2 changes: 1 addition & 1 deletion frontend/src/components/comments/comment/Comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function Comment(props) {
if (props.isBest) {
bestCommentLikeMutate({ isHeart: props.isHeart });
return;
} else {
} else {
commentLikeMutate({ isHeart: props.isHeart });
return;
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/itemcreate/ItemCreate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function ItemCreate() {
const handleOnclick = () => {
const firstItemText = textarea1Ref.current.value;
const secondItemText = textarea2Ref.current.value;

mutate({ firstItemText, secondItemText });
};
return (
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const END_POINTS = {
GET_MY_GAMES: (page, limit) => `/game/user?page=${page}&limit=${limit}`,
DELETE_MY_GAMES: (gameId) => `/game/${gameId}`,
DELETE_COMMENT: (commentId) => `/comment/${commentId}`,
SELECT_ITEM: (gameId, itemId) => `/games/${gameId}/items/${itemId}/select`,
};

export default END_POINTS;
97 changes: 97 additions & 0 deletions frontend/src/hooks/queries/useSelectItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import selectItem from '../../apis/selectItem';
import QUERY_KEYS from '../../constants/queryKeys';
import { toast } from 'react-toastify';

export default function useSelectItem(gameId) {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async (itemId) => {
const response = await selectItem(gameId, itemId);
return response;
},
onMutate: async (itemId) => {
await queryClient.cancelQueries([QUERY_KEYS.GAMES_ITEMS]);

const previousData = queryClient.getQueryData([QUERY_KEYS.GAMES_ITEMS]);

queryClient.setQueryData([QUERY_KEYS.GAMES_ITEMS], (oldData) => {
if (!oldData) return oldData;

const firstItemSelected = oldData.firstItem.item_id === itemId;
const secondItemSelected = oldData.secondItem.item_id === itemId;

// 이미 선택된 아이템을 다시 눌러서 선택을 취소하는 경우
if (firstItemSelected && oldData.firstItem.isSelected) {
return {
...oldData,
firstItem: {
...oldData.firstItem,
isSelected: false,
selected_count: oldData.firstItem.selected_count - 1,
},
};
}

if (secondItemSelected && oldData.secondItem.isSelected) {
return {
...oldData,
secondItem: {
...oldData.secondItem,
isSelected: false,
selected_count: oldData.secondItem.selected_count - 1,
},
};
}

// 반대쪽 아이템을 선택했을 때 기존 선택한 아이템의 선택 해제 및 카운트 감소
if (firstItemSelected && !oldData.firstItem.isSelected) {
return {
...oldData,
firstItem: {
...oldData.firstItem,
isSelected: true,
selected_count: oldData.firstItem.selected_count + 1,
},
secondItem: oldData.secondItem.isSelected
? {
...oldData.secondItem,
isSelected: false,
selected_count: oldData.secondItem.selected_count - 1,
}
: oldData.secondItem,
};
}

if (secondItemSelected && !oldData.secondItem.isSelected) {
return {
...oldData,
secondItem: {
...oldData.secondItem,
isSelected: true,
selected_count: oldData.secondItem.selected_count + 1,
},
firstItem: oldData.firstItem.isSelected
? {
...oldData.firstItem,
isSelected: false,
selected_count: oldData.firstItem.selected_count - 1,
}
: oldData.firstItem,
};
}

return oldData;
});

return { previousData };
},

onError: (err, variables, context) => {
console.log(err);
toast.error('다시 시도해주세요.');
queryClient.setQueryData([QUERY_KEYS.GAMES_ITEMS], context.previousData);
},
});
}
3 changes: 3 additions & 0 deletions frontend/src/styles/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ export const theme = {
gray350: '#C8C8C8',
gray400: '#444444',
gray450: '#A7A7A7',
gray500: '#D9D9D9',

black: '#080808',
primaryBlue: '#4DDDFF',
primaryGreen: '#64F0A0',
primaryRed: '#F34343',
percentBoxBlue: '#11CEFA',
percentBoxGreen: '#37EF85',
},
};
3 changes: 3 additions & 0 deletions frontend/src/utils/calculatePercent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function calculatePercent(value, total) {
return total > 0 ? ((value / total) * 100).toFixed(0) : 0;
}

0 comments on commit 505ae52

Please sign in to comment.