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

[fix] 어드민 페이지 관련 일부 에러 수정 #137

Merged
merged 6 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/adminPage/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const buildUrl = [
"events/[id]",
"comments",
"comments/[id]",
"users"
"users",
];

async function copyFolder(src, dest) {
Expand Down
111 changes: 78 additions & 33 deletions packages/adminPage/src/features/eventDetail/drawButton/DrawButton.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { fetchServer } from "@common/dataFetch/fetchServer.js";
import { useQuery, useMutation } from "@common/dataFetch/getQuery.js";

import Button from "@common/components/Button.jsx";
import openModal from "@common/modal/openModal.js";

import AlertModal from "@admin/modals/AlertModal.jsx";

import DrawResultModal from "./DrawResultModal.jsx";
Expand All @@ -12,17 +12,8 @@ import Suspense from "@common/components/Suspense.jsx";
import Spinner from "@common/components/Spinner.jsx";
import DelaySkeleton from "@common/components/DelaySkeleton.jsx";

function DrawButton() {
const { eventId } = useParams();
const drawResultData = useQuery(
`event-detail-draw-result-${eventId}`,
() => {
return fetchServer(`/api/v1/admin/draw/${eventId}/winners`);
},
{ deferred: true },
);

const resultModal = (
function ResultModalContainer({ eventId }) {
return (
<div className="w-[calc(100vw-8rem)] h-[calc(100vh-8rem)] p-8 bg-white relative">
<ErrorBoundary fallback={<div>에러남</div>}>
<Suspense
Expand All @@ -39,28 +30,82 @@ function DrawButton() {
</ErrorBoundary>
</div>
);
}

const mutate = useMutation(
`event-detail-draw-result-${eventId}`,
() => fetchServer(`/api/v1/admin/draw/${eventId}/draw`, { method: "post" }),
{
onSuccess: () => openModal(resultModal),
onFail: () =>
openModal(<AlertModal title="오류" description="추첨에 오류가 발생했습니다." />),
},
);
function DrawButton() {
const { eventId } = useParams();
const [drawState, setDrawState] = useState("BEFORE_END");
const interval = useRef(null);
const timeout = useRef(null);

if (drawResultData.length === 0)
return (
<Button className="w-32 h-8 px-4 py-1" onClick={mutate}>
추첨하기
</Button>
);
return (
<Button className="w-32 h-8 px-4 py-1" onClick={() => openModal(resultModal)}>
결과 보기
</Button>
);
useEffect(() => {
fetchServer(`/api/v1/admin/draw/${eventId}/status`)
.then(({ status }) => setDrawState(status))
.catch(() => {
setDrawState("ERROR");
});
}, [eventId]);

useEffect(() => {
return () => {
clearInterval(interval.current);
clearTimeout(timeout.current);
};
}, []);

async function onSubmit() {
function shortPooling() {
fetchServer(`/api/v1/admin/draw/${eventId}/status`)
.then(({ status }) => {
setDrawState(status);
if (status !== "IS_DRAWING") {
clearInterval(interval.current);
}
})
.catch(() => {
setDrawState("ERROR");
clearInterval(interval.current);
});
}

try {
await fetchServer(`/api/v1/admin/draw/${eventId}/draw`, { method: "post" });
setDrawState("IS_DRAWING");
openModal(<AlertModal title="성공" description="성공적으로 추첨 요청이 전송되었습니다." />);
timeout.current = setTimeout(() => {
shortPooling();
interval.current = setInterval(shortPooling, 5000);
}, 500);
} catch {
openModal(<AlertModal title="오류" description="추첨에 오류가 발생했습니다." />);
}
}

switch (drawState) {
case "BEFORE_END":
return null;
case "AVAILABLE":
return (
<Button className="w-32 h-8 px-4 py-1" onClick={onSubmit}>
추첨하기
</Button>
);
case "IS_DRAWING":
return (
<div className="w-32 h-8 px-4 py-1 bg-neutral-600 text-neutral-400">추첨 진행중...</div>
);
case "COMPLETE":
return (
<Button
className="w-32 h-8 px-4 py-1"
onClick={() => openModal(<ResultModalContainer eventId={eventId} />)}
>
결과 보기
</Button>
);
default:
return <div className="w-32 h-8 px-4 py-1 bg-neutral-600 text-neutral-400">에러</div>;
}
}

export default DrawButton;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { http, HttpResponse } from "msw";
import getRandomString from "@common/mock/getRandomString.js";

let result = [];
let status = "AVAILABLE";

function makeDrawComplete() {
const newResult = [];
Expand All @@ -24,11 +25,16 @@ function makeDrawComplete() {
const handlers = [
http.post("/api/v1/admin/draw/:eventId/draw", () => {
result = makeDrawComplete();
status = "IS_DRAWING";
setTimeout(() => (status = "COMPLETE"), 3000);
return new HttpResponse(null, { status: 201 });
}),
http.get("/api/v1/admin/draw/:eventId/winners", () => {
return HttpResponse.json(result);
}),
http.get("/api/v1/admin/draw/:eventId/status", () => {
return HttpResponse.json({ status });
}),
];

export default handlers;
18 changes: 16 additions & 2 deletions packages/adminPage/src/features/eventList/DeleteButton.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fetchServer } from "@common/dataFetch/fetchServer.js";
import { fetchServer, HTTPError } from "@common/dataFetch/fetchServer.js";
import { useMutation } from "@common/dataFetch/getQuery.js";
import ConfirmModal from "@admin/modals/ConfirmModal.jsx";
import AlertModal from "@admin/modals/AlertModal.jsx";
Expand All @@ -17,9 +17,23 @@ function DeleteButton({ selected, reset }) {
}),
{
onSuccess: () => {
openModal(<AlertModal title="삭제" description="기대평이 삭제되었습니다." />);
openModal(<AlertModal title="삭제" description="이벤트가 삭제되었습니다." />);
reset();
},
onError: async (e) => {
if (e instanceof HTTPError && e.status === 400) {
return openModal(
<AlertModal
title="오류"
description="진행 중이거나 삭제된 이벤트는 삭제가 불가능합니다."
/>,
);
}
if (e instanceof HTTPError && e.status === 404) {
return openModal(<AlertModal title="오류" description="존재하지 않는 이벤트입니다." />);
}
return openModal(<AlertModal title="오류" description="이벤트를 삭제할 수 없습니다." />);
},
},
);
const deleteConfirmModal = (
Expand Down
9 changes: 9 additions & 0 deletions packages/adminPage/src/features/eventList/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ const handlers = [
size,
});
}),
http.delete("/api/v1/admin/events/:id", async ({ params }) => {
const { id } = params;

const index = dummyData.findIndex(({ eventId }) => eventId === id);
if (index === -1) return HttpResponse.json(false);
dummyData.splice(index, 1);

return HttpResponse.json(true);
}),
http.delete("/api/v1/admin/events", async ({ request }) => {
const { eventIds } = await request.json();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Link } from "react-router-dom";

import tableTemplateCol from "./tableStyle.js";
import EventStatus from "@admin/serverTime/EventStatus.js";
import { useEventStatus } from "@admin/serverTime/EventStatus.js";
import Button from "@common/components/Button.jsx";
import Checkbox from "@common/components/Checkbox.jsx";
import { formatDate } from "@common/utils.js";

function SearchResultItem({ eventId, name, startTime, endTime, eventType, checked, setCheck }) {
const eventStatus = useEventStatus(startTime, endTime);

return (
<label className={`${tableTemplateCol} h-8 text-body-s bg-white hover:bg-blue-100`}>
<div className="flex justify-center items-center">
<Checkbox checked={checked} onChange={setCheck} />
<Checkbox checked={checked} onChange={setCheck} disabled={eventStatus !== "예정"} />
</div>
<div className="flex justify-center items-center font-medium">{eventId}</div>
<div className="flex justify-center items-center overflow-hidden">
Expand All @@ -27,9 +29,7 @@ function SearchResultItem({ eventId, name, startTime, endTime, eventType, checke
<div className="flex justify-center items-center">
{eventType === "fcfs" ? "선착순" : eventType === "draw" ? "추첨" : "???"}
</div>
<div className="flex justify-center items-center">
<EventStatus startTime={startTime} endTime={endTime} />
</div>
<div className="flex justify-center items-center">{eventStatus}</div>
<div className="flex justify-center items-center">
<Link to={`./${eventId}`}>
<Button styleType="ghost" className="px-2 py-1 text-body-s">
Expand Down
7 changes: 6 additions & 1 deletion packages/adminPage/src/features/eventList/table/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import Pagination from "@admin/components/Pagination.jsx";

import { useQuery } from "@common/dataFetch/getQuery.js";
import { fetchServer } from "@common/dataFetch/fetchServer.js";
import serverTimeStore from "@admin/serverTime/store.js";

function SearchResult({ query, queryState, queryDispatch, checkState, checkDispatch }) {
const { contents, totalPages } = useQuery(`admin-event-list@${query}`, () => fetchServer(query), {
deferred: true,
});

const checkSelect = () => {
const keys = contents.map(({ eventId }) => eventId);
const keys = contents
.filter(({ startTime }) => {
return new Date(startTime) > serverTimeStore.getState().serverTime;
})
.map(({ eventId }) => eventId);
checkDispatch({ type: "toggle_keys", keys });
};

Expand Down
7 changes: 6 additions & 1 deletion packages/adminPage/src/shared/serverTime/EventStatus.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import useServerTime from "./store.js";

function EventStatus({ startTime: _startTime, endTime: _endTime }) {
export function useEventStatus(_startTime, _endTime) {
const serverTime = useServerTime((store) => store.serverTime);
const startTime = _startTime instanceof Date ? _startTime : new Date(_startTime);
const endTime = _endTime instanceof Date ? _endTime : new Date(_endTime);
Expand All @@ -10,4 +10,9 @@ function EventStatus({ startTime: _startTime, endTime: _endTime }) {
return "종료";
}

function EventStatus({ startTime: _startTime, endTime: _endTime }) {
const status = useEventStatus(_startTime, _endTime);
return status;
}

export default EventStatus;
6 changes: 4 additions & 2 deletions packages/common/sharedAssetRouter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fileURLToPath } from "node:url";
import { join } from "node:path";
import { createReadStream } from "node:fs";
import { createReadStream, existsSync } from "node:fs";
import { lookup } from "mime-types";

const __dirname = fileURLToPath(new URL("../../", import.meta.url));
Expand All @@ -24,11 +24,13 @@ export default function sharedAssetRouter(paths) {
if (originPath === null) return next();
const filePath = join(__dirname, originPath);

if (!existsSync(filePath)) return next();

const stream = createReadStream(filePath);
stream.on("error", (err) => {
if (err.code === "ENOENT") {
res.statusCode = 404;
res.end("File not found");
res.end("Send Fallback");
} else {
res.statusCode = 500;
res.end("Server error");
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/components/Checkbox.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function Checkbox({ className, checked, onChange: userOnChange, defaultChecked, ...otherProps }) {
const checkboxStyle = `${className} size-4 appearance-none
border border-neutral-300 checked:bg-blue-400 checked:border-0
checked:bg-checked bg-center bg-cover`;
checked:bg-checked bg-center bg-cover disabled:bg-neutral-100`;

function onChange({ target }) {
userOnChange(target.checked);
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const EVENT_FCFS_ID = "HD_240821_001";
export const EVENT_DRAW_ID = "HD_240808_001";
export const EVENT_ID = "the-new-ioniq5";
export const EVENT_START_DATE = new Date(2024, 7, 20);
export const EVENT_START_DATE = new Date(2024, 7, 19);

export const SERVICE_TOKEN_ID = "AWESOME_ORANGE_ACCESS_TOKEN";
export const ADMIN_TOKEN_ID = "AWESOME_ORANGE_ADMIN_ACCESS_TOKEN";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function DetailSwiper({ content }) {
ref={swiperElRef}
>
{content.map((item) => (
<swiper-slide class={slideClass} key={item.title}>
<swiper-slide class={slideClass} key={item.title} lazy="true">
<DetailItem {...item} />
</swiper-slide>
))}
Expand Down
2 changes: 1 addition & 1 deletion packages/mainPage/src/features/interactions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function InteractionPage() {
ref={swiperRef}
>
{JSONData.interaction.map((interactionDesc, index) => (
<swiper-slide key={index} class="w-5/6 sm:w-[566px] h-[456px]">
<swiper-slide key={index} class="w-5/6 sm:w-[566px] h-[456px]" lazy="true">
<InteractionSlide
interactionDesc={interactionDesc}
index={index}
Expand Down