Skip to content

Commit

Permalink
feat: 밸런스게임 조회 모바일 페이지 ui 구현 및 api 연동 (#267)
Browse files Browse the repository at this point in the history
* feat: 밸런스게임 조회 관련 컴포넌트 구현 및 스토리북 작성

* feat: 게임 비회원 투표를 훅으로 분리

* feat: 게임 북마크 훅 분리

* feat: 게임 회원 투표를 훅으로 분리

* feat: 밸런스게임 조회 모바일 페이지 구현

* feat: 밸런스게임 조회 페이지에 모바일 훅 적용

* refactor: 게임이 존재할 때에 게임 컴포넌트가 보여지도록 수정

* refactor: BalanceGameButton 컴포넌트 스타일 선언 수정

* refactor: 스토리북 오류 수정

* refactor: BalanceGameButton의 outline 스타일 분리

* refactor: 단계 조절 함수를 하나로 통일하도록 수정

* refactor: 비회원 투표 함수에서 게임 단건의 아이디를 변수로 처리

* feat: 게임 북마크 실패 시 토스트 모달 제시 구현
  • Loading branch information
areumH authored Dec 29, 2024
1 parent a7bac45 commit 1f91ab7
Show file tree
Hide file tree
Showing 34 changed files with 1,312 additions and 175 deletions.
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PICK-O</title>
</head>

Expand Down
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import TalkPickPage from './pages/TalkPickPage/TalkPickPage';
import TalkPickPlacePage from './pages/TalkPickPlacePage/TalkPickPlacePage';
import SignUpPage from './pages/SignUpPage/SignUpPage';
import BalanceGamePage from './pages/BalanceGamePage/BalanceGamePage';
import BalanceGameMobilePage from './pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage';
import BalanceGameCreationPage from './pages/BalanceGameCreationPage/BalanceGameCreationPage';
// import NotAuthRoutes from './components/Routes/NotAuthRoutes';
// import { useMemberQuery } from './hooks/api/member/useMemberQuery';
Expand All @@ -43,9 +44,11 @@ import { getCookie } from './utils/cookie';
import { axiosInstance } from './api/interceptor';
import { useNewDispatch } from './store';
import { tokenActions } from './store/auth';
import useIsMobile from './hooks/common/useIsMobile';

const App: React.FC = () => {
const dispatch = useNewDispatch();
const isMobile = useIsMobile();

useEffect(() => {
const token = getCookie('accessToken');
Expand Down Expand Up @@ -74,7 +77,10 @@ const App: React.FC = () => {
<Route path={PATH.TODAY_TALKPICK} element={<TalkPickPage />} />
<Route path={PATH.TALKPICK_PLACE} element={<TalkPickPlacePage />} />
<Route path={PATH.TALKPICK()} element={<TalkPickPage />} />
<Route path={PATH.BALANCEGAME()} element={<BalanceGamePage />} />
<Route
path={PATH.BALANCEGAME()}
element={isMobile ? <BalanceGameMobilePage /> : <BalanceGamePage />}
/>

{/* <Route path="/search" element={<SearchResultsPage />} /> */}
{/* <Route path="posts" element={<PostList />} />
Expand Down
2 changes: 2 additions & 0 deletions src/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export { default as MobileFolder } from './svg/mobile-folder.svg';
export { default as MobileFolderCheck } from './svg/mobile-folder-check.svg';
export { default as MobileCheckIcon } from './svg/mobile-check-icon.svg';
export { default as MobileTempIcon } from './svg/mobile-temp-icon.svg';
export { default as RedCheckIcon } from './svg/red-check-icon.svg';
export { default as BlueCheckIcon } from './svg/blue-check-icon.svg';

// TODO: 이전 SVG
export { default as Email } from './svg/email.svg';
Expand Down
4 changes: 4 additions & 0 deletions src/assets/svg/blue-check-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/red-check-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import typo from '@/styles/typo';

export const buttonWrapStyle = css({
all: 'unset',
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
width: '305px',
backgroundColor: color.WT,
borderRadius: '10px',
overflow: 'hidden',
});

export const getOutlineStyle = (option: 'A' | 'B', isSelected: boolean) => {
if (!isSelected) return css({});

return css({
outline:
option === 'A' ? `2px solid ${color.RED}` : `2px solid ${color.BLUE}`,
});
};

export const nameWrapper = css({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
padding: '12px 20px',
});

export const nameStyle = css(typo.Mobile.Text.SemiBold_14, {
width: '220px',
fontSize: '16px',
});

export const imageStyle = css({
width: '90px',
height: '90px',
borderRadius: '10px',
objectFit: 'cover',
});

export const contentWrapper = css({
display: 'flex',
width: '100%',
padding: '15px 20px',
gap: '15px',
borderTop: `1px solid ${color.GY[4]}`,
});

export const descriptionStyle = css(typo.Mobile.Main.Regular_12, {
display: 'flex',
width: '100%',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { RedCheckIcon, BlueCheckIcon } from '@/assets';
import * as S from './BalanceGameButton.style';

export interface BalanceGameButtonProps {
name: string;
imgUrl: string | null;
description: string;
optionType: 'A' | 'B';
selectedButton: 'A' | 'B' | null;
onClick: (optionType: 'A' | 'B') => void;
}

const BalanceGameButton = ({
name,
imgUrl,
description,
optionType,
selectedButton,
onClick,
}: BalanceGameButtonProps) => {
const isSelected = selectedButton === optionType;
const hasOnlyTitle = !description && !imgUrl;

const handleClick = () => {
onClick(optionType);
};

const renderCheckedIcon = () => {
return optionType === 'A' ? <RedCheckIcon /> : <BlueCheckIcon />;
};

return (
<button
type="button"
css={[S.buttonWrapStyle, S.getOutlineStyle(optionType, isSelected)]}
onClick={handleClick}
>
<div css={S.nameWrapper}>
<div css={S.nameStyle}>{name}</div>
{isSelected && renderCheckedIcon()}
</div>
{!hasOnlyTitle && (
<div css={S.contentWrapper}>
{imgUrl && <img src={imgUrl} alt={name} css={S.imageStyle} />}
<div css={S.descriptionStyle}>{description}</div>
</div>
)}
</button>
);
};

export default BalanceGameButton;
2 changes: 1 addition & 1 deletion src/components/mobile/atoms/Button/Button.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const getSizeByVariantStyling = (
height: '50px',
}),
medium: css(typo.Main.SemiBold, {
width: '130px',
width: '160px',
height: '50px',
}),
},
Expand Down
10 changes: 3 additions & 7 deletions src/components/mobile/atoms/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import React, {
ComponentPropsWithoutRef,
ForwardedRef,
forwardRef,
} from 'react';
import React, { ComponentPropsWithRef, ForwardedRef, forwardRef } from 'react';
import * as S from './IconButton.style';

interface IconButtonProps extends ComponentPropsWithoutRef<'button'> {
interface IconButtonProps extends ComponentPropsWithRef<'button'> {
icon: React.ReactNode;
}

const IconButton = (
{ icon, onClick, ...props }: IconButtonProps,
{ icon, ...props }: IconButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
) => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import React, {
ComponentPropsWithoutRef,
ForwardedRef,
forwardRef,
} from 'react';
import React, { ComponentPropsWithRef, ForwardedRef, forwardRef } from 'react';
import * as S from './InteractionButton.style';

interface InteractionButtonProps extends ComponentPropsWithoutRef<'button'> {
interface InteractionButtonProps extends ComponentPropsWithRef<'button'> {
buttonLabel: string;
icon: React.ReactNode;
iconLabel: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from '@emotion/react';

export const containerStyle = css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px',
gap: '15px',
});
77 changes: 77 additions & 0 deletions src/components/mobile/molecules/BalanceGameBox/BalanceGameBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import { GameOption } from '@/types/game';
import BalanceGameButton from '@/components/mobile/atoms/BalanceGameButton/BalanceGameButton';
import { useNewSelector } from '@/store';
import { selectAccessToken } from '@/store/auth';
import { MyVoteOption, VoteOption } from '@/types/vote';
import { useUserGameVote } from '@/hooks/game/useBalanceGameVote';
import * as S from './BalanceGameBox.style';

export interface BalanceGameBoxProps {
gameSetId: number;
gameId: number;
options: GameOption[];
selectedVote: MyVoteOption;
handleNextStage: () => void;
handleGuestGameVote: (
selectedOption: MyVoteOption,
voteOption: VoteOption,
) => void;
}

const BalanceGameBox = ({
gameSetId,
gameId,
options,
selectedVote,
handleNextStage,
handleGuestGameVote,
}: BalanceGameBoxProps) => {
const accessToken = useNewSelector(selectAccessToken);
const optionA = options[0];
const optionB = options[1];

const { handleUserGameVote } = useUserGameVote(
gameSetId,
gameId,
handleNextStage,
);

const handleButtonClick = (voteOption: VoteOption) => {
if (accessToken) {
handleUserGameVote(selectedVote, voteOption);
} else {
handleGuestGameVote(selectedVote, voteOption);
setTimeout(() => {
handleNextStage();
}, 500);
}
};

return (
<div css={S.containerStyle}>
<BalanceGameButton
name={optionA?.name ?? ''}
imgUrl={optionA?.imgUrl ?? null}
description={optionA?.description ?? ''}
optionType={optionA?.optionType ?? 'A'}
selectedButton={selectedVote ?? null}
onClick={() => {
handleButtonClick('A');
}}
/>
<BalanceGameButton
name={optionB?.name ?? ''}
imgUrl={optionB?.imgUrl ?? null}
description={optionB?.description ?? ''}
optionType={optionB?.optionType ?? 'B'}
selectedButton={selectedVote ?? null}
onClick={() => {
handleButtonClick('B');
}}
/>
</div>
);
};

export default BalanceGameBox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import typo from '@/styles/typo';

export const balanceGameEndingStyling = css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '335px',
height: `calc(100vh - 80px)`,
maxHeight: '800px',
position: 'relative',
});

export const titleStyling = css(typo.Mobile.Text.Bold_20, {
color: color.MAIN,
width: '260px',
marginTop: '80px',
textAlign: 'center',
});

export const checkIconWrapper = css({
marginTop: '80px',
marginBottom: '100px',
});

export const buttonWrapper = css({
display: 'flex',
gap: '10px',
});

export const buttonStyling = css({
display: 'flex',
alignItems: 'center',
position: 'absolute',
bottom: '0',
left: '50%',
transform: 'translate(-50%)',
});

export const toastModalStyling = css({
position: 'fixed',
top: '65px',
left: '50%',
transform: 'translate(-50%)',
zIndex: '1000',
});

export const centerStyling = css({
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: '1000',
});
Loading

0 comments on commit 1f91ab7

Please sign in to comment.