diff --git a/src/Main/components/DetailModal.tsx b/src/Main/components/DetailModal.tsx
new file mode 100644
index 0000000..851a03d
--- /dev/null
+++ b/src/Main/components/DetailModal.tsx
@@ -0,0 +1,150 @@
+import {
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalCloseButton,
+ ModalBody,
+ ModalFooter,
+ Button,
+ Text,
+ Box,
+ Flex,
+ useBreakpointValue,
+} from "@chakra-ui/react";
+import api from "../../api/interceptor";
+
+interface DetailModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ content: {
+ id: number;
+ showId: string;
+ type: string;
+ title: string;
+ director: string;
+ cast: string;
+ country: string;
+ dateAdded: string;
+ releaseYear: string;
+ rating: string;
+ duration: string;
+ listedIn: string;
+ description: string;
+ } | null;
+}
+
+const DetailModal = ({ isOpen, onClose, content }: DetailModalProps) => {
+ if (!content) return null;
+
+ const handleLikeDislike = async (isLike: boolean) => {
+ try {
+ await api.post(`/api/like/${content.id}/${isLike}`);
+ console.log(isLike ? "좋아요 요청 성공" : "싫어요 요청 성공");
+ } catch (error) {
+ console.error("좋아요/싫어요 요청 중 오류 발생:", error);
+ }
+ };
+
+ const handleWatchAndNavigate = async () => {
+ try {
+ // 시청 기록 저장 API 호출
+ await api.post(`/api/watch/${content.id}`);
+ console.log("시청 기록 저장 요청 성공");
+ } catch (error) {
+ console.error("시청 기록 저장 요청 중 오류 발생:", error);
+ } finally {
+ // 넷플릭스로 이동
+ window.open(
+ "https://www.netflix.com/kr/",
+ "_blank",
+ "noopener,noreferrer"
+ );
+ }
+ };
+
+ // 반응형 스타일 설정
+ const textFontSize = useBreakpointValue({ base: "sm", md: "md" });
+ const headerFontSize = useBreakpointValue({ base: "lg", md: "xl" });
+ const buttonSize = useBreakpointValue({ base: "sm", md: "md" });
+ const modalWidth = useBreakpointValue({ base: "90%", md: "500px" });
+
+ return (
+
+
+
+ {content.title}
+
+
+
+
+ Type: {content.type}
+
+
+ Director: {content.director}
+
+
+ Cast: {content.cast}
+
+
+ Country: {content.country}
+
+
+ Date Added: {content.dateAdded}
+
+
+ Release Year: {content.releaseYear}
+
+
+ Rating: {content.rating}
+
+
+ Duration: {content.duration}
+
+
+ Genres: {content.listedIn}
+
+
+ Description: {content.description}
+
+
+
+
+ {/* 좋아요/싫어요 버튼 */}
+
+
+
+
+ {/* 넷플릭스로 이동 버튼 */}
+
+
+
+
+ );
+};
+
+export default DetailModal;
diff --git a/src/Main/components/RandomContents.tsx b/src/Main/components/RandomContents.tsx
index b2f2bc2..4832d43 100644
--- a/src/Main/components/RandomContents.tsx
+++ b/src/Main/components/RandomContents.tsx
@@ -1,14 +1,36 @@
import { useEffect, useState } from "react";
-import { Box, Grid, Text, Spinner, useBreakpointValue } from "@chakra-ui/react";
+import {
+ Box,
+ Grid,
+ Text,
+ Spinner,
+ useBreakpointValue,
+ useDisclosure,
+} from "@chakra-ui/react";
import api from "../../api/interceptor";
+import DetailModal from "./DetailModal";
interface Content {
+ id: number;
+ showId: string;
+ type: string;
title: string;
+ director: string;
+ cast: string;
+ country: string;
+ dateAdded: string;
+ releaseYear: string;
+ rating: string;
+ duration: string;
+ listedIn: string;
+ description: string;
}
function RandomContents(): JSX.Element {
const [randomContents, setRandomContents] = useState([]);
const [isLoading, setIsLoading] = useState(true);
+ const [selectedContent, setSelectedContent] = useState(null);
+ const { isOpen, onOpen, onClose } = useDisclosure();
useEffect(() => {
const fetchRandomContents = async () => {
@@ -29,6 +51,11 @@ function RandomContents(): JSX.Element {
fetchRandomContents();
}, []);
+ const handleCardClick = (content: Content) => {
+ setSelectedContent(content);
+ onOpen();
+ };
+
const cardColumns = useBreakpointValue({ base: "1fr", md: "repeat(5, 1fr)" });
return (
@@ -43,33 +70,50 @@ function RandomContents(): JSX.Element {
) : (
-
- {randomContents.map((content, index) => (
-
-
+
+ {randomContents.map((content) => (
+ handleCardClick(content)}
>
- {content.title}
-
-
- ))}
-
+
+ {content.title}
+
+
+ ))}
+
+
+ >
)}
);
diff --git a/src/Main/components/RecommendedContents.tsx b/src/Main/components/RecommendedContents.tsx
index 10fa14e..4056f57 100644
--- a/src/Main/components/RecommendedContents.tsx
+++ b/src/Main/components/RecommendedContents.tsx
@@ -1,5 +1,186 @@
+import { useEffect, useState } from "react";
+import { Box, Grid, Text, Spinner, Flex } from "@chakra-ui/react";
+import api from "../../api/interceptor";
+import DetailModal from "./DetailModal"; // DetailModal 컴포넌트를 import
+
+interface Content {
+ id: number;
+ showId: string;
+ type: string;
+ title: string;
+ director: string;
+ cast: string;
+ country: string;
+ dateAdded: string;
+ releaseYear: string;
+ rating: string;
+ duration: string;
+ listedIn: string;
+ description: string;
+}
+
+interface CategoryContent {
+ category: string;
+ contents: Content[];
+}
+
function RecommendedContents(): JSX.Element {
- return 추천 콘텐츠 컴포넌트 내용
;
+ const [categories, setCategories] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [selectedContent, setSelectedContent] = useState(null); // 선택된 콘텐츠 상태
+ const [isModalOpen, setIsModalOpen] = useState(false); // 모달 상태
+
+ const categoryLabels: { [key: string]: string } = {
+ similarCastFromLikes: "🎭 좋아요한 콘텐츠와 출연진 유사",
+ similarGenreFromLikes: "🎬 좋아요한 콘텐츠와 장르 유사",
+ sameDirectorFromLikes: "🎥 좋아요한 콘텐츠와 감독 동일",
+ similarCastFromWatchHistory: "🌟 시청한 콘텐츠와 출연진 유사",
+ similarGenreFromWatchHistory: "📽️ 시청한 콘텐츠와 장르 유사",
+ sameDirectorFromWatchHistory: "🎞️ 시청한 콘텐츠와 감독 동일",
+ };
+
+ useEffect(() => {
+ const fetchRecommendedContents = async () => {
+ setIsLoading(true);
+ try {
+ const response = await api.get("/api/recommend/10");
+ const fetchedCategories = Object.keys(categoryLabels).map(
+ (category) => ({
+ category,
+ contents:
+ response.data[category]?.map(
+ (item: { content: Content }) => item.content
+ ) || [],
+ })
+ );
+ setCategories(fetchedCategories);
+ } catch (error) {
+ console.error("추천 콘텐츠를 가져오는 중 오류 발생:", error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchRecommendedContents();
+ }, []);
+
+ const handleCardClick = (content: Content) => {
+ setSelectedContent(content); // 선택된 콘텐츠 설정
+ setIsModalOpen(true); // 모달 열기
+ };
+
+ const closeModal = () => {
+ setSelectedContent(null); // 선택된 콘텐츠 초기화
+ setIsModalOpen(false); // 모달 닫기
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {categories.map(({ category, contents }) => (
+
+ {/* 카테고리 제목 */}
+
+ {categoryLabels[category]}
+
+ {contents.length > 0 ? (
+ // 콘텐츠 리스트
+
+
+ {contents.map((content) => (
+ handleCardClick(content)} // 카드 클릭 핸들러
+ transition="transform 0.2s, box-shadow 0.2s"
+ _hover={{
+ transform: "scale(1.05)",
+ boxShadow: "lg",
+ borderColor: "teal.500",
+ }}
+ >
+
+ {content.title}
+
+
+ ))}
+
+
+ ) : (
+ // 데이터가 없는 경우 안내 메시지
+
+ 충분한 데이터가 쌓이지 않았습니다.
+
+ )}
+
+ ))}
+
+ {/* DetailModal */}
+ {selectedContent && (
+
+ )}
+
+ );
}
export default RecommendedContents;