Skip to content

Commit

Permalink
Merge pull request #80 from softeerbootcamp4th/feature/13-fcfs
Browse files Browse the repository at this point in the history
[feat] 선착순 이벤트 일부 구현
  • Loading branch information
darkdulgi authored Aug 8, 2024
2 parents 5b16851 + fe7c1f2 commit 3e6b3db
Show file tree
Hide file tree
Showing 33 changed files with 691 additions and 70 deletions.
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Header from "./header";
import SimpleInformation from "./simpleInformation";
import DetailInformation from "./detailInformation";
import CommentSection from "./comment";
import FcfsSection from "./fcfs";
import QnA from "./qna";
import Footer from "./footer";
import Modal from "./modal/modal.jsx";
Expand All @@ -25,6 +26,7 @@ function App() {
<InteractionPage />
<DetailInformation />
<CommentSection />
<FcfsSection />
<QnA />
<Footer />
<Modal layer="interaction" />
Expand Down
2 changes: 1 addition & 1 deletion src/auth/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const handlers = [
{ error: "응답 내용이 잘못됨" },
{ status: 400 },
);
if (authCode !== "726679")
if (+authCode < 500000 === false)
return HttpResponse.json(
{ error: "인증번호 일치 안 함" },
{ status: 401 },
Expand Down
8 changes: 6 additions & 2 deletions src/comment/commentCarousel/CommentCarousel.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useQuery } from "@/common/dataFetch/getQuery.js";
import { fetchServer } from "@/common/dataFetch/fetchServer.js";
import AutoScrollCarousel from "../autoScrollCarousel";

function mask(string) {
Expand All @@ -15,8 +17,10 @@ function formatDate(dateString) {
return `${year}. ${month}. ${day}`;
}

function CommentCarousel({ resource }) {
const comments = resource().comments;
function CommentCarousel() {
const { comments } = useQuery("comment-data", () =>
fetchServer("/api/v1/comment"),
);

return (
<div className="w-full h-[29rem]">
Expand Down
5 changes: 1 addition & 4 deletions src/comment/commentCarousel/index.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { useMemo } from "react";
import Suspense from "@/common/Suspense.jsx";
import ErrorBoundary from "@/common/ErrorBoundary.jsx";
import { fetchResource } from "@/common/dataFetch/fetchServer.js";
import CommentCarousel from "./CommentCarousel.jsx";
import CommentCarouselSkeleton from "./CommentCarouselSkeleton.jsx";
import CommentCarouselError from "./CommentCarouselError.jsx";

function CommentCarouselView() {
const resource = useMemo(() => fetchResource("/api/v1/comment"), []);
return (
<ErrorBoundary fallback={<CommentCarouselError />}>
<Suspense fallback={<CommentCarouselSkeleton />}>
<CommentCarousel resource={resource} />
<CommentCarousel />
</Suspense>
</ErrorBoundary>
);
Expand Down
39 changes: 18 additions & 21 deletions src/common/ClientOnly.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { useSyncExternalStore, useEffect } from "react";
import { useSyncExternalStore } from "react";

const mountedStore = {
mounted: false,
listeners: new Set(),
mount() {
mountedStore.mounted = true;
mountedStore.listeners.forEach((listener) => listener());
},
subscribe(listener) {
mountedStore.listeners.add(listener);
return () => mountedStore.listeners.delete(listener);
},
getSnapshot() {
return mountedStore.mounted;
},
};
// const mountedStore = {
// mounted: false,
// listeners: new Set(),
// mount() {
// mountedStore.mounted = true;
// mountedStore.listeners.forEach((listener) => listener());
// },
// subscribe(listener) {
// mountedStore.listeners.add(listener);
// return () => mountedStore.listeners.delete(listener);
// },
// getSnapshot() {
// return mountedStore.mounted;
// },
// };

/**
* react 클라이언트 only 래퍼 입니다.
Expand All @@ -23,13 +23,10 @@ const mountedStore = {
*/
export default function ClientOnly({ children, fallback }) {
const mounted = useSyncExternalStore(
mountedStore.subscribe,
mountedStore.getSnapshot,
() => {},
() => true,
() => false,
);
useEffect(() => {
mountedStore.mount();
}, []);

if (!mounted) return fallback;
return children;
Expand Down
2 changes: 2 additions & 0 deletions src/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const TOKEN_ID = "AWESOME_ORANGE_ACCESS_TOKEN";

// scroll section constants
export const INTERACTION_SECTION = 1;
export const COMMENT_SECTION = 3;
export const FCFS_SECTION = 4;
19 changes: 1 addition & 18 deletions src/common/dataFetch/fetchServer.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import wrapPromise from "./wrapPromise.js";
import tokenSaver from "@/auth/tokenSaver.js";

const cacheMap = new Map();
const CACHE_DURATION = 0.2 * 1000;

class HTTPError extends Error {
constructor(response) {
super(response.status + " " + response.statusText);
Expand All @@ -20,12 +16,6 @@ class ServerCloseError extends Error {
}

function fetchServer(url, options = {}) {
const key = JSON.stringify({ url, options });
if (cacheMap.has(key)) {
const { promise, date } = cacheMap.get(key);
if (Date.now() - date < CACHE_DURATION) return promise;
}

// 기본적으로 옵션을 그대로 가져오지만, body가 존재하고 header.content-type을 설정하지 않는다면
// json으로 간주하여 option을 생성합니다.
const fetchOptions = { ...options };
Expand Down Expand Up @@ -65,17 +55,10 @@ function fetchServer(url, options = {}) {
}
throw e;
});
cacheMap.set(key, { promise, date: Date.now() });

return promise;
}

function fetchResource(url, loginStatus = false) {
return wrapPromise(
fetchServer(url, { credentials: loginStatus ? "include" : "same-origin" }),
);
}

function handleError(errorDescriptor) {
return (error) => {
if (error instanceof HTTPError) {
Expand All @@ -95,4 +78,4 @@ function handleError(errorDescriptor) {
};
}

export { fetchServer, fetchResource, handleError, HTTPError, ServerCloseError };
export { fetchServer, handleError, HTTPError, ServerCloseError };
26 changes: 26 additions & 0 deletions src/common/dataFetch/getQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import use from "./use.js";

const queryMap = new Map();
const CACHE_DURATION = 10 * 60 * 1000;

function isSame(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
return arr1.every((value, i) => value === arr2[i]);
}

export function getQuery(key, promiseFn, dependencyArray = []) {
if (queryMap.has(key)) {
const { promise, depArr } = queryMap.get(key);
if (isSame(depArr, dependencyArray)) return promise;
}
const promise = promiseFn();
queryMap.set(key, { promise, depArr: dependencyArray });
setTimeout(() => queryMap.delete(key), CACHE_DURATION);
return promise;
}

export function useQuery(key, promiseFn, dependencyArray = []) {
return use(getQuery(key, promiseFn, dependencyArray));
}

export const getQuerySuspense = useQuery;
19 changes: 19 additions & 0 deletions src/common/dataFetch/use.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function use(promise) {
if (promise.status === "resolved") return promise.value;
if (promise.status === "rejected") throw promise.error;
if (promise.status === "pending") throw promise;

promise.status = "pending";
promise
.then((e) => {
promise.status = "resolved";
promise.value = e;
return e;
})
.catch((e) => {
promise.status = "rejected";
promise.error = e;
});

throw promise;
}
23 changes: 0 additions & 23 deletions src/common/dataFetch/wrapPromise.js

This file was deleted.

19 changes: 19 additions & 0 deletions src/common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,22 @@ export function clamp(target, min, max) {
export function linearMap(ratio, min, max) {
return ratio * (max - min) + min;
}

const HOURS = 24;
const MINUTES = 60;
const SECONDS = 60;

export function padNumber(number) {
return number.toString().padStart(2, "0");
}

export function convertSecondsToString(time) {
if (time < 0) return "00 : 00 : 00";

const days = Math.floor(time / (HOURS * MINUTES * SECONDS));
const hours = Math.floor(time / (MINUTES * SECONDS)) % HOURS;
const minutes = Math.floor(time / SECONDS) % MINUTES;
const seconds = time % SECONDS;

return `${days > 0 ? days + " : " : ""}${[hours, minutes, seconds].map(padNumber).join(" : ")}`;
}
58 changes: 58 additions & 0 deletions src/eventDescription/EventDetail.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import makeHighlight from "./makeHighlight.jsx";

export default function EventDetail({
durationYear,
duration,
announceDate,
announceDateCaption,
howto,
}) {
return (
<div className="flex flex-col">
<span className="text-body-l pb-10 text-neutral-50 font-bold">
상세 안내
</span>

<div className="flex gap-5">
<div className="bg-neutral-900 p-6 flex flex-col font-bold">
<span className="text-body-m text-neutral-300">이벤트 기간</span>

<span className="pt-6 text-body-m text-blue-100">{durationYear}</span>

<span className="text-body-l text-blue-400">{duration}</span>
</div>

<div className="bg-neutral-900 p-6 flex flex-col">
<span className="text-body-m text-neutral-300 font-bold">
당첨자 발표
</span>

<span className="pt-6 text-body-l text-white font-bold">
{makeHighlight(announceDate, "font-normal text-neutral-300")}
</span>

<span className="pt-2 text-body-s text-neutral-300">
{announceDateCaption}
</span>
</div>
</div>

<div className="mt-5 p-6 bg-neutral-900 flex flex-col font-bold">
<span className="pb-6 text-body-m text-neutral-300">참여방법</span>

<ul className="flex flex-col gap-2">
{howto.map((description, index) => (
<li key={`howTo-${index}`} className="flex gap-2">
<span className="size-6 flex justify-center items-center bg-neutral-100 text-neutral-900 text-body-s px-2 py-0.5 rounded flex-shrink-0">
{index + 1}
</span>
<p className="text-neutral-400 text-body-m">
{makeHighlight(description, "text-white")}
</p>
</li>
))}
</ul>
</div>
</div>
);
}
17 changes: 17 additions & 0 deletions src/eventDescription/makeHighlight.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// **매일매일 공개되는** 더 뉴 아이오닉과 관련된 **인터랙션을 수행한다.**
// **...**로 감싸진 것은 강조입니다.

function makeHighlight(plainText, highlightClass) {
const tokened = plainText.split(/\*\*(.*?)\*\*/gm);

return tokened.map((content, index) => {
if (index % 2 === 0) return content;
return (
<span key={content + index} className={highlightClass}>
{content}
</span>
);
});
}

export default makeHighlight;
12 changes: 12 additions & 0 deletions src/fcfs/FcfsDescription.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import EventDetail from "@/eventDescription/EventDetail.jsx";
import content from "./content.json";

function FcfsDescription() {
return (
<div>
<EventDetail {...content} />
</div>
);
}

export default FcfsDescription;
Loading

0 comments on commit 3e6b3db

Please sign in to comment.