Skip to content

Commit

Permalink
Merge pull request #79 from softeerbootcamp4th/feature/10-interaction…
Browse files Browse the repository at this point in the history
…page

[feat] 인터랙션 모달 구현
  • Loading branch information
lybell-art authored Aug 8, 2024
2 parents 3e6b3db + 405c361 commit 0efac08
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 23 deletions.
6 changes: 6 additions & 0 deletions public/icons/close-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/icons/left-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/polygon-tri.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/icons/refresh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
98 changes: 98 additions & 0 deletions src/interactions/InteractionAnswer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import userStore from "@/auth/store.js";
import scrollTo from "@/scroll/scrollTo";
import style from "./InteractionAnswer.module.css";
import { useState } from "react";

export default function InteractionAnswer({
isAnswerUp,
setIsAnswerUp,
answer,
close,
}) {
const isLogin = userStore((state) => state.isLogin);
const [isAniPlaying, setIsAniPlaying] = useState(false);

function onClickWrite() {
close();
scrollTo(3);
}

function onClickShare() {
setIsAniPlaying(true);

/*
* 서버에서 받아온 단축 url을 클립보드에 복사하는 코드 미구현
*/
}

return (
<div
className={`top-0 ${isAnswerUp ? "" : "translate-y-full"} absolute w-full h-full flex items-center justify-center bg-black/90 transition-all ease-out duration-300`}
>
<span
onAnimationEnd={() => setIsAniPlaying(false)}
className={`${isAniPlaying ? style.toast : ""} opacity-0 fixed top-10 left-1/2 -translate-x-1/2 px-8 py-4 rounded-full bg-blue-100 text-neutral-600 text-body-m font-bold`}
>
단축 URL이 클립보드에 복사 되었습니다!
</span>

<button
onClick={() => setIsAnswerUp(false)}
className="absolute top-10 left-10 p-3 bg-neutral-800 rounded-full"
>
<img src="icons/left-arrow.svg" alt="뒤로가기" />
</button>

<div className="w-1/2 flex gap-8">
<span className="text-head-l text-blue-400 font-bold whitespace-pre">
{answer.head}
</span>
<div className="flex flex-col gap-4">
<span className="text-title-s text-neutral-50">{answer.desc}</span>

<span className="text-body-s text-neutral-300">{answer.subdesc}</span>
</div>
</div>

<div className="absolute bottom-10 flex flex-col items-center gap-10">
{isLogin ? (
<>
<span className="text-body-m text-green-400 font-bold">
오늘 응모가 완료되었습니다!
</span>

<div className="flex gap-4 items-end">
<div className="flex flex-col gap-2">
<div className="relative flex flex-col items-center animate-bounce">
<span className=" bg-green-400 text-nowrap text-body-m text-neutral-800 rounded-full px-8 py-2 font-bold">
당첨확률 UP!
</span>

<img src="icons/polygon-tri.svg" alt="역삼각형" />
</div>

<button
onClick={onClickWrite}
className="bg-white text-body-m text-black px-10 py-4"
>
기대평 작성하기
</button>
</div>

<button
onClick={onClickShare}
className="border-2 border-neutral-300 text-body-m text-white px-10 py-[14px]"
>
공유하기
</button>
</div>
</>
) : (
<button className="text-body-m text-black bg-white px-10 py-4">
응모하기
</button>
)}
</div>
</div>
);
}
18 changes: 18 additions & 0 deletions src/interactions/InteractionAnswer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.toast {
animation: toast-ani 5s linear;
}

@keyframes toast-ani {
0% {
opacity: 0;
}
5% {
opacity: 1;
}
95% {
opacity: 1;
}
100% {
opacity: 0;
}
}
64 changes: 64 additions & 0 deletions src/interactions/InteractionModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Suspense from "@/common/Suspense.jsx";
import { ModalCloseContext } from "@/modal/modal.jsx";
import { lazy, useContext, useRef, useState } from "react";
import InteractionAnswer from "./InteractionAnswer";

const lazyInteractionList = [
lazy(() => import("./distanceDriven")),
lazy(() => import("./fastCharge")),
lazy(() => import("./univasalIsland")),
lazy(() => import("./v2l")),
lazy(() => import("./subsidy")),
];

export default function InteractionModal({ index, answer }) {
const close = useContext(ModalCloseContext);
const InteractionComponent = lazyInteractionList[index];
const [isActive, setIsActive] = useState(false);
const [isAnswerUp, setIsAnswerUp] = useState(false);
const interactionRef = useRef(null);

if (!InteractionComponent) return <></>;

return (
<Suspense fallback={<div>Loading...</div>}>
<div className="w-5/6 h-5/6 relative backdrop-blur-[100px] border border-neutral-600 rounded overflow-hidden">
<button
onClick={close}
className="z-10 absolute top-10 right-10 bg-neutral-800 p-3 rounded-full"
>
<img src="icons/close-white.svg" alt="닫기" />
</button>

<InteractionComponent
interactCallback={() => setIsActive(true)}
$ref={interactionRef}
/>

<div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex gap-4">
<button
onClick={() => setIsAnswerUp(true)}
disabled={!isActive}
className={`${isActive ? "opacity-100" : "opacity-50"} bg-white px-10 py-4 text-black text-body-s`}
>
확인하기
</button>

<button
onClick={() => interactionRef.current.reset()}
className="border-2 border-neutral-100 p-2"
>
<img src="icons/refresh.svg" alt="다시하기" />
</button>
</div>

<InteractionAnswer
isAnswerUp={isAnswerUp}
setIsAnswerUp={setIsAnswerUp}
answer={answer}
close={close}
/>
</div>
</Suspense>
);
}
34 changes: 11 additions & 23 deletions src/interactions/InteractionSlide.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import openModal from "@/modal/openModal.js";
import { lazy } from "react";
import Suspense from "@/common/Suspense.jsx";
import InteractionModal from "./InteractionModal";

export default function InteractionSlide({
interactionDesc,
index,
isCurrent,
joined,
swiperRef,
answer,
}) {
const activeImgPath = `active${index + 1}.png`;
const inactiveImgPath = `inactive${index + 1}.png`;
Expand All @@ -23,33 +24,20 @@ export default function InteractionSlide({
return `${month < 9 ? "0" : ""}${month + 1}${date < 10 ? "0" : ""}${date}일(${day[fullDate.getDay()]})`;
}

const lazyInteractionList = [
lazy(() => import("./distanceDriven")),
lazy(() => import("./fastCharge")),
lazy(() => import("./univasalIsland")),
lazy(() => import("./v2l")),
lazy(() => import("./subsidy")),
];

function onClickExperience() {
if (joined < 0) return;

const InteractionComponent = lazyInteractionList[index];
if (!InteractionComponent) return;

const InteractionModal = (
<Suspense fallback={<div>Loading...</div>}>
<div className="w-5/6 h-5/6 backdrop-blur-[100px] border border-neutral-600 rounded">
<InteractionComponent />
</div>
</Suspense>
openModal(
<InteractionModal index={index} answer={answer} />,
"interaction",
);

openModal(InteractionModal, "interaction");
}

return (
<div className="w-full h-full flex flex-col items-center select-none">
<div
onClick={() => swiperRef.current.swiper.slideTo(index)}
className="w-full h-full flex flex-col items-center select-none"
>
<span className="pt-[150px] text-body-l text-white font-bold">
{eventDate()}
</span>
Expand All @@ -70,7 +58,7 @@ export default function InteractionSlide({
className={`mt-8 py-4 px-10 bg-white ${joined < 0 ? "hidden" : isCurrent ? "opacity-100" : "opacity-50"}`}
>
<span className="text-body-s text-black font-bold">
안터랙션 체험하기
인터랙션 체험하기
</span>
</button>

Expand Down
27 changes: 27 additions & 0 deletions src/interactions/content.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@
"언제 어디서나, 편리하게",
"누구보다 경제적으로"
],
"answer": [
{
"head": "485km",
"desc": "더 뉴 아이오닉 5는 84.0kWh의 4세대 배터리 셀을 탑재하여 보다 여유있는 장거리 주행이 가능합니다.",
"subdesc": "배터리 용량이 늘어났음에도 기존과 동일하거나 더 빠른 속도로 차량을 충전할 수 있습니다. 또한 다양한 EV 충전 솔루션과 충전 서비스를 통해 차별화된 충전 경험을 제공합니다."
},
{
"head": "18분",
"desc": "더 뉴 아이오닉 5는 350kW 급속 충전기 사용 시 18분 이내에 배터리 용량의 10%에서 80%까지 충전이 가능합니다.",
"subdesc": "배터리 용량이 늘어났음에도 기존과 동일하거나 더 빠른 속도로 차량을 충전할 수 있습니다. 또한 다양한 EV 충전 솔루션과 충전 서비스를 통해 차별화된 충전 경험을 제공합니다."
},
{
"head": "Universal\nIsland",
"desc": "더 뉴 아이오닉 5는 유니버설 아일랜드를 적용해 다양한 상황에 맞는 최적화된 공간 활용성을 제공합니다.",
"subdesc": "기존 모델과 달리 더 뉴 아이오닉 5에서는 자주 사용하는 기능을 버튼식으로 배치했으며, 스마트폰 무선 충전 패드도 상단으로 옮겨 사용 편의성을 높였습니다."
},
{
"head": "V2L",
"desc": "더 뉴 아이오닉 5는 차량 외부로 전력을 공급할 수 있는 V2L 기능을 통해 새로운 전동화 경험을 제공합니다.",
"subdesc": "야외활동 시 여러가지 외부환경에서도 다양한 전자기기 사용이 가능합니다. 또한 2열 시트 하단의 실내 V2L을 사용하여 차량 내부에서도 배터리 걱정 없는 전자기기 사용이 가능합니다."
},
{
"head": "690만원",
"desc": "더 뉴 아이오닉 5의 뛰어난 성능과 합리적인 가격으로 최대 690만원의 국비 보조금을 혜택을 누릴 수 있습니다.",
"subdesc": "에너지 밀도와 재활용 가치가 높은 4세대 배터리를 탑재하여 성능보조금 100%를 지원 받을 수 있으며 더 뉴 아이오닉 5만의 혁신적인 기술과 충전 인프라로 추가적인 경제적 혜택을 제공받을 수 있습니다."
}
],
"howto": [
["매일매일 공개되는", " 더 뉴 아이오닉5과 관련한 ", "인터랙션을 수행한다."],
["", "이벤트 경품을 받기 위한 ", "필수 정보를 입력하면 응모 완료!"],
Expand Down
2 changes: 2 additions & 0 deletions src/interactions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export default function InteractionPage() {
index={index}
isCurrent={currentInteraction === index}
joined={joinedList[index]}
swiperRef={swiperRef}
answer={JSONData.answer[index]}
/>
</swiper-slide>
))}
Expand Down

0 comments on commit 0efac08

Please sign in to comment.