Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 숨은그림찾기 clinet 내부로직 구현 완료 #9

Merged
merged 18 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
48e46b0
fix: 사용하지않는 useRef삭제 (CC-108)
Dunkkkk Jul 31, 2024
88aece6
fix: overlay가 state를 부모에게서 받지 않고 스스로 만들고록 변경 (CC-108)
Dunkkkk Jul 31, 2024
36932e1
feat: 3분이 끝나면 자동으로 overlay가 내려가도록 구현 (CC-108)
Dunkkkk Jul 31, 2024
fd1946c
fix: FindingGame 변수명 변경 (CC-108)
Dunkkkk Jul 31, 2024
45e3884
comment: Reducer에 미래의 개선사항 주석추가 (CC-108)
Dunkkkk Jul 31, 2024
4173b25
comment: smile bage의 타입에 따른 index 주석추가 (CC-108)
Dunkkkk Jul 31, 2024
655abb7
feat: 최종 응모가 완료됬을 때 보인는 overlay contet 구현 (CC-108)
Dunkkkk Jul 31, 2024
a108d19
feat: 선착순 응모가 실패했을 때 보이는 overlay content 구현 (CC-108)
Dunkkkk Jul 31, 2024
5ca8d9d
feat: 핸드폰 번호를 입력하는 공통 overlay content 구현 (CC-108)
Dunkkkk Jul 31, 2024
0845cb7
feat: 선착순으로 게임을 완료했을 때 보이는 overlay content 구현 (CC-108)
Dunkkkk Jul 31, 2024
e70338a
fix: 기존의 useWork에서 disptach 함수를 리턴하도록 수정 (CC-108)
Dunkkkk Jul 31, 2024
ce91a1b
feat: 완료된 overlay content들 적용 (CC-108)
Dunkkkk Jul 31, 2024
b4fcac7
feat: 숨은그림찾기에서 힌트를 나타내는 hintSpot 이미지 추가 (CC-108)
Dunkkkk Jul 31, 2024
68af562
feat: hintTalk svg 추가 (CC-108)
Dunkkkk Jul 31, 2024
e2eb903
feat: hint를 배열로관리하는 state 추가 (CC-108)
Dunkkkk Jul 31, 2024
10fac0e
feat: hintSpot을 활용하여 반짝이는 component 추가 (CC-108)
Dunkkkk Jul 31, 2024
80b7147
feat: 40초마다 hint를 띄워달라고 요청하는 로직 추가 (CC-108)
Dunkkkk Jul 31, 2024
2e1367f
feat: hint가 있으면 bade가 talk하는 로직 구현 (CC-108)
Dunkkkk Jul 31, 2024
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
27 changes: 25 additions & 2 deletions Caecae/src/Component/FindingGame/FindingGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,34 @@ import {
initFindingGameState,
} from "../../Job/FindingGame/FindingGame.tsx";
import store from "../../Shared/Hyundux/Store.tsx";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import LottieContainer from "../../Widget/LottieContainer/LottieContainter.tsx";
import correctLottie from "../../Shared/assets/animationCorrect.json";
import wrongLottie from "../../Shared/assets/animationIncorrect.json";
import useExistState from "../../Shared/Hyundux/Hooks/useExistState.tsx";
import HintSpot from "./Hint/HintSpot.tsx";

const FindingGame = () => {
const state = useExistState(initFindingGameState);
const timerId = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
store.dispatch(action.init());
timerId.current = setTimeout(() => {
store.dispatch(action.showHint());
}, 40000);
}, []);

useEffect(() => {
if (state.showingHint.length == 0) {
if (timerId.current != null) {
clearInterval(timerId.current);
}
timerId.current = setTimeout(() => {
store.dispatch(action.showHint());
}, 40000);
}
}, [state.showingHint]);

const imgURL =
"https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202109/01/b57fdda5-3996-430f-8bf2-65052b1d12b2.jpg";

Expand Down Expand Up @@ -59,12 +75,19 @@ const FindingGame = () => {
/>
);
});
const showingHintElement = state.showingHint.map((hintAnswer) => {
return <HintSpot y={hintAnswer.y} x={hintAnswer.x} />;
});

return (
<div>
<PictureGameBoard
imageURL={imgURL}
showingElements={[...showingCorrectElements, ...showingWrongElement]}
showingElements={[
...showingCorrectElements,
...showingWrongElement,
...showingHintElement,
]}
onClickAction={onClickAction}
/>
</div>
Expand Down
5 changes: 5 additions & 0 deletions Caecae/src/Component/FindingGame/FindingGameInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ const FindingGameInfo = () => {
{badges}
</div>
</div>
{state.showingHint.length != 0 ? (
<div className="flex justify-center items-center">
<img src="/src/Shared/assets/hintTalk.svg" alt="hintTalk" />
</div>
) : null}
</div>
</>
);
Expand Down
12 changes: 12 additions & 0 deletions Caecae/src/Component/FindingGame/Hint/HintSpot.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@keyframes fade-in-out {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0;
}
}

.fade-in-out {
animation: fade-in-out 1s ease-in-out infinite;
}
29 changes: 29 additions & 0 deletions Caecae/src/Component/FindingGame/Hint/HintSpot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import "./HintSpot.css";
import React from "react";

interface HintSpotProps {
y: number;
x: number;
}

const HintSpot: React.FC<HintSpotProps> = ({ y, x }) => {
const style: React.CSSProperties = {
position: "absolute",
zIndex: 20,
top: `${y - 25}px`,
left: `${x - 25}px`,
width: "50px",
animation: "fade-in-out 1s ease-in-out infinite",
};

return (
<img
src="/src/Shared/assets/hintSpot.svg"
alt="hintImg"
style={style}
className="fade-in-out"
/>
);
};

export default HintSpot;
154 changes: 154 additions & 0 deletions Caecae/src/Component/PhoneNumberOverlay/PhoneNumberOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { ChangeEventHandler, useEffect, useState } from "react";
import { action } from "../../Job/Overlay/OverlayWork";
import store from "../../Shared/Hyundux/Store";

const PhoneNumberOverlay = () => {
const [timeLeft, setTimeLeft] = useState(3 * 60); // 3분을 초 단위로 변환
const [phoneNumber, setPhoneNumber] = useState("");
const [check, setCheck] = useState(false);
const [enterable, setEnterable] = useState(false);

const timeToString = () => {
const minute = Math.floor(timeLeft / 60);
const second = timeLeft % 60;
const minuteStr = "0" + `${minute}`;
const secondStr = second < 10 ? "0" + `${second}` : `${second}`;
return minuteStr + ":" + secondStr;
};

useEffect(() => {
const intervalId = setInterval(() => {
setTimeLeft((prevTime) => {
if (prevTime <= 1) {
// Todo: 여기 있는 store 제거하기
store.dispatch(action.toggleOverlay());
clearInterval(intervalId);
return 0;
}
return prevTime - 1;
});
}, 1000);

return () => clearInterval(intervalId);
});

const onPhoneNumberFieldChange: ChangeEventHandler<HTMLInputElement> = (
event
) => {
let number = "";
const cleaned = event.target.value.replace(/\D/g, "");

// 유효성 검사
if (cleaned.length <= 3) {
number = cleaned;
setPhoneNumber(number);
} else if (cleaned.length <= 7) {
number = `${cleaned.slice(0, 3)}-${cleaned.slice(3)}`;
setPhoneNumber(number);
} else if (cleaned.length <= 11) {
number = `${cleaned.slice(0, 3)}-${cleaned.slice(3, 7)}-${cleaned.slice(
7,
11
)}`;
if (cleaned.length == 11) {
console.log(1234123);
}
setPhoneNumber(number);
}
};

const onCheckboxChange: ChangeEventHandler<HTMLInputElement> = () => {
setCheck((prev) => !prev);
};

useEffect(() => {
const number = phoneNumber.split("-").join("");
if (
check &&
number.length == 11 &&
number.slice(0, 3) === "010" &&
enterable === false
) {
setEnterable(true);
} else if (enterable === true) {
setEnterable(false);
}
}, [check, phoneNumber]);

return (
<div className="flex flex-col h-full w-full">
<div className="pl-[59px] pr-[59px] grow pl-[60px] pt-[80px]">
<p className="text-2xl font-medium">전화번호 입력</p>
<div className="mt-[7px]">
<span className="text-[red] underline">
<span>{timeToString()}</span>내
</span>
<span>에 입력하지 않으면 미당첨으로 간주되어 자동 종료됩니다.</span>
</div>
<div className="flex items-center mt-[45px] justify-between">
<p className="font-medium font-xl mr-[80px]">전화번호</p>
<input
type="text"
value={phoneNumber}
onChange={onPhoneNumberFieldChange}
placeholder={""}
className="border border-gray-300 bg-white py-2 px-4 text-base focus:focus:border-[#002C5F] w-[600px] h-[55px]"
/>
</div>
<div className="flex mt-[45px] justify-between">
<p className="font-medium font-xl mr-[80px] pt-[12px]">
개인정보 동의
</p>
<div>
<div className="border border-gray-300 bg-white py-2 px-4 w-[600px] h-[140px] overflow-scroll">
<p>
1. 개인정보의 처리 목적
<br />
개인정보를 다음의 목적을 위해 처리합니다. 처리한 개인정보는
다음의 목적 이외의 용도로는 사용되지 않으며 이용 목적이 변경될
시에는 사전 동의를 구할 예정입니다.
<br />
1. 개인정보의 처리 목적
<br />
개인정보를 다음의 목적을 위해 처리합니다. 처리한 개인정보는
다음의 목적 이외의 용도로는 사용되지 않으며 이용 목적이 변경될
시에는 사전 동의를 구할 예정입니다.
<br />
1. 개인정보의 처리 목적
<br />
개인정보를 다음의 목적을 위해 처리합니다. 처리한 개인정보는
다음의 목적 이외의 용도로는 사용되지 않으며 이용 목적이 변경될
시에는 사전 동의를 구할 예정입니다.
<br />
</p>
</div>
<div className="flex mt-[10px] items-center">
<input
type="checkbox"
checked={check}
onChange={onCheckboxChange}
className="form-checkbox h-4 w-4 text-[#002C5F] border-[#DDD] bg-neutral-white mr-[5px]"
/>
<p>
개인정보보호법에 따라 귀하의 개인정보를 다음과 같이
수집・이용하는데 동의합니다.
</p>
</div>
</div>
</div>
</div>
<div
onClick={() => {
store.dispatch(action.nextPage());
}}
className={`bg-[${
enterable ? "#002C5F" : "#CCCCCC"
}] h-[12%] flex items-center justify-center`}
>
<p className="text-[white] text-[20px] font-bold">응모 완료가기</p>
</div>
</div>
);
};

export default PhoneNumberOverlay;
41 changes: 34 additions & 7 deletions Caecae/src/Job/FindingGame/FindingGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ import Reducer from "../../Shared/Hyundux/Reducer";
import { Action } from "../../Shared/Hyundux/Actions";
import FindingGameAnswer from "../../Shared/Types/FindingGameAnswer";

const WORKFLOW_NAME = "FindingGame";
const WORK_NAME = "FindingGame";

// state type
interface FindingGamePayLoad {
hintIntervalId: NodeJS.Timeout | null;
answers: FindingGameAnswer[];
showingAnswers: FindingGameAnswer[];
wrongAnswers: { id: number; y: number; x: number }[];
showingHint: FindingGameAnswer[];
}

const initFindingGameState = createState<FindingGamePayLoad>(WORKFLOW_NAME, {
const initFindingGameState = createState<FindingGamePayLoad>(WORK_NAME, {
hintIntervalId: null,
answers: [],
showingAnswers: [],
wrongAnswers: [],
showingHint: [],
});

// define reducer
const findingGameReducer: Reducer<FindingGamePayLoad> = {
type: WORKFLOW_NAME,
type: WORK_NAME,
reducer: async function reducer(state, action) {
const payLoad = state.payload;
switch (action.actionName) {
Expand All @@ -31,6 +35,7 @@ const findingGameReducer: Reducer<FindingGamePayLoad> = {
{ id: Math.random(), y: 100, x: 100, imageURL: null, info: null },
{ id: Math.random(), y: 500, x: 500, imageURL: null, info: null },
];

return makePayLoad(state, { answers: fetchedAnswers });
}
case "click": {
Expand All @@ -56,7 +61,10 @@ const findingGameReducer: Reducer<FindingGamePayLoad> = {
}
});
if (isCorrect) {
return makePayLoad(state, { showingAnswers: showingAnswers });
return makePayLoad(state, {
showingAnswers: showingAnswers,
showingHint: [],
});
} else {
const _wrongAnswers: { id: number; y: number; x: number }[] = [
...payLoad.wrongAnswers,
Expand All @@ -74,6 +82,19 @@ const findingGameReducer: Reducer<FindingGamePayLoad> = {
);
return makePayLoad(state, { wrongAnswers: _wrongAnswers });
}
case "showHint": {
if (state.payload.showingAnswers.length < 2) {
const idsInAnswers = new Set(
state.payload.showingAnswers.map((item) => item.id)
);
const newHints = state.payload.answers.filter(
(item) => !idsInAnswers.has(item.id)
);
const newShowingHints = [newHints[0]];
return makePayLoad(state, { showingHint: newShowingHints });
}
return state;
}
default:
return state;
}
Expand All @@ -84,13 +105,13 @@ const findingGameReducer: Reducer<FindingGamePayLoad> = {
const action = {
init: (): Action => {
return {
type: WORKFLOW_NAME,
type: WORK_NAME,
actionName: "init",
};
},
click: (y: number, x: number): Action => {
return {
type: WORKFLOW_NAME,
type: WORK_NAME,
actionName: "click",
payload: {
id: Math.random(),
Expand All @@ -101,13 +122,19 @@ const action = {
},
removeWrongAnswer: (id: number): Action => {
return {
type: WORKFLOW_NAME,
type: WORK_NAME,
actionName: "removeWrongAnswer",
payload: {
id: id,
},
};
},
showHint: (): Action => {
return {
type: WORK_NAME,
actionName: "showHint",
};
},
};

const calculateRange = (
Expand Down
Loading