Skip to content

Commit

Permalink
Merge pull request #98 from naya-h2/fix/edit
Browse files Browse the repository at this point in the history
✨ feat: 수정페이지 유저id 로직 추가 & 아티스트 무한스크롤
  • Loading branch information
naya-h2 authored Feb 17, 2024
2 parents 9dec74d + 5a5676c commit 94f47b0
Show file tree
Hide file tree
Showing 30 changed files with 365 additions and 200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const EditBox = ({ children, isEdited = false }: Props) => {
return (
<div className="text-14 text-gray-500">
{isEdited ? "수정 내용" : "기존 내용"}
<div className={classNames("min-h-40 rounded-sm border-[0.15rem] border-gray-100 px-12 py-8 text-16 text-gray-900", { "!border-blue/25": isEdited })}>{children}</div>
<div className={classNames("min-h-40 whitespace-pre-wrap rounded-sm border-[0.15rem] border-gray-100 px-12 py-8 text-16 text-gray-900", { "!border-blue/25": isEdited })}>
{children}
</div>
</div>
);
};
Expand Down
28 changes: 22 additions & 6 deletions app/(route)/(header)/event/[eventId]/approve/[editId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import { useQuery } from "@tanstack/react-query";
import Link from "next/link";
import { useParams } from "next/navigation";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { instance } from "@/api/api";
import { useSession } from "@/store/session/cookies";
import { EditErrMsgType } from "@/types/errorMsgType";
import { CategoryType, EditContentType, LabelType, PostValueType } from "@/types/index";
import { EDIT_ERR_MSG } from "@/constants/errorMsg";
import { LABEL_BY_CATEGORY, exceptionList } from "@/constants/post";
import ApproveIcon from "@/public/icon/edit-approve.svg";
import DeclineIcon from "@/public/icon/edit-reject.svg";
import LinkIcon from "@/public/icon/link.svg";
import IdIcon from "@/public/icon/user.svg";
import BottomDoubleButton from "../../_components/BottomDoubleButton";
Expand All @@ -18,14 +22,16 @@ import RenderException from "../_components/RenderException";
import EditBox from "./_components/EditBox";

const EditDetailApprove = () => {
const router = useRouter();
const { eventId, editId } = useParams();
const [originData, setOriginData] = useState<EditContentType>();
const { editId } = useParams();
const { data, isSuccess, refetch } = useQuery({
queryKey: ["approveDetail", editId],
queryFn: async () => {
return instance.get(`/event/update/application/${editId}`, { eventUpdateApplicationId: String(editId) });
},
});
const session = useSession();

useEffect(() => {
if (isSuccess) {
Expand All @@ -42,10 +48,19 @@ const EditDetailApprove = () => {
}, [data]);

const handleApplicationSubmit = async (isApproved: boolean) => {
const res = await instance.post("/event/update/approval", { eventUpdateApplicationId: String(editId), isApproved, userId: "edit-api" });
refetch();
if (res.error) {
toast(EDIT_ERR_MSG[res.statusCode as "409" | "500"], { icon: "⚠️", className: "text-14 !text-red font-600" });
try {
if (!session) throw Error("Unauthorized");
if (session.user.userId === data.applicationDetail.userId) throw Error("the applicant is the author");
const res = await instance.post("/event/update/approval", { eventUpdateApplicationId: String(editId), isApproved, userId: "edit-api" });
refetch();
toast(EDIT_ERR_MSG[isApproved ? "approve" : "reject"], {
icon: isApproved ? <ApproveIcon width="20" height="20" /> : <DeclineIcon width="20" height="20" />,
className: "text-16 font-500",
});
router.replace(`/event/${eventId}/approve`);
} catch (err: any) {
toast.error(EDIT_ERR_MSG[err.message as EditErrMsgType], { className: "text-16 !text-red font-500" });
if (err.message === "Unauthorized") router.push("/signin");
}
};

Expand Down Expand Up @@ -74,6 +89,7 @@ const EditDetailApprove = () => {
<>{JSON.parse(data.applicationDetail.updateData)[data.applicationDetail.updateCategory]}</>
)}
</EditBox>
{data.applicationDetail.updateCategory === "eventImages" && <p className="text-14 text-gray-400">사진을 클릭해 확인해 보세요.</p>}
</section>
<section className="flex flex-col gap-4">
승인 현황
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,30 @@ const EditCard = ({ id, type, editContent, count, createdAt, category }: Props)
const curPath = usePathname();

return (
<div className="relative flex w-full flex-col gap-8 border-b border-gray-100 py-16 pr-24 text-14 font-500">
<div className="text-gray-500">{type}</div>
{exceptionList.includes(type) ? (
<RenderException editContent={editContent} instance={instance} type={type} />
) : (
<div className="truncate text-16 text-gray-900">{editContent[category as PostValueType]}</div>
)}
<div className="flex items-center gap-12 text-12 text-gray-400">
<div className="flex gap-4">
승인
<ReactionIcon count={count.approve} type="approve" />
<Link href={`${curPath}/${id}`}>
<div className="relative flex w-full flex-col gap-8 border-b border-gray-100 py-16 pr-24 text-14 font-500">
<div className="text-gray-500">{type}</div>
{exceptionList.includes(type) ? (
<RenderException editContent={editContent} instance={instance} type={type} />
) : (
<div className="truncate text-16 text-gray-900">{editContent[category as PostValueType]}</div>
)}
<div className="flex items-center gap-12 text-12 text-gray-400">
<div className="flex gap-4">
승인
<ReactionIcon count={count.approve} type="approve" />
</div>
<div className="flex gap-4">
거절
<ReactionIcon count={count.decline} type="reject" />
</div>
<p className="text-gray-300">{formatDate(createdAt, "yyyy.MM.dd")}</p>
</div>
<div className="flex gap-4">
거절
<ReactionIcon count={count.decline} type="reject" />
</div>
<p className="text-gray-300">{formatDate(createdAt, "yyyy.MM.dd")}</p>
</div>
<button className="absolute right-0 top-0 flex h-full items-center">
<Link href={`${curPath}/${id}`}>
<button className="absolute right-0 top-0 flex h-full items-center">
<LinkIcon />
</Link>
</button>
</div>
</button>
</div>
</Link>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import classNames from "classnames";
import Image from "next/image";
import { useParams, usePathname, useRouter } from "next/navigation";
import { useParams, usePathname } from "next/navigation";
import Chip from "@/components/chip/Chip";
import ModalPortal from "@/components/modal/ModalPortal";
import { useModal } from "@/hooks/useModal";
import { GiftType } from "@/types/index";
import { EDIT_ERR_MSG } from "@/constants/errorMsg";
import { SnsIcon } from "@/constants/snsIcon";
Expand All @@ -11,20 +13,29 @@ interface ImageEditProps {
}

export const ImageEdit = ({ imgList }: ImageEditProps) => {
const router = useRouter();
const { eventId, editId } = useParams();
const curPath = usePathname();
const isPreview = curPath === `/event/${eventId}/approve/${editId}`;
const { modal, openModal, closeModal } = useModal();

return (
<div className="flex gap-8 overflow-x-scroll scrollbar-hide">
{imgList.length > 0
? imgList.map((url: string) => (
<div key={url} className={classNames("relative h-120 w-120 flex-shrink-0", { "cursor-pointer": isPreview })} onClick={() => (isPreview ? router.push(url) : null)}>
<div key={url} className={classNames("relative h-120 w-120 flex-shrink-0", { "cursor-pointer": isPreview })} onClick={() => (isPreview ? openModal(url) : null)}>
<Image alt="수정된 행사 이미지" src={url} fill sizes="12rem, 12rem" className="object-cover" />
</div>
))
: EDIT_ERR_MSG["noInfo"]}
{modal && (
<ModalPortal>
<div onClick={closeModal} className="fixed left-0 top-0 z-popup flex h-screen w-full items-center justify-center bg-gray-900 bg-opacity-70 px-20 py-32">
<div className="relative z-popup h-full w-full">
<Image alt="크게보기" src={modal} width={0} height={0} sizes="100vw, 100vh" className="h-full w-full object-contain" />
</div>
</div>
</ModalPortal>
)}
</div>
);
};
Expand Down
31 changes: 20 additions & 11 deletions app/(route)/(header)/event/[eventId]/approve/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import LoadingDot from "@/(route)/(bottom-nav)/signin/_components/LoadingDot";
import { useQuery } from "@tanstack/react-query";
import { useParams } from "next/navigation";
import { instance } from "@/api/api";
Expand All @@ -22,18 +23,26 @@ const EditApprove = () => {
<section className="flex justify-center rounded-sm bg-gray-50 px-12 py-8 text-center text-14 text-gray-700">
<p className="max-w-300">수정사항은 사용자 3인 이상의 승인 후에 반영됩니다. 거절이 3회 누적된 수정사항은 자동으로 삭제됩니다.</p>
</section>
{isLoading && <div>로딩중</div>}
{isLoading && (
<div className="flex justify-center py-40">
<LoadingDot />
</div>
)}
{isSuccess &&
data.map(({ id, approvalCount, rejectionCount, updateCategory, updateData, createdAt }: EditApplicationType) => (
<EditCard
key={id}
id={id}
category={updateCategory}
type={LABEL_BY_CATEGORY[updateCategory] as LabelType}
editContent={JSON.parse(updateData)}
count={{ approve: Number(approvalCount), decline: Number(rejectionCount) }}
createdAt={createdAt}
/>
(data.length === 0 ? (
<div className="py-20 text-center text-16 font-500 text-gray-600">요청된 수정사항이 없어요💦</div>
) : (
data.map(({ id, approvalCount, rejectionCount, updateCategory, updateData, createdAt }: EditApplicationType) => (
<EditCard
key={id}
id={id}
category={updateCategory}
type={LABEL_BY_CATEGORY[updateCategory] as LabelType}
editContent={JSON.parse(updateData)}
count={{ approve: Number(approvalCount), decline: Number(rejectionCount) }}
createdAt={createdAt}
/>
))
))}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import MainInput from "@/(route)/post/_components/_inputs/MainInput";
import StarInput from "@/(route)/post/_components/_inputs/StarInput";
import SubInput from "@/(route)/post/_components/_inputs/SubInput";
import { PostType } from "@/(route)/post/page";
import { useEffect } from "react";
import { useFormContext } from "react-hook-form";
import BottomButton from "@/components/button/BottomButton";
import { useStore } from "@/store/index";
import { checkArrUpdate } from "@/utils/checkArrUpdate";
import { PostValueType } from "@/types/index";

const ArrCategory = ["artists", "artistNames", "tags", "eventImages"];

const EditContent = () => {
const {
watch,
formState: { defaultValues },
} = useFormContext<PostType>();
const { isCheck } = useStore((state) => ({ isCheck: state.isWarningCheck }));
const { isCheck, setCheck } = useStore((state) => ({ isCheck: state.isWarningCheck, setCheck: state.setIsWarningCheck }));
const watchedValue = watch();

const checkUpdated = () => {
Expand All @@ -26,20 +29,7 @@ const EditContent = () => {
if (postTypeGuard(defaultValues, key)) {
const prev = defaultValues[key];
const cur = watchedValue[key];
if (typeof prev === "undefined" || typeof cur === "undefined") {
return false;
}
switch (key) {
case "artists":
case "artistNames":
case "tags":
case "eventImages":
if (typeof prev === "string" || typeof cur === "string") return false;
isUpdated = checkArrUpdate(prev, cur);
break;
default:
isUpdated = prev !== cur;
}
isUpdated = ArrCategory.includes(key) ? checkArrUpdate(prev as any[], cur as any[]) : prev !== cur;
if (isUpdated) {
return true;
}
Expand All @@ -50,6 +40,10 @@ const EditContent = () => {

const isValid = checkUpdated() && isCheck;

useEffect(() => {
setCheck(false);
}, []);

return (
<div className="flex flex-col gap-20 pb-120">
<StarInput />
Expand All @@ -66,5 +60,5 @@ const EditContent = () => {
export default EditContent;

const postTypeGuard = (obj: { [a: string]: any }, key: string): key is PostValueType => {
return !!obj[key];
return typeof obj[key] !== undefined;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface Props {
onClick?: () => void;
}

const InitButton = ({ onClick }: Props) => {
return (
<button type="button" onClick={onClick} className="absolute right-0 text-12 font-600 text-blue">
초기화
</button>
);
};

export default InitButton;
18 changes: 12 additions & 6 deletions app/(route)/(header)/event/[eventId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,38 @@ import { PostType } from "@/(route)/post/page";
import { instance } from "app/_api/api";
import { format } from "date-fns";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";
import { Suspense, useEffect, useState } from "react";
import GenericFormProvider from "@/components/GenericFormProvider";
import { useAuth } from "@/hooks/useAuth";
import { useStore } from "@/store/index";
import EditContent from "./_components/EditContent";

let INITIAL_DATA: PostType;

const Edit = () => {
// const session = useAuth("/signin");
const { eventId } = useParams();
const [init, setInit] = useState(false);
const { setWriterId } = useStore((state) => ({ setWriterId: state.setWriterId }));

useEffect(() => {
const fetchData = async () => {
const data = await instance.get(`/event/${eventId}`);
const { address, addressDetail, description, endDate, startDate, eventImages, eventTags, eventUrl, eventType, organizerSns, placeName, snsType, targetArtists } = data;
const { address, addressDetail, description, endDate, startDate, eventImages, eventTags, eventUrl, eventType, organizerSns, placeName, snsType, targetArtists, userId } =
data;
const artistNames: string[] = targetArtists.map(({ artistName }: { artistName: string }) => artistName);
const artists = targetArtists.map(({ artistId }: { artistId: string }) => artistId);
const tags = eventTags.map(({ tagName }: { tagName: string }) => tagName);
const imgList = eventImages.map(({ imageUrl }: { imageUrl: string }) => imageUrl);
setWriterId(userId);

INITIAL_DATA = {
placeName,
eventType,
groupId: targetArtists[0].groupId,
groupName: targetArtists[0].groupName,
groupId: targetArtists[0].groupId || "",
groupName: targetArtists[0].groupId ? targetArtists[0].groupName : targetArtists[0].artistName,
artists,
artistNames,
artistNames: targetArtists[0].groupId ? artistNames : [],
startDate: format(startDate, "yyyy.MM.dd"),
endDate: format(endDate, "yyyy.MM.dd"),
address,
Expand All @@ -49,7 +55,7 @@ const Edit = () => {
return (
<div className="flex flex-col gap-24 p-20 text-16">
{init && (
<GenericFormProvider formOptions={{ mode: "onBlur", defaultValues: INITIAL_DATA, shouldFocusError: true }}>
<GenericFormProvider formOptions={{ mode: "onChange", defaultValues: INITIAL_DATA, shouldFocusError: true }}>
<EditContent />
</GenericFormProvider>
)}
Expand Down
4 changes: 3 additions & 1 deletion app/(route)/post/_components/DetailInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const DetailInfo = () => {
<FunnelTitle step="상세 설명" />
</div>
<DetailInput />
<BottomButton isDisabled={!isCheck || description.length > 100}>작성 완료</BottomButton>
<BottomButton isSubmit isDisabled={!isCheck || description.length > 100}>
작성 완료
</BottomButton>
</PostFrame>
);
};
Expand Down
6 changes: 4 additions & 2 deletions app/(route)/post/_components/_inputs/DetailInput.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import InitButton from "@/(route)/(header)/event/[eventId]/edit/_components/InitButton";
import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";
import WarningCheck from "@/components/WarningCheck";
Expand Down Expand Up @@ -32,10 +33,10 @@ const DetailInput = () => {
return (
<>
<section className="flex flex-col gap-8">
<div className="flex items-center gap-4">
<div className="relative flex items-center gap-4">
이미지
{validateEdit(typeof defaultValues?.eventImages !== "undefined" && checkArrUpdate(defaultValues?.eventImages, imgList)) && (
<p className="text-12 font-600 text-blue">수정됨</p>
<InitButton onClick={() => setImgList(defaultValues?.eventImages as (string | File)[])} />
)}
</div>
<MemoizedImageSection imgList={imgList} setImgList={setImgList} />
Expand All @@ -51,6 +52,7 @@ const DetailInput = () => {
rules={{ maxLength: 100 }}
hasLimit
isEdit={validateEdit(defaultValues?.description !== getValues("description"))}
onInit={() => setValue("description", defaultValues?.description || "")}
>
상세 내용
</InputArea>
Expand Down
Loading

0 comments on commit 94f47b0

Please sign in to comment.