From f0dd8f20718a65118ecf6daa6fe9fef66c786a21 Mon Sep 17 00:00:00 2001 From: choi Date: Sun, 22 Dec 2024 17:34:51 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=A0=EA=B9=90=EB=A7=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/album/[albumId]/page.tsx | 95 ++++--- src/components/container/square-container.tsx | 2 +- .../modal/album/albumEdit-modal.tsx | 232 ++++++++++++++++++ .../modal/album/information-modal.tsx | 47 ++++ src/components/modal/modal-provider.tsx | 6 + src/hooks/modal/use-albumEdit-modal.ts | 15 ++ src/hooks/modal/use-information-modal.ts | 17 ++ src/services/albumService.ts | 20 ++ 8 files changed, 397 insertions(+), 37 deletions(-) create mode 100644 src/components/modal/album/albumEdit-modal.tsx create mode 100644 src/components/modal/album/information-modal.tsx create mode 100644 src/hooks/modal/use-albumEdit-modal.ts create mode 100644 src/hooks/modal/use-information-modal.ts diff --git a/src/app/album/[albumId]/page.tsx b/src/app/album/[albumId]/page.tsx index b9b0fe4..3160653 100644 --- a/src/app/album/[albumId]/page.tsx +++ b/src/app/album/[albumId]/page.tsx @@ -19,39 +19,52 @@ import { } from "@/components/ui/table"; import Image from "next/image"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { useMediaQuery } from "react-responsive"; import useStreamingBar from "@/hooks/modal/use-streaming-bar"; import { cn } from "@/lib/utils"; +import { useEffect, useState } from "react"; +import { Album, Profile, Track, getAlbumById } from "@/services/albumService"; +import { toast } from "sonner"; +import useInformationModal from "@/hooks/modal/use-information-modal"; +import useAlbumEditModal from "@/hooks/modal/use-albumEdit-modal"; export default function AlbumPage() { + const [albumData, setAlbumData] = useState(); + const [albumTrack, setAlbumTrack] = useState([]); + const [albumProfile, setAlbumProfile] = useState(); + const router = useRouter(); const streamingBar = useStreamingBar(); + const informationModal = useInformationModal(); + const albumEditModal = useAlbumEditModal(); + const isMobile = useMediaQuery({ maxWidth: 768 }); + const pathname = usePathname(); + const uuid = String(pathname.split("/").pop()); + + useEffect(() => { + const getAlbum = async () => { + try { + const data = await getAlbumById(uuid); + setAlbumData(data.albumResponseDto); + setAlbumTrack(data.trackResponseDtos); + setAlbumProfile(data.profileResponseDto); + } catch (error) { + console.error("앨범 로딩 실패:", error); + } + }; - const dummy = [ - { - id: 1, - title: "피곤해", - duration: "3:02", - }, - { - id: 2, - title: "피곤해", - duration: "3:02", - }, - { - id: 3, - title: "피곤해", - duration: "3:02", - }, - { - id: 4, - title: "피곤해", - duration: "3:02", - }, - ]; + getAlbum(); + }, []); + + const handleShareClick = () => { + navigator.clipboard + .writeText(window.location.href) + .then(() => toast.success("링크가 복사되었습니다!")) + .catch(() => toast.error("복사 실패!")); + }; return (
albumCover
router.push("/user/123")} + onClick={() => router.push(`/profile/${albumProfile?.name}`)} className="flex gap-x-2 items-center" > - + U - RARO YANG + + {albumProfile?.name} +
- THIRSTY + {albumData?.title}
@@ -97,9 +115,12 @@ export default function AlbumPage() { 13.1k
- - - + + informationModal.onOpen(albumData)} + className="size-6 cursor-pointer" + /> +
@@ -120,13 +141,15 @@ export default function AlbumPage() { - {dummy.map((song) => ( + {albumTrack.map((song) => ( -
{song.id}
+
+ {song.uuid} +
streamingBar.onOpen()} className="hidden group-hover:flex text-[#FF239C]" @@ -136,7 +159,7 @@ export default function AlbumPage() { {song.title} - {song.duration} + {song.title} diff --git a/src/components/container/square-container.tsx b/src/components/container/square-container.tsx index 55462cd..6e5cbce 100644 --- a/src/components/container/square-container.tsx +++ b/src/components/container/square-container.tsx @@ -25,7 +25,7 @@ const SquareContainer = ({ return (
-
+
profile { + const [file, setFile] = useState(null); + const [isLoading, setIsloading] = useState(false); + const albumEditModal = useAlbumEditModal(); + const router = useRouter(); + + const pathname = usePathname(); + const uuid = String(pathname.split("/").pop()); + + const onChange = (open: boolean) => { + if (!open) { + reset(); + albumEditModal.onClose(); + } + }; + + const handleFileUpload = (event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files.length > 0) { + const file = files[0]; + if (!file.type.startsWith("image/")) { + toast.error("이미지 파일만 업로드 가능합니다."); + return; + } + + setFile(file); + } + }; + + const uploadToS3 = async (file: File) => { + const formData = new FormData(); + formData.append("file", file); + + const response = await api.post(`/upload/images`, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + if (response.status === 201) { + return response.data; + } else { + throw new Error("이미지 업로드에 실패했습니다."); + } + }; + + const FormSchema = z.object({ + title: z + .string() + .min(1, "1자 이상 입력하세요") + .max(20, "20자 이하로 입력하세요"), + description: z + .string() + .max(100, "소개는 100자 이내로 작성하세요") + .nullable() + .transform((val) => val ?? ""), + albumArt: z.string().nullable().optional(), + }); + + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: { + title: "", + description: "", + albumArt: null, + }, + }); + + const onSubmit: SubmitHandler = async (values) => { + if (!uuid) { + toast.error("앨범 정보를 찾을 수 없습니다."); + return; + } + + setIsloading(true); + + try { + const albumArtUrl = file ? (await uploadToS3(file)).url : ""; + + const response = await api.patch(`/album/${uuid}`, { + title: values.title, + description: values.description, + albumArt: albumArtUrl, + }); + + if (isSuccessResponse(response)) { + handleSuccess(); + } else { + throw new Error("앨범 수정에 실패했습니다."); + } + } catch (error) { + handleError(error); + } finally { + setIsloading(false); + } + }; + + const handleSuccess = () => { + toast.success("앨범이 수정되었습니다."); + reset(); + albumEditModal.onClose(); + router.refresh(); + }; + + const handleError = (error: unknown) => { + if (axios.isAxiosError(error)) { + console.error("서버 응답:", error.response); + toast.error( + error.response?.data?.message || + error.response?.data?.detail || + "앨범 수정 중 오류가 발생했습니다." + ); + } else { + console.error("에러 상세:", error); + toast.error("앨범 수정 과정에서 오류가 발생했습니다."); + } + }; + + const isSuccessResponse = (response: AxiosResponse) => { + return response.status >= 200 && response.status < 300; + }; + + return ( + } + title="앨범 편집" + /> + } + description="앨범을 소개해주세요" + isOpen={albumEditModal.isOpen} + onChange={onChange} + className="p-4 flex flex-col items-center justify-center" + > +
+
+ + + +

+ {errors.title ? String(errors.title.message) : null} +

+