diff --git a/client/package-lock.json b/client/package-lock.json
index d70fd67..f5eaa10 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -9,20 +9,24 @@
"version": "0.0.0",
"dependencies": {
"@reduxjs/toolkit": "^1.9.5",
+ "@types/pica": "^9.0.1",
"@types/react-redux": "^7.1.25",
- "@types/react-router-dom": "^5.3.3",
"axios": "^1.4.0",
+ "pica": "^9.0.1",
"react": "^18.2.0",
+ "react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-icons": "^4.10.1",
"react-rating-stars-component": "^2.2.0",
"react-redux": "^8.1.1",
- "react-router-dom": "^6.14.1"
+ "react-router-dom": "^6.14.1",
+ "react-spinners": "^0.13.8"
},
"devDependencies": {
"@types/node": "^20.4.0",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
+ "@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"@vitejs/plugin-react": "^4.0.0",
@@ -1013,10 +1017,16 @@
"node": ">=14"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
+ "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
+ },
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
- "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "dev": true
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
@@ -1045,6 +1055,11 @@
"integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==",
"dev": true
},
+ "node_modules/@types/pica": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@types/pica/-/pica-9.0.1.tgz",
+ "integrity": "sha512-hTsYxcy0MqIOKzeALuh3zOHyozBlndxV/bX9X52GBFq2XUQchZF6T0vcRYeT5P1ggmswi2LlIwHAH+bKWxxalg=="
+ },
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -1084,6 +1099,7 @@
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
+ "dev": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
@@ -1093,6 +1109,7 @@
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
+ "dev": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
@@ -3065,6 +3082,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/glur": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz",
+ "integrity": "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA=="
+ },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -3734,6 +3756,15 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/multimath": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/multimath/-/multimath-2.0.0.tgz",
+ "integrity": "sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==",
+ "dependencies": {
+ "glur": "^1.1.2",
+ "object-assign": "^4.1.1"
+ }
+ },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -3803,7 +3834,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4024,6 +4054,17 @@
"node": ">=8"
}
},
+ "node_modules/pica": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/pica/-/pica-9.0.1.tgz",
+ "integrity": "sha512-v0U4vY6Z3ztz9b4jBIhCD3WYoecGXCQeCsYep+sXRefViL+mVVoTL+wqzdPeE+GpBFsRUtQZb6dltvAt2UkMtQ==",
+ "dependencies": {
+ "glur": "^1.1.2",
+ "multimath": "^2.0.0",
+ "object-assign": "^4.1.1",
+ "webworkify": "^1.5.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -4357,6 +4398,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-cookie": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz",
+ "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.0.1",
+ "hoist-non-react-statics": "^3.0.0",
+ "universal-cookie": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0"
+ }
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -4469,6 +4523,15 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-spinners": {
+ "version": "0.13.8",
+ "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz",
+ "integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==",
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -5097,6 +5160,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/universal-cookie": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
+ "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
+ "dependencies": {
+ "@types/cookie": "^0.3.3",
+ "cookie": "^0.4.0"
+ }
+ },
+ "node_modules/universal-cookie/node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
@@ -5198,6 +5278,11 @@
}
}
},
+ "node_modules/webworkify": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz",
+ "integrity": "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g=="
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/client/package.json b/client/package.json
index 09f71f6..5616093 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,20 +13,24 @@
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.5",
+ "@types/pica": "^9.0.1",
"@types/react-redux": "^7.1.25",
- "@types/react-router-dom": "^5.3.3",
"axios": "^1.4.0",
+ "pica": "^9.0.1",
"react": "^18.2.0",
+ "react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-icons": "^4.10.1",
"react-rating-stars-component": "^2.2.0",
"react-redux": "^8.1.1",
- "react-router-dom": "^6.14.1"
+ "react-router-dom": "^6.14.1",
+ "react-spinners": "^0.13.8"
},
"devDependencies": {
"@types/node": "^20.4.0",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
+ "@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"@vitejs/plugin-react": "^4.0.0",
diff --git a/client/public/img/errorImg.png b/client/public/img/errorImg.png
new file mode 100644
index 0000000..cf3bdbb
Binary files /dev/null and b/client/public/img/errorImg.png differ
diff --git a/client/public/mockupdata/highscoredata.json b/client/public/mockupdata/highscoredata.json
new file mode 100644
index 0000000..351ff9c
--- /dev/null
+++ b/client/public/mockupdata/highscoredata.json
@@ -0,0 +1,39 @@
+{
+ "data": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails.json b/client/public/mockupdata/moviedetails.json
deleted file mode 100644
index 5c8476b..0000000
--- a/client/public/mockupdata/moviedetails.json
+++ /dev/null
@@ -1,100 +0,0 @@
-{
- "movie": {
- "title": "센과 치히로의 행방불명",
- "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
- "genre": "애니메이션/판타지/가족",
- "running_time": "2시간 6분",
- "poster_url": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
- "score": 4.3,
- "review_count": 6,
- "스태프": [
- {
- "director": "미야자키 하야오"
- }
- ],
- "배우": [
- {
- "actor": "히이라기 루미"
- },
- {
- "actor": "이리노 미유"
- }
- ]
- },
- "review": {
- "rev": [
- {
- "score": 5,
- "username": "dongbin420",
- "content": "아름다운 영화입니다..",
- "tags": [
- "#감동",
- "#가족"
- ],
- "like": 20
- },
- {
- "score": 4.5,
- "username": "치히로",
- "content": "재밌네요",
- "tags": [
- "#힐링", "#킬링타임"
- ],
- "like": 17
- },
- {
- "score": 4,
- "username": "센",
- "content": "또 보고 싶어요",
- "tags": [
- "tag1",
- "tag2"
- ],
- "like": 12
- },
- {
- "score": 3.5,
- "username": "하쿠",
- "content": "굿",
- "tags": [
- "tag1", "tag2"
- ],
- "like": 10
- },
- {
- "score": 4,
- "username": "마크주커버그",
- "content": "짱",
- "tags": [
- "tag1", "tag2"
- ],
- "like": 5
- },
- {
- "score": 2.5,
- "username": "미야자키 하야오",
- "content": "오",
- "tags": [
- "tag1", "tag2"
- ],
- "like": 1
- }
- ]
- },
- "recommend": [
- {
- "id": 1,
- "title": "영화 제목",
- "release_date": "개봉일",
- "score": 4,
- "poster_url": "영화 포스터 URL"
- },
- {
- "id": 2,
- "title": "영화 제목",
- "release_date": "개봉일",
- "score": 4,
- "poster_url": "영화 포스터 URL"
- }
- ]
-}
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails1.json b/client/public/mockupdata/moviedetails1.json
new file mode 100644
index 0000000..7f444d8
--- /dev/null
+++ b/client/public/mockupdata/moviedetails1.json
@@ -0,0 +1,156 @@
+{
+ "movie": {
+ "docId": "K28660",
+ "title": "센과 치히로의 행방불명",
+ "titleEng": "Spirited Away",
+ "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
+ "genre": "애니메이션/판타지/가족",
+ "runtime": "126",
+ "repRlsDate": "2001",
+ "nation": "일본",
+ "rating": "전체",
+ "posterUrl": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
+ "score": 4.3,
+ "review_count": 30,
+ "directorNm": "미야자키 하야오",
+ "actors": [
+ { "actor": "히이라기 루미", "role": "센" },
+ { "actor": "이리노 미유", "role": "하쿠" }
+ ],
+ "stills": [
+ "https://an2-img.amz.wtchn.net/image/v2/SwBb771uZIlAVhyMmtX8jg.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZZV0oxZW1SNFptVjVaRzVoY3pCaGJHTm9NWFFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuNzd6S1o0aHJDYkNwM3FHSWFGc2pIMi1TcElzWDI2aDg1ZllMWTdZYzQxdw",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_12%2F1324523436632RMEX7_JPEG%2Fmovie_image.jpg",
+ "https://an2-img.amz.wtchn.net/image/v2/-6PlfJLDCAx2Ke2t4dH2tw.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZjVE5sY25CMWQyZHZhV2RpWkc0ME5IRXdaREFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuMDZNR012UThvUDJCYjIzcC05R0hFT2xCMnRFMUxmbjhuODZfdzdEalNoTQ",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_227%2F13245234480448qNfK_JPEG%2Fmovie_image.jpg"
+ ],
+ "reviews": [
+ {
+ "docId": "K28660",
+ "reviewId": 1,
+ "score": 5,
+ "content": "아름다운 영화입니다..",
+ "likes": 20,
+ "tags": [
+ "감동",
+ "모험"
+ ],
+ "user":
+ {
+ "userId": 1,
+ "username": "dongbin420",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 2,
+ "score": 4.5,
+ "content": "재밌네요",
+ "like": 10,
+ "tags": [
+ "힐링",
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 2,
+ "username": "spongebob",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 3,
+ "score": 3,
+ "content": "그저그래요",
+ "like": 6,
+ "tags": [
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 3,
+ "username": "spiderman",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 4,
+ "score": 3.5,
+ "content": "재밌음",
+ "like": 4,
+ "tags": [
+ "킬링타임",
+ "음악"
+ ],
+ "user":
+ {
+ "userId": 4,
+ "username": "센",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 5,
+ "score": 5,
+ "content": "짱",
+ "like": 3,
+ "tags": [
+ "감동",
+ "창의적"
+ ],
+ "user":
+ {
+ "userId": 5,
+ "username": "마크주커버그",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ }
+ ]
+ },
+ "pageInfo": {
+ "page": 1,
+ "size": 5,
+ "totalElements": 2,
+ "totalPages": 1
+ },
+ "recommended_movies": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails2.json b/client/public/mockupdata/moviedetails2.json
new file mode 100644
index 0000000..729ce5c
--- /dev/null
+++ b/client/public/mockupdata/moviedetails2.json
@@ -0,0 +1,162 @@
+{
+ "movie": {
+ "docId": "K28660",
+ "title": "센과 치히로의 행방불명",
+ "titleEng": "Spirited Away",
+ "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
+ "genre": "애니메이션/판타지/가족",
+ "runtime": "126",
+ "repRlsDate": "2001",
+ "nation": "일본",
+ "rating": "전체",
+ "posterUrl": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
+ "score": 4.3,
+ "review_count": 30,
+ "directorNm": "미야자키 하야오",
+ "actors": [
+ { "actor": "히이라기 루미", "role": "센" },
+ { "actor": "이리노 미유", "role": "하쿠" }
+ ],
+ "stills": [
+ "https://an2-img.amz.wtchn.net/image/v2/SwBb771uZIlAVhyMmtX8jg.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZZV0oxZW1SNFptVjVaRzVoY3pCaGJHTm9NWFFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuNzd6S1o0aHJDYkNwM3FHSWFGc2pIMi1TcElzWDI2aDg1ZllMWTdZYzQxdw",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_12%2F1324523436632RMEX7_JPEG%2Fmovie_image.jpg",
+ "https://an2-img.amz.wtchn.net/image/v2/-6PlfJLDCAx2Ke2t4dH2tw.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZjVE5sY25CMWQyZHZhV2RpWkc0ME5IRXdaREFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuMDZNR012UThvUDJCYjIzcC05R0hFT2xCMnRFMUxmbjhuODZfdzdEalNoTQ",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_227%2F13245234480448qNfK_JPEG%2Fmovie_image.jpg"
+
+ ],
+ "reviews": [
+ {
+ "docId": "K28660",
+ "reviewId": 6,
+ "score": 2,
+ "content": "재미없어요..",
+ "like": 1,
+ "tags": [],
+ "user":
+ {
+ "userId": 6,
+ "username": "징징이",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 7,
+ "score": 4.5,
+ "content": "역대급..",
+ "like": 6,
+ "tags": [
+ "힐링",
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 7,
+ "username": "짱구",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 8,
+ "score": 2,
+ "content": "보다 잤음;",
+ "like": 3,
+ "tags": [
+ "힐링"
+ ],
+ "user":
+ {
+ "userId": 8,
+ "username": "ironman",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 9,
+ "score": 5,
+ "content": "6점주고싶음 인생영화",
+ "like": 4,
+ "tags": [
+ "킬링타임",
+ "음악",
+ "힐링",
+ "모험",
+ "영상미",
+ "창의적",
+ "영감",
+ "긴장감",
+ "반전",
+ "감동"
+ ],
+ "user":
+ {
+ "userId": 9,
+ "username": "미야자키 하야오",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 10,
+ "score": 5,
+ "content": "짱",
+ "like": 3,
+ "tags": [
+ "감동",
+ "영상미"
+ ],
+ "user":
+ {
+ "userId": 10,
+ "username": "일론머스크",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ }
+ ]
+ },
+ "pageInfo": {
+ "page": 1,
+ "size": 5,
+ "totalElements": 2,
+ "totalPages": 1
+ },
+ "recommend": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails3.json b/client/public/mockupdata/moviedetails3.json
new file mode 100644
index 0000000..d47464f
--- /dev/null
+++ b/client/public/mockupdata/moviedetails3.json
@@ -0,0 +1,158 @@
+{
+ "movie": {
+ "docId": "K28660",
+ "title": "센과 치히로의 행방불명",
+ "titleEng": "Spirited Away",
+ "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
+ "genre": "애니메이션/판타지/가족",
+ "runtime": "126",
+ "repRlsDate": "2001",
+ "nation": "일본",
+ "rating": "전체",
+ "posterUrl": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
+ "score": 4.3,
+ "review_count": 30,
+ "directorNm": "미야자키 하야오",
+ "actors": [
+ { "actor": "히이라기 루미", "role": "센" },
+ { "actor": "이리노 미유", "role": "하쿠" }
+ ],
+ "stills": [
+ "https://an2-img.amz.wtchn.net/image/v2/SwBb771uZIlAVhyMmtX8jg.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZZV0oxZW1SNFptVjVaRzVoY3pCaGJHTm9NWFFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuNzd6S1o0aHJDYkNwM3FHSWFGc2pIMi1TcElzWDI2aDg1ZllMWTdZYzQxdw",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_12%2F1324523436632RMEX7_JPEG%2Fmovie_image.jpg",
+ "https://an2-img.amz.wtchn.net/image/v2/-6PlfJLDCAx2Ke2t4dH2tw.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZjVE5sY25CMWQyZHZhV2RpWkc0ME5IRXdaREFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuMDZNR012UThvUDJCYjIzcC05R0hFT2xCMnRFMUxmbjhuODZfdzdEalNoTQ",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_227%2F13245234480448qNfK_JPEG%2Fmovie_image.jpg"
+ ],
+ "reviews": [
+ {
+ "docId": "K28660",
+ "reviewId": 11,
+ "score": 2,
+ "content": "별로에요",
+ "like": 1,
+ "tags": [],
+ "user":
+ {
+ "userId": 6,
+ "username": "징징이",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 12,
+ "score": 4.5,
+ "content": "그닥....",
+ "like": 6,
+ "tags": [
+ "힐링",
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 7,
+ "username": "짱구",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 13,
+ "score": 2,
+ "content": "너무 재밌어요",
+ "like": 3,
+ "tags": [
+ "힐링"
+ ],
+ "user":
+ {
+ "userId": 8,
+ "username": "ironman",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 14,
+ "score": 5,
+ "content": "와우",
+ "like": 4,
+ "tags": [
+ "킬링타임",
+ "힐링",
+ "모험",
+ "영감",
+ "긴장감",
+ "반전",
+ "감동"
+ ],
+ "user":
+ {
+ "userId": 9,
+ "username": "미야자키 하야오",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 15,
+ "score": 5,
+ "content": "시간아까움",
+ "like": 3,
+ "tags": [
+ "감동",
+ "창의적"
+ ],
+ "user":
+ {
+ "userId": 10,
+ "username": "일론머스크",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ }
+ ]
+ },
+ "pageInfo": {
+ "page": 1,
+ "size": 5,
+ "totalElements": 2,
+ "totalPages": 1
+ },
+ "recommend": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails4.json b/client/public/mockupdata/moviedetails4.json
new file mode 100644
index 0000000..4ba76af
--- /dev/null
+++ b/client/public/mockupdata/moviedetails4.json
@@ -0,0 +1,160 @@
+{
+ "movie": {
+ "docId": "K28660",
+ "title": "센과 치히로의 행방불명",
+ "titleEng": "Spirited Away",
+ "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
+ "genre": "애니메이션/판타지/가족",
+ "runtime": "126",
+ "repRlsDate": "2001",
+ "nation": "일본",
+ "rating": "전체",
+ "posterUrl": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
+ "score": 4.3,
+ "review_count": 30,
+ "directorNm": "미야자키 하야오",
+ "actors": [
+ { "actor": "히이라기 루미", "role": "센" },
+ { "actor": "이리노 미유", "role": "하쿠" }
+ ],
+ "stills": [
+ "https://an2-img.amz.wtchn.net/image/v2/SwBb771uZIlAVhyMmtX8jg.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZZV0oxZW1SNFptVjVaRzVoY3pCaGJHTm9NWFFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuNzd6S1o0aHJDYkNwM3FHSWFGc2pIMi1TcElzWDI2aDg1ZllMWTdZYzQxdw",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_12%2F1324523436632RMEX7_JPEG%2Fmovie_image.jpg",
+ "https://an2-img.amz.wtchn.net/image/v2/-6PlfJLDCAx2Ke2t4dH2tw.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZjVE5sY25CMWQyZHZhV2RpWkc0ME5IRXdaREFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuMDZNR012UThvUDJCYjIzcC05R0hFT2xCMnRFMUxmbjhuODZfdzdEalNoTQ",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_227%2F13245234480448qNfK_JPEG%2Fmovie_image.jpg"
+ ],
+ "reviews": [
+ {
+ "docId": "K28660",
+ "reviewId": 16,
+ "score": 2,
+ "content": "역시 지브리",
+ "like": 1,
+ "tags": [],
+ "user":
+ {
+ "userId": 6,
+ "username": "징징이",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 17,
+ "score": 4.5,
+ "content": "보는 내내 울었음",
+ "like": 6,
+ "tags": [
+ "힐링",
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 7,
+ "username": "짱구",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 18,
+ "score": 2,
+ "content": "아이언맨보다 재밌음",
+ "like": 3,
+ "tags": [
+ "힐링"
+ ],
+ "user":
+ {
+ "userId": 8,
+ "username": "ironman",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 19,
+ "score": 5,
+ "content": "내가만든거지만 명작",
+ "like": 4,
+ "tags": [
+ "킬링타임",
+ "힐링",
+ "모험",
+ "영감",
+ "긴장감",
+ "반전",
+ "감동"
+ ],
+ "user":
+ {
+ "userId": 9,
+ "username": "미야자키 하야오",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 20,
+ "score": 5,
+ "content": "돈아까움",
+ "like": 3,
+ "tags": [
+ "감동"
+ ],
+ "user": {
+ "userId": 10,
+ "username": "일론머스크",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ }
+ ]
+ },
+ "pageInfo": {
+ "page": 1,
+ "size": 5,
+ "totalElements": 2,
+ "totalPages": 1
+ },
+ "recommend": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails5.json b/client/public/mockupdata/moviedetails5.json
new file mode 100644
index 0000000..3c488a6
--- /dev/null
+++ b/client/public/mockupdata/moviedetails5.json
@@ -0,0 +1,163 @@
+{
+ "movie": {
+ "docId": "K28660",
+ "title": "센과 치히로의 행방불명",
+ "titleEng": "Spirited Away",
+ "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
+ "genre": "애니메이션/판타지/가족",
+ "runtime": "126",
+ "repRlsDate": "2001",
+ "nation": "일본",
+ "rating": "전체",
+ "posterUrl": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
+ "score": 4.3,
+ "review_count": 30,
+ "directorNm": "미야자키 하야오",
+ "actors": [
+ { "actor": "히이라기 루미", "role": "센" },
+ { "actor": "이리노 미유", "role": "하쿠" }
+ ],
+ "stills": [
+ "https://an2-img.amz.wtchn.net/image/v2/SwBb771uZIlAVhyMmtX8jg.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZZV0oxZW1SNFptVjVaRzVoY3pCaGJHTm9NWFFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuNzd6S1o0aHJDYkNwM3FHSWFGc2pIMi1TcElzWDI2aDg1ZllMWTdZYzQxdw",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_12%2F1324523436632RMEX7_JPEG%2Fmovie_image.jpg",
+ "https://an2-img.amz.wtchn.net/image/v2/-6PlfJLDCAx2Ke2t4dH2tw.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZjVE5sY25CMWQyZHZhV2RpWkc0ME5IRXdaREFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuMDZNR012UThvUDJCYjIzcC05R0hFT2xCMnRFMUxmbjhuODZfdzdEalNoTQ",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_227%2F13245234480448qNfK_JPEG%2Fmovie_image.jpg"
+ ],
+ "reviews": [
+ {
+ "docId": "K28660",
+ "reviewId": 21,
+ "score": 2,
+ "content": "재미없어요..",
+ "like": 1,
+ "tags": [],
+ "user":
+ {
+ "userId": 6,
+ "username": "징징이",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 22,
+ "score": 4.5,
+ "content": "역대급..",
+ "like": 6,
+ "tags": [
+ "힐링",
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 7,
+ "username": "짱구",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 23,
+ "score": 2,
+ "content": "보다 잤음;",
+ "like": 3,
+ "tags": [
+ "힐링"
+ ],
+ "user":
+ {
+ "userId": 8,
+ "username": "ironman",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 24,
+ "score": 5,
+ "content": "6점주고싶음 인생영화",
+ "like": 4,
+ "tags": [
+ "킬링타임",
+ "영상미",
+ "힐링",
+ "모험",
+ "음악",
+ "창의적",
+ "영감",
+ "긴장감",
+ "반전",
+ "감동"
+ ],
+ "user":
+ {
+ "userId": 9,
+ "username": "미야자키 하야오",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": "K28660",
+ "reviewId": 25,
+ "score": 5,
+ "content": "짱",
+ "like": 3,
+ "tags": [
+ "감동"
+ ],
+ "user":
+ {
+ "userId": 10,
+ "username": "일론머스크",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ }
+ ]
+ },
+ "pageInfo": {
+ "page": 1,
+ "size": 5,
+ "totalElements": 2,
+ "totalPages": 1
+ },
+ "recommend": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/client/public/mockupdata/moviedetails6.json b/client/public/mockupdata/moviedetails6.json
new file mode 100644
index 0000000..75f5c7e
--- /dev/null
+++ b/client/public/mockupdata/moviedetails6.json
@@ -0,0 +1,159 @@
+{
+ "movie": {
+ "docId": "K28660",
+ "title": "센과 치히로의 행방불명",
+ "titleEng": "Spirited Away",
+ "description": "평범한 열 살 짜리 소녀 치히로 식구는 이사 가던 중 길을 잘못 들어 낡은 터널을 지나가게 된다. 터널 저편엔 폐허가 된 놀이공원이 있었고 그곳엔 이상한 기운이 흘렀다. 인기척 하나 없는 이 마을의 낯선 분위기에 불길한 기운을 느낀 치히로는 부모님에게 돌아가자고 조르지만 부모님은 호기심에 들떠 마을 곳곳을 돌아다니기 시작한다. 어느 음식점에 도착한 치히로의 부모님은 그 곳에 차려진 음식들을 보고 즐거워하며 허겁지겁 먹어대다가 돼지로 변해버린다. 겁에 질려 당황하는 치히로에게 낯선 소년 하쿠가 나타나 빨리 이곳을 나가라고 소리치는데...",
+ "genre": "애니메이션/판타지/가족",
+ "runtime": "126",
+ "repRlsDate": "2001",
+ "nation": "일본",
+ "rating": "전체",
+ "posterUrl": "https://upload.wikimedia.org/wikipedia/ko/b/bc/%EC%84%BC%EA%B3%BC_%EC%B9%98%ED%9E%88%EB%A1%9C%EC%9D%98_%ED%96%89%EB%B0%A9%EB%B6%88%EB%AA%85_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg",
+ "score": 4.3,
+ "review_count": 30,
+ "directorNm": "미야자키 하야오",
+ "actors": [
+ { "actor": "히이라기 루미", "role": "센" },
+ { "actor": "이리노 미유", "role": "하쿠" }
+ ],
+ "stills": [
+ "https://an2-img.amz.wtchn.net/image/v2/SwBb771uZIlAVhyMmtX8jg.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZZV0oxZW1SNFptVjVaRzVoY3pCaGJHTm9NWFFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuNzd6S1o0aHJDYkNwM3FHSWFGc2pIMi1TcElzWDI2aDg1ZllMWTdZYzQxdw",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_12%2F1324523436632RMEX7_JPEG%2Fmovie_image.jpg",
+ "https://an2-img.amz.wtchn.net/image/v2/-6PlfJLDCAx2Ke2t4dH2tw.jpg?jwt=ZXlKaGJHY2lPaUpJVXpJMU5pSjkuZXlKdmNIUnpJanBiSW1KbklsMHNJbkFpT2lJdmRqRXZjVE5sY25CMWQyZHZhV2RpWkc0ME5IRXdaREFpTENKeElqbzRNQ3dpZHlJNk1Ua3lNSDAuMDZNR012UThvUDJCYjIzcC05R0hFT2xCMnRFMUxmbjhuODZfdzdEalNoTQ",
+ "https://search.pstatic.net/common?quality=75&direct=true&src=https%3A%2F%2Fmovie-phinf.pstatic.net%2F20111222_227%2F13245234480448qNfK_JPEG%2Fmovie_image.jpg"
+ ],
+ "reviews": [
+ {
+ "docId": 1,
+ "reviewId": 26,
+ "score": 2,
+ "content": "6번봄",
+ "like": 1,
+ "tags": [],
+ "user":
+ {
+ "userId": 6,
+ "username": "징징이",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": 1,
+ "reviewId": 27,
+ "score": 4.5,
+ "content": "난 10번",
+ "like": 6,
+ "tags": [
+ "힐링",
+ "킬링타임"
+ ],
+ "user":
+ {
+ "userId": 7,
+ "username": "짱구",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": 1,
+ "reviewId": 28,
+ "score": 2,
+ "content": "영화관에서만 10번봄",
+ "like": 3,
+ "tags": [
+ "힐링"
+ ],
+ "user":
+ {
+ "userId": 8,
+ "username": "ironman",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": 1,
+ "reviewId": 29,
+ "score": 5,
+ "content": "왜 재밌다는지 모르겠음",
+ "like": 4,
+ "tags": [
+ "킬링타임",
+ "힐링",
+ "모험",
+ "감동"
+ ],
+ "user":
+ {
+ "userId": 9,
+ "username": "미야자키 하야오",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+
+ },
+ {
+ "docId": 1,
+ "reviewId": 30,
+ "score": 5,
+ "content": "굿",
+ "like": 3,
+ "tags": [
+ "감동",
+ "음악"
+ ],
+ "user":
+ {
+ "userId": 10,
+ "username": "일론머스크",
+ "profile_Img": "https://d2u3dcdbebyaiu.cloudfront.net/uploads/atch_img/646/b447de09bcdc8174046f61581355d374.jpeg"
+ }
+ }
+ ]
+ },
+ "pageInfo": {
+ "page": 1,
+ "size": 5,
+ "totalElements": 2,
+ "totalPages": 1
+ },
+ "recommend": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/public/mockupdata/searchdata.json b/client/public/mockupdata/searchdata.json
new file mode 100644
index 0000000..466e943
--- /dev/null
+++ b/client/public/mockupdata/searchdata.json
@@ -0,0 +1,111 @@
+{
+ "movie": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ },
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ],
+ "recommend": [
+ {
+ "docId": "K28661",
+ "title": "스파이더맨: 어크로스 더 유니버스",
+ "repRlsDate": "20230602",
+ "score": 4.6,
+ "posterUrl": "https://i.namu.wiki/i/YaOX0b7cNnR9sLNgLAncoYcIHJ71PLcvP1FC89HWvwGxoXG6wuUlXAsBFHI_f6lvahFXRhljZis-zI4PFA1ys4wJtoBgYIINQCAPNWqW-uo2Lk3nNu_NMLvMI9vbWPhfBoqYdY8s5C7NOtKPMFiqGA.webp"
+ },
+ {
+ "docId": "K28662",
+ "title": "인터스텔라",
+ "repRlsDate": "20141107",
+ "score": 4.5,
+ "posterUrl": "https://i.namu.wiki/i/ei0XJc1X5aV2Z9rEN1L9BnJVfQ0VfizGdGK3QTFwhz5QXOxGSeZ2zWcJsvFZ2FjXl23ajEOeCE4msO1EZ6xHFMDi_YafkaoFB9jPS7HExnYJMmwbJxh2XStOdh4Wtp24sTKvTAsltpYceUiOcgwa-Q.webp"
+ },
+ {
+ "docId": "K28663",
+ "title": "겨울왕국",
+ "repRlsDate": "20131127",
+ "score": 4.3,
+ "posterUrl": "https://i.namu.wiki/i/mvGynWOsOvDngjMlad2Kz5JxZQ1BqcTtPkAg2P3oE5FA6RAvYTAlcbt8rP8iNjBC4kQ3z4JLJRg1m6IzLkKrOjAmss32AVu_4uedeWfsjmunm61tfxeDGQd-VYt51nhpJjMaoxTwhm9KLmxcMJrsdQ.webp"
+ },
+ {
+ "docId": "K28664",
+ "title": "범죄와의 전쟁",
+ "repRlsDate": "20120202",
+ "score": 4.1,
+ "posterUrl": "https://i.namu.wiki/i/kUuVAVNy0jTXcSQ6xPvGDpN9BUOkdxtPuki4RCYHpb9eD5YFYbvbsPWNerBfqTIqGeTzB2OF9EJ1RyA6PNXVi0vRzY0d3dQsSzgkpRw75xsonNpl3ZPX2uhr9Gc6NBLLKqy4UT0DmeqdIy9SnpvnoQ.webp"
+ },
+ {
+ "docId": "K28665",
+ "title": "해리포터와 마법사의 돌",
+ "repRlsDate": "20011114",
+ "score": 4.0,
+ "posterUrl": "https://i.namu.wiki/i/_pr1mVcm6gjM8u7gz6kDuVfUSOn0UeYrXDPNWNHAVRS0FikoDjTpg9hBOBo3erF-FK5FRkkJgqZd8GmfHia3Y-8JpbWdXxBqF0vQCAD--iXiAb1D2O82ZgHCZlxS1whQdPiwgTxvCV4BN9bxdXZTYQ.webp"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 72641b3..736a439 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,13 +1,14 @@
import './App.css';
import { Outlet } from 'react-router-dom';
-import Main from './pages/MainPage/Main';
-import MovieGallery from './pages/UI/MovieGallery';
import Header from './pages/UI/Header';
+import Footer from './pages/UI/Footer';
function App() {
return (
<>
+
+
>
);
}
diff --git a/client/src/components/LogoutButton.tsx b/client/src/components/LogoutButton.tsx
new file mode 100644
index 0000000..219ebda
--- /dev/null
+++ b/client/src/components/LogoutButton.tsx
@@ -0,0 +1,20 @@
+import { removeCookie } from '../utils/cookie';
+import { useNavigate } from 'react-router-dom';
+
+const LogoutButton = () => {
+ const navigate = useNavigate();
+
+ const handleLogout = () => {
+ removeCookie('jwtToken');
+ window.alert('로그아웃 되었습니다.');
+ navigate('/');
+ };
+
+ return (
+
+ );
+};
+
+export default LogoutButton;
diff --git a/client/src/components/SocialLogin.tsx b/client/src/components/SocialLogin.tsx
index b9a6ec0..3a796a8 100644
--- a/client/src/components/SocialLogin.tsx
+++ b/client/src/components/SocialLogin.tsx
@@ -1,19 +1,22 @@
import kakao from '../pages/UI/login-button/kakaoBtn.svg';
-import naver from '../pages/UI/login-button/naverBtn.svg';
+import naverIcon from '../pages/UI/login-button/naverBtn.svg';
import facebook from '../pages/UI/login-button/facebookBtn.svg';
import apple from '../pages/UI/login-button/appleBtn.svg';
+import kakaoLoginRequestHandler from '../utils/Login/kakaoLogin';
const SocialLogin = () => {
return (
- <>
+
);
};
diff --git a/client/src/components/Spinner.tsx b/client/src/components/Spinner.tsx
new file mode 100644
index 0000000..c658ba9
--- /dev/null
+++ b/client/src/components/Spinner.tsx
@@ -0,0 +1,10 @@
+const Spinner = () => {
+ return (
+
+ );
+};
+
+export default Spinner;
diff --git a/client/src/components/Test.tsx b/client/src/components/Test.tsx
deleted file mode 100644
index 84c0c02..0000000
--- a/client/src/components/Test.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useAppSelector, useAppDispatch } from '../redux-toolkit/hooks';
-
-import { decrement, increment, selectTest } from '../redux-toolkit/slices/testSlice';
-
-const Test = () => {
- const test = useAppSelector(selectTest);
- const dispatch = useAppDispatch();
- return (
-
-
-
테스트: {test}
-
-
- );
-};
-
-export default Test;
diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts
new file mode 100644
index 0000000..0ae3a29
--- /dev/null
+++ b/client/src/constants/constants.ts
@@ -0,0 +1,10 @@
+export const emailValidationRegex =
+ /^(([^<>()\\[\].,;:\s@"]+(\.[^<>()\\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
+
+export const pwValidationRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
+
+export const REST_API_KEY = 'c53bc747113925c65fbd2c3ff50ee43a';
+
+export const REDIRECT_URI = 'http://localhost:5173/redirect';
+
+export const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}`;
diff --git a/client/src/index.css b/client/src/index.css
index bd6213e..dd812bb 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -1,3 +1,15 @@
@tailwind base;
@tailwind components;
-@tailwind utilities;
\ No newline at end of file
+@tailwind utilities;
+
+@layer components {
+ .custom-height {
+ height: calc(100vh - 90px);
+ }
+
+ @media (max-width: 697px) {
+ .custom-height {
+ height: calc(100vh - 104px);
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/src/main.tsx b/client/src/main.tsx
index 80cd554..f69623d 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -1,39 +1,47 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
+import { CookiesProvider } from 'react-cookie';
import { store } from './redux-toolkit/store';
import { Provider } from 'react-redux';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import App from './App';
-import Test from './components/Test';
+import RedirectPage from './pages/RedirectPage/RedirectPage';
import SignupPage from './pages/SignupPage/SignupPage';
import LoginPage from './pages/LoginPage/LoginPage';
import DetailsPage from './pages/DetailsPage/DetailsPage';
+import MainPage from './pages/MainPage/MainPage';
+import ErrorPage from './pages/ErrorPage/ErrorPage';
+import SearchPage from './pages/SearchPage/SearchPage';
+import MyPage from './pages/MyPage/MyPage';
+import CategoryPage from './pages/CategoryPage/CategoryPage';
const router = createBrowserRouter([
{
path: '/',
element: ,
- // 에러페이지 라우터 적용 예시
- // errorElement: ,
-
children: [
- { index: true, element: },
+ { index: true, element: },
+ { path: '/redirect', element: },
{ path: '/signup', element: },
{ path: '/login', element: },
- {
- path: '/movies/:movieId',
- element: ,
- },
+ { path: '/search', element: },
+ { path: '/movies/:movieId', element: },
+ { path: '/category/:genre', element: },
+ { path: '/category/:tag', element: },
+ { path: '*', element: },
+ { path: '/mypage', element: },
],
},
]);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
-
-
+
+
+
+
+
);
diff --git a/client/src/pages/CategoryPage/CategoryPage.tsx b/client/src/pages/CategoryPage/CategoryPage.tsx
new file mode 100644
index 0000000..221f96c
--- /dev/null
+++ b/client/src/pages/CategoryPage/CategoryPage.tsx
@@ -0,0 +1,15 @@
+import MovieRecommend from '../UI/MovieRecommend';
+import CategorymostReview from './components/CategoryMostReview';
+import CategoryTopScore from './components/CategoryTopScore';
+
+const CategoryPage = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default CategoryPage;
diff --git a/client/src/pages/CategoryPage/components/CategoryMostReview.tsx b/client/src/pages/CategoryPage/components/CategoryMostReview.tsx
new file mode 100644
index 0000000..856dc32
--- /dev/null
+++ b/client/src/pages/CategoryPage/components/CategoryMostReview.tsx
@@ -0,0 +1,112 @@
+import { useState, useEffect } from 'react';
+import axios from 'axios';
+import MoviePoster from '../../UI/MoivePoster';
+import { useLocation } from 'react-router-dom';
+
+interface Movie {
+ title: string;
+ docId: string;
+ repRlsDate: string;
+ score: number;
+ bookmarked: boolean;
+ posterUrl: string;
+}
+
+const CategorymostReview: React.FC = () => {
+ const [showAllMovies, setShowAllMovies] = useState(false);
+ const [movies, setMovies] = useState([]);
+ const [isLoading, setisLoading] = useState(true);
+ // debugging 반복
+ const location = useLocation();
+ const queryString = new URLSearchParams(location.search);
+ const genreFromQuery = queryString.get('genre');
+ const tagFromQuery = queryString.get('tag');
+ //console.log(movies);
+
+ console.log('queryString', queryString);
+ console.log('genreFromQuery', genreFromQuery);
+ console.log('tagFromQuery', tagFromQuery);
+
+ useEffect(() => {
+ const fetchMovies = async () => {
+ try {
+ let endpoint =
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/category';
+
+ // 장르 또는 태그 정보가 쿼리스트링에 있다면, 해당 정보로 요청하는 엔드포인트를 변경
+ if (genreFromQuery) {
+ endpoint = `http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/movies/genre?genre=${genreFromQuery}`;
+ } else if (tagFromQuery) {
+ endpoint = `http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/movies/tag?tag=#${tagFromQuery}`;
+ } //encodeURIComponent()
+
+ const response = await axios.get(endpoint);
+ setisLoading(false);
+
+ if (response.status === 200) {
+ const data = response.data;
+ const mostReview = data.mostReview;
+ setMovies(mostReview);
+ console.log(data);
+ } else {
+ console.log('Failed to fetch movies:', response.data);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ setisLoading(false);
+ }
+ };
+
+ fetchMovies();
+ }, [genreFromQuery, tagFromQuery]);
+
+ const handleShowMoreMovies = () => {
+ setShowAllMovies(!showAllMovies);
+ };
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ const renderMovies = () => {
+ const moviesToRender = showAllMovies ? movies : movies.slice(0, 5);
+ return (
+
+
+ {genreFromQuery ? `${genreFromQuery} Most Reviewed` : `#${tagFromQuery} Most Reviewed`}
+ {showAllMovies ? (
+
+ ) : (
+ <>>
+ )}
+
+
+ {moviesToRender.map((movie, index) => (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderMovies()}
+ {!showAllMovies && movies.length > 5 && (
+
+ )}
+
+ );
+};
+export default CategorymostReview;
diff --git a/client/src/pages/CategoryPage/components/CategoryTopScore.tsx b/client/src/pages/CategoryPage/components/CategoryTopScore.tsx
new file mode 100644
index 0000000..dfbcc6f
--- /dev/null
+++ b/client/src/pages/CategoryPage/components/CategoryTopScore.tsx
@@ -0,0 +1,112 @@
+import { useState, useEffect } from 'react';
+import axios from 'axios';
+import MoviePoster from '../../UI/MoivePoster';
+import { useLocation } from 'react-router-dom';
+
+interface Movie {
+ title: string;
+ docId: string;
+ repRlsDate: string;
+ score: number;
+ bookmarked: boolean;
+ posterUrl: string;
+}
+
+const CategoryTopScore: React.FC = () => {
+ const [showAllMovies, setShowAllMovies] = useState(false);
+ const [movies, setMovies] = useState([]);
+ const [isLoading, setisLoading] = useState(true);
+ // debugging 반복
+ const location = useLocation();
+ const queryString = new URLSearchParams(location.search);
+ const genreFromQuery = queryString.get('genre');
+ const tagFromQuery = queryString.get('tag');
+ //console.log(movies);
+
+ console.log('queryString', queryString);
+ console.log('genreFromQuery', genreFromQuery);
+ console.log('tagFromQuery', tagFromQuery);
+
+ useEffect(() => {
+ const fetchMovies = async () => {
+ try {
+ let endpoint =
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/category';
+
+ // 장르 또는 태그 정보가 쿼리스트링에 있다면, 해당 정보로 요청하는 엔드포인트를 변경
+ if (genreFromQuery) {
+ endpoint = `http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/movies/genre?genre=${genreFromQuery}`;
+ } else if (tagFromQuery) {
+ endpoint = `http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/movies/tag?tag=#${tagFromQuery}`;
+ } //encodeURIComponent(
+
+ const response = await axios.get(endpoint);
+ setisLoading(false);
+
+ if (response.status === 200) {
+ const data = response.data;
+ const topscore = data.topScore;
+ setMovies(topscore);
+ console.log(data);
+ } else {
+ console.log('Failed to fetch movies:', response.data);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ setisLoading(false);
+ }
+ };
+
+ fetchMovies();
+ }, [genreFromQuery, tagFromQuery]);
+
+ const handleShowMoreMovies = () => {
+ setShowAllMovies(!showAllMovies);
+ };
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ const renderMovies = () => {
+ const moviesToRender = showAllMovies ? movies : movies.slice(0, 5);
+ return (
+
+
+ {genreFromQuery ? `${genreFromQuery} Top Score` : `#${tagFromQuery} Top Score`}
+ {showAllMovies ? (
+
+ ) : (
+ <>>
+ )}
+
+
+ {moviesToRender.map((movie, index) => (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderMovies()}
+ {!showAllMovies && movies.length > 5 && (
+
+ )}
+
+ );
+};
+export default CategoryTopScore;
diff --git a/client/src/pages/DetailsPage/DetailsPage.tsx b/client/src/pages/DetailsPage/DetailsPage.tsx
index 37a808d..e1afbcf 100644
--- a/client/src/pages/DetailsPage/DetailsPage.tsx
+++ b/client/src/pages/DetailsPage/DetailsPage.tsx
@@ -1,71 +1,122 @@
+import api from './assets/api/axiosInstance';
import { useAppSelector, useAppDispatch } from '../../redux-toolkit/hooks';
import { fetchMovieSuccess, selectMovieDetails } from '../../redux-toolkit/slices/movieDetailSlice';
import { useEffect, useState } from 'react';
-import { useParams } from 'react-router-dom';
-import { getMovies } from './assets/api/movieApi';
+import { useParams, useSearchParams } from 'react-router-dom';
import MovieTitle from './MovieTitle/MovieTitle';
import MovieInfo from './MovieInfo/MovieInfo';
import Review from './Review/Review';
-import CreateReviewModal from './UI/CreateReviewModal/CreateReviewModal';
+import ReviewModal from './UI/ReviewModal/ReviewModal';
+import Pagination from './UI/Pagination';
+import MoviePoster from '../UI/MoivePoster';
+import ErrorPage from '../ErrorPage/ErrorPage';
+import Spinner from '../../components/Spinner';
+import { getCookie } from '../../utils/cookie'; // 로그인 기능 완성시 사용
const DetailsPage = () => {
- const dispatch = useAppDispatch();
+ const isLoggedIn = Boolean(getCookie('jwtToken')); // 로그인 기능 완성시 사용
const movieDetail = useAppSelector(selectMovieDetails);
const [isModalOpen, setIsModalOpen] = useState(false);
- const { movieId } = useParams();
+ const [isLoading, setIsLoading] = useState(true);
+ const [isError, setIsError] = useState(false);
+ const dispatch = useAppDispatch();
+ console.log(isLoggedIn);
- //로그인 상태이용해서 modal창 open or 로그인/회원가입 요구
+ // 리액트 라우터 돔
+ const { movieId } = useParams(); // 테스트용 임시 movieId: "F58480" (MoviePoster에서 링크 걸때까지)
+ const [searchParams] = useSearchParams();
+ const page = searchParams.get('page');
- const openModal = () => {
- setIsModalOpen(true);
- };
-
- const closeModal = () => {
- setIsModalOpen(false);
- };
+ // 페이지네이션
+ const pageNumber = Number(page || 1); // 쿼리파라미터가 없는 경우에 default값 1
+ const [totalReviews, setTotalReviews] = useState(0);
+ // 해당 영화데이터 get 요청 // 예상 endpoint: `/movies/${movieId}?page=${pageNumber}`
+ // `/mockupdata/moviedetails${pageNumber}.json` => 목업데이터
useEffect(() => {
- const fetchData = async () => {
+ const fetchMovieDetail = async () => {
try {
- const movieData = await getMovies();
- dispatch(fetchMovieSuccess(movieData));
+ const response = await api.get(`/movies/${movieId}?page=${pageNumber}`);
+ dispatch(fetchMovieSuccess(response.data));
+ setTotalReviews(response.data.pageInfo.totalElements);
+ setIsLoading(false);
} catch (err) {
console.error(err);
+ setIsError(true);
}
};
- fetchData();
- }, [dispatch]);
+ fetchMovieDetail();
+ }, [dispatch, pageNumber, movieId]);
- console.log(movieDetail);
+ // 모달 열기, 닫기
+ // 로그인 기능 완성시, 아래주석 삭제
+ const openModal = () => {
+ if (!isLoggedIn) {
+ alert('로그인을 해주세요.');
+ } else {
+ setIsModalOpen(true);
+ }
+ };
+ // const openModal = () => {
+ // setIsModalOpen(true);
+ // };
+ const closeModal = () => {
+ setIsModalOpen(false);
+ };
return (
<>
-
-
-
-
-
리뷰 {movieDetail?.movie.review_count}개
-
-
-
- {movieDetail?.review.rev.map((review, index) => {
- return
;
- })}
-
-
비슷한 장르의 영화
-
- {/* 컴포넌트로 들어갈 예정 */}
-
-
영화
-
영화
-
영화
-
영화
-
영화
+ {isError ? (
+
// (error메세지 상태 props로 넘기기? or not)
+ ) : isLoading ? (
+
+
-
- {isModalOpen &&
}
+ ) : (
+ <>
+
+ {/* 영화정보 */}
+
+ {/* 리뷰 */}
+
+
+
리뷰 {movieDetail?.pageInfo.totalElements}개
+
+
+ {movieDetail?.movie.reviews.map((review, index) => {
+ return
;
+ })}
+
+
+ {/* 추천영화 */}
+
+
비슷한 장르의 영화
+
+ {movieDetail?.recommended_movies &&
+ movieDetail?.recommended_movies.map((movie, index) => (
+
+ ))}
+
+
+ {/* 리뷰작성모달 */}
+ {isModalOpen &&
}
+ >
+ )}
>
);
};
diff --git a/client/src/pages/DetailsPage/MovieInfo/MovieInfoBottom/MovieInfoBottom.tsx b/client/src/pages/DetailsPage/MovieInfo/MovieInfoBottom/MovieInfoBottom.tsx
index dca597f..5d2b9bb 100644
--- a/client/src/pages/DetailsPage/MovieInfo/MovieInfoBottom/MovieInfoBottom.tsx
+++ b/client/src/pages/DetailsPage/MovieInfo/MovieInfoBottom/MovieInfoBottom.tsx
@@ -1,27 +1,59 @@
-import still1 from '../../assets/photo-1.jpg';
-import still2 from '../../assets/photo-2.jpg';
-import still3 from '../../assets/photo-3.jpg';
-import still4 from '../../assets/photo-4.jpg';
+import { useState } from 'react';
+import { useAppSelector } from '../../../../redux-toolkit/hooks';
+import { selectMovieDetails } from '../../../../redux-toolkit/slices/movieDetailSlice';
const MovieInfoBottom = () => {
+ const movieDetail = useAppSelector(selectMovieDetails);
+ const [isModalOpen, setIsModalOpen] = useState
(false);
+ const [stillUrl, setStillUrl] = useState('');
+
+ const preventModalClick = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ };
+
+ const openModal = (still: string) => {
+ setIsModalOpen(true);
+ setStillUrl(still);
+ };
+ const closeModal = () => {
+ setIsModalOpen(false);
+ };
+
return (
-
-
갤러리
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
갤러리
+
+ {movieDetail?.movie.stills.map((still, index) => (
+
+ {/* 버튼이 아니어도 onClick 이벤트 줄 수 있게, 아래 줄에 한해서 eslint 무시 */}
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */}
+
openModal(still)}
+ className="h-full w-full cursor-pointer duration-300 ease-in-out hover:scale-110"
+ src={still}
+ alt="still"
+ />
+
+ ))}
-
+ {isModalOpen && (
+
+
+
+
+
+ )}
+ >
);
};
diff --git a/client/src/pages/DetailsPage/MovieInfo/MovieInfoTop/MovieInfoTop.tsx b/client/src/pages/DetailsPage/MovieInfo/MovieInfoTop/MovieInfoTop.tsx
index 70510fe..94077be 100644
--- a/client/src/pages/DetailsPage/MovieInfo/MovieInfoTop/MovieInfoTop.tsx
+++ b/client/src/pages/DetailsPage/MovieInfo/MovieInfoTop/MovieInfoTop.tsx
@@ -1,55 +1,49 @@
import { useAppSelector } from '../../../../redux-toolkit/hooks';
import { selectMovieDetails } from '../../../../redux-toolkit/slices/movieDetailSlice';
import Person from '../../UI/Person';
-import directorImg from '../../assets/director-img.jpg';
-import actress1Img from '../../assets/actress-1.jpg';
-import actor1Img from '../../assets/actor-1.jpg';
import { AiFillStar } from 'react-icons/ai';
import { FaPlay } from 'react-icons/fa';
-const people = [
- {
- name: '미야자키 하야오',
- img: directorImg,
- },
- {
- name: '히이라기 루미',
- img: actress1Img,
- },
- {
- name: '이리노 미유',
- img: actor1Img,
- },
-];
-
const MovieInfoTop = () => {
const movieDetail = useAppSelector(selectMovieDetails);
return (
-
-
-
+
+
+
-
-
평균 별점
+
+
평점
-
{movieDetail?.movie.score}
-
({movieDetail?.movie.review_count}명)
+
{movieDetail?.movie.score}
+
({movieDetail?.movie.review_count}명)
-
{movieDetail?.movie.description}
-
감독
-
-
출연
-
- {movieDetail?.movie.배우.map((actor, index) => {
- return
;
+
{movieDetail?.movie.description}
+
감독
+
+
출연
+
+ {movieDetail?.movie.actors.map((actor, index) => {
+ return
;
})}
diff --git a/client/src/pages/DetailsPage/MovieTitle/MovieTitle.tsx b/client/src/pages/DetailsPage/MovieTitle/MovieTitle.tsx
index 6b228d3..7d0aa90 100644
--- a/client/src/pages/DetailsPage/MovieTitle/MovieTitle.tsx
+++ b/client/src/pages/DetailsPage/MovieTitle/MovieTitle.tsx
@@ -1,19 +1,43 @@
-import mainImg from '../assets/mockup-main-img.jpg';
import { useAppSelector } from '../../../redux-toolkit/hooks';
import { selectMovieDetails } from '../../../redux-toolkit/slices/movieDetailSlice';
const MovieTitle = () => {
const movieDetail = useAppSelector(selectMovieDetails);
+
return (
-
-
-
-
-
{movieDetail?.movie.title}
-
(영어제목)
-
(개봉년도) · {movieDetail?.movie.genre} · (국가)
-
{movieDetail?.movie.running_time} · (연령)
-
+ // background img사용해서, absolute 사용할 필요 없어짐
+
+
+
{movieDetail?.movie.title}
+
{movieDetail?.movie.titleEng}
+
+ {movieDetail?.movie.repRlsDate} ·{' '}
+ {movieDetail?.movie.genre.length !== 0 ? movieDetail?.movie.genre : '(장르)'} ·{' '}
+ {movieDetail?.movie.nation.length !== 0 ? movieDetail?.movie.nation : '(국가)'}
+
+
+ {movieDetail?.movie.runtime.length !== 0
+ ? movieDetail?.movie.runtime + '분'
+ : '(상영시간)'}{' '}
+ · {movieDetail?.movie.rating.length !== 0 ? movieDetail?.movie.rating : '(연령)'}
+
);
diff --git a/client/src/pages/DetailsPage/Review/Comment/Comment.tsx b/client/src/pages/DetailsPage/Review/Comment/Comment.tsx
index 104fc12..8fac28b 100644
--- a/client/src/pages/DetailsPage/Review/Comment/Comment.tsx
+++ b/client/src/pages/DetailsPage/Review/Comment/Comment.tsx
@@ -1,11 +1,42 @@
+import { useState } from 'react';
import UserForComment from '../../UI/UserForComment';
+import CommentForm from './CommentForm/CommentForm';
const Comment = () => {
+ const [isEditExpandOpen, setIsEditExandOpen] = useState
(false);
+ const prevCommentInput = '기존 댓글'; // 서버로부터 받아온 기존 댓글내용
+ const expandEditOpenHandler: () => void = () => {
+ setIsEditExandOpen(!isEditExpandOpen);
+ };
+ const handleCommentInputDelete = () => {
+ // DELETE 요청 // 댓글 id 이용
+ };
return (
-
+ <>
+
+
+ {/* 수정 및 삭제버튼 쿠키의 유저정보(아마 id)와 해당 댓글의 유저id가 일치하는 경우에만 렌더링 */}
+
+
+
+
+
+ {isEditExpandOpen && (
+
+
+
+ )}
+ >
);
};
diff --git a/client/src/pages/DetailsPage/Review/Comment/CommentForm/CommentForm.tsx b/client/src/pages/DetailsPage/Review/Comment/CommentForm/CommentForm.tsx
new file mode 100644
index 0000000..adcc174
--- /dev/null
+++ b/client/src/pages/DetailsPage/Review/Comment/CommentForm/CommentForm.tsx
@@ -0,0 +1,40 @@
+import { useState } from 'react';
+
+interface PrevCommentInputProps {
+ prevCommentInput?: string;
+}
+
+const CommentForm = ({ prevCommentInput }: PrevCommentInputProps) => {
+ const [commentInput, setCommentInput] = useState(prevCommentInput ? prevCommentInput : '');
+
+ const handleCommentInputChange = (e: React.ChangeEvent) => {
+ setCommentInput(e.target.value);
+ };
+
+ // 댓글작성 post 요청, 댓글수정 patch 요청
+ const handleCommentInputSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (prevCommentInput) {
+ // PATCH 요청
+ } else {
+ // POST 요청
+ }
+ };
+ console.log(commentInput);
+ return (
+
+ );
+};
+
+export default CommentForm;
diff --git a/client/src/pages/DetailsPage/Review/Review.tsx b/client/src/pages/DetailsPage/Review/Review.tsx
index 3ee259a..ddfe076 100644
--- a/client/src/pages/DetailsPage/Review/Review.tsx
+++ b/client/src/pages/DetailsPage/Review/Review.tsx
@@ -1,28 +1,74 @@
+import api from '../assets/api/axiosInstance';
import { useState } from 'react';
import ReviewTop from './ReviewTop/ReviewTop';
import ReviewBottom from './ReviewBottom/ReviewBottom';
import Comment from './Comment/Comment';
+import ReviewModal from '../UI/ReviewModal/ReviewModal';
+import CommentForm from './Comment/CommentForm/CommentForm';
import { IoMdArrowDropdown, IoMdArrowDropup } from 'react-icons/io';
import { ReviewContent } from '../assets/types/movieTypes';
+import { getCookie } from '../../../utils/cookie'; // 로그인 기능 완성시 사용
+import { useNavigate } from 'react-router-dom';
export interface ReviewProps {
review: ReviewContent;
}
const Review = ({ review }: ReviewProps) => {
+ const token = getCookie('jwtToken'); // 로그인 기능 완성시 사용
const [isExpandOpen, setIsExandOpen] = useState(false);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const navigate = useNavigate();
+
const expandOpenHandler: () => void = () => {
setIsExandOpen(!isExpandOpen);
};
+ const openModal = () => {
+ setIsModalOpen(true);
+ };
+ const closeModal = () => {
+ setIsModalOpen(false);
+ };
+
+ //리뷰 삭제(DELETE 요청) // 예상 endpoint: `/reviews/${review.reviewId}`
+ const handleReviewDelete = async () => {
+ try {
+ const response = await api.delete(`/reviews/${review.reviewId}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ console.log(response);
+ alert('삭제되었습니다.');
+ navigate(`/movies/${review.docId}?page=1`);
+ } catch (err) {
+ console.error(err);
+ alert('에러가 발생했습니다. 다시 시도해주세요: ' + err);
+ }
+ };
+
return (
<>
-
{review.content}
+
+
{review.content}
+
+ {/* 수정 및 삭제버튼 쿠키의 유저정보(아마 id)와 해당 리뷰의 유저id가 일치하는 경우에만 렌더링 */}
+
+
+
+
-
+
{isExpandOpen ? (
) : (
@@ -33,11 +79,14 @@ const Review = ({ review }: ReviewProps) => {
{isExpandOpen ? (
<>
+
+ {/* Comment 데이터 받으면, map으로 뿌려줌 */}
>
) : null}
+ {isModalOpen &&
}
>
);
};
diff --git a/client/src/pages/DetailsPage/Review/ReviewBottom/ReviewBottom.tsx b/client/src/pages/DetailsPage/Review/ReviewBottom/ReviewBottom.tsx
index e544b48..ecef3ed 100644
--- a/client/src/pages/DetailsPage/Review/ReviewBottom/ReviewBottom.tsx
+++ b/client/src/pages/DetailsPage/Review/ReviewBottom/ReviewBottom.tsx
@@ -1,22 +1,68 @@
-import { useState } from 'react';
+import api from '../../assets/api/axiosInstance';
+// import { useState } from 'react';
import Tag from '../../UI/Tag';
-import { MdOutlineThumbUp, MdOutlineThumbDown } from 'react-icons/md';
+import { MdOutlineThumbUp } from 'react-icons/md';
import { ReviewProps } from '../Review';
+import { getCookie } from '../../../../utils/cookie'; // 로그인 기능 완성시 사용
const ReviewBottom = ({ review }: ReviewProps) => {
- const [isLogin, setIsLogin] = useState
(false); // 나중에 실제 로그인 상태 이용
- const [likes, setLikes] = useState(review.like); // 리다이렉트를 사용하면, 상태를 이렇게 수동으로 변경하지 않아도 될 수도..
+ const isLoggedIn = Boolean(getCookie('jwtToken')); // 로그인 기능 완성시 사용
+ const token = getCookie('jwtToken');
+ // const [likes, setLikes] = useState(review.likes); // 좋아요 요청만 요청 성공시 상태 변경(페이지 리프레쉬 -> 사용자경험에 좋지않음)
+ // 처음 likes가져와서 렌더링 하는 과정에서 상태가 변경되지 않는거같음 그래서 그냥 일단 refresh하는걸로
- const handleLike: () => void = () => {
- if (!isLogin) {
- alert('로그인을 해주세요');
+ // 좋아요 클릭 post 요청 // 예상 endpoint: `/review/{review-id}/likes`
+ // 로그인 기능 완성시 사용
+ // 같은 유저가 같은 리뷰 좋아요 두 번 눌렀을때의 로직 고민해봐야함.
+ const likeClickHandler = async () => {
+ if (!isLoggedIn) {
+ alert('로그인을 해주세요.');
} else {
- setLikes((prev) => prev + 1);
+ try {
+ const response = await api.post(
+ `/review/${review.reviewId}/likes`,
+ {},
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`, // 이걸 보내야하는지 안보내도 되는지 모르겠음
+ },
+ }
+ );
+ console.log(response);
+ alert('추가되었습니다.');
+ window.location.reload();
+ // setLikes((prev) => prev + 1);
+ } catch (err) {
+ console.error(err);
+ alert('에러가 발생했습니다. 다시 시도해주세요: ' + err);
+ }
}
};
+
+ // const likeClickHandler = async () => {
+ // try {
+ // const response = await api.post(
+ // `/review/${review.reviewId}/likes`,
+ // {},
+ // {
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // // Authorization: `Bearer ${token}`,
+ // },
+ // }
+ // );
+ // console.log(response);
+ // setLikes((prev) => prev + 1);
+ // } catch (err) {
+ // console.error(err);
+ // alert('에러가 발생했습니다. 다시 시도해주세요: ' + err);
+ // }
+ // };
+
return (
-
+
{review.tags.map((tag, index) => (
))}
@@ -24,17 +70,18 @@ const ReviewBottom = ({ review }: ReviewProps) => {
-
+
-
-
{likes}
+
{review.likes}
-
+
+ {/* 싫어요 기능 제거 */}
+ {/*
+
*/}
);
diff --git a/client/src/pages/DetailsPage/Review/ReviewTop/ReviewTop.tsx b/client/src/pages/DetailsPage/Review/ReviewTop/ReviewTop.tsx
index b0f9393..d50401c 100644
--- a/client/src/pages/DetailsPage/Review/ReviewTop/ReviewTop.tsx
+++ b/client/src/pages/DetailsPage/Review/ReviewTop/ReviewTop.tsx
@@ -11,12 +11,12 @@ const ReviewTop = ({ review }: ReviewProps) => {
{Array(Math.floor(review.score))
.fill('')
.map((_, index) => (
-
+
))}
{review.score - Math.floor(review.score) > 0 &&
Array(review.score - Math.floor(review.score) + 0.5)
.fill('')
- .map((_, index) =>
)}
+ .map((_, index) =>
)}
);
diff --git a/client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/ModalForm.tsx b/client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/ModalForm.tsx
deleted file mode 100644
index 5b6fd08..0000000
--- a/client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/ModalForm.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { useState } from 'react';
-import ModalTag from './ModalTag/ModalTag';
-import RatingStars from 'react-rating-stars-component';
-import { ReviewContent } from '../../../assets/types/movieTypes';
-// import { postReview } from '../../../assets/api/movieApi';
-// import { redirect } from 'react-router-dom';
-
-const tags: string[] = [
- '#태그1',
- '#태그2',
- '#태그3',
- '#태그4',
- '#태그5',
- '#태그6',
- '#태그7',
- '#태그8',
- '#태그9',
- '#태그10',
-];
-
-interface ModalFormProps {
- movieId: string | undefined;
- review?: ReviewContent;
-}
-
-const ModalForm = ({ movieId, review }: ModalFormProps) => {
- const [selectedTags, setSelectedTags] = useState(review ? review.tags : []);
- const [reviewContent, setReviewContent] = useState(review ? review.content : '');
- const [score, setScore] = useState(review ? review.score : 0);
-
- console.log(selectedTags);
-
- const addTagHandler = (tag: string) => {
- const updatedTags = [...selectedTags];
- if (updatedTags.includes(tag)) {
- const index = updatedTags.indexOf(tag);
- updatedTags.splice(index, 1);
- } else {
- updatedTags.push(tag);
- }
- setSelectedTags(updatedTags);
- };
-
- const handleContentChange = (event: React.ChangeEvent) => {
- setReviewContent(event.target.value);
- };
-
- const handleStarClick = (newRating: number) => {
- setScore(newRating);
- };
-
- // const handleFormSubmit = async (event: React.FormEvent) => {
- // event.preventDefault();
-
- // const createdReviewData = {
- // movieId: 1,
- // userId: 1,
- // userImg: '',
- // userName: '',
- // score: score,
- // content: reviewContent,
- // tags: selectedTags,
- // likes: 0,
- // comments: [],
- // };
-
- // try {
- // const redirectLocation = await postReview(createdReviewData, movieId);
- // return redirect(redirectLocation);
- // } catch (err) {
- // console.error('Error submitting review:', err);
- // }
- // };
-
- return (
-
- );
-};
-
-export default ModalForm;
diff --git a/client/src/pages/DetailsPage/UI/Pagination.tsx b/client/src/pages/DetailsPage/UI/Pagination.tsx
new file mode 100644
index 0000000..95b2f56
--- /dev/null
+++ b/client/src/pages/DetailsPage/UI/Pagination.tsx
@@ -0,0 +1,52 @@
+import { useState } from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+interface PaginationProps {
+ totalReviews: number;
+ movieId?: string;
+ pageNumber: number;
+}
+
+const Pagination = ({ totalReviews, movieId, pageNumber }: PaginationProps) => {
+ const navigate = useNavigate();
+ const [pageWindow, setPageWindow] = useState([1, 2, 3, 4, 5]);
+ const reviewPerPage = 5;
+ const totalPages = Math.ceil(totalReviews / reviewPerPage);
+ const nextPages = () => {
+ const newPageWindow = pageWindow.map((page) => page + 5);
+ setPageWindow(newPageWindow);
+ navigate(`/movies/${movieId}?page=${newPageWindow[0]}`);
+ };
+ const previousPages = () => {
+ const newPageWindow = pageWindow.map((page) => page - 5);
+ setPageWindow(newPageWindow);
+ navigate(`/movies/${movieId}?page=${newPageWindow[4]}`);
+ };
+ return (
+ <>
+ {pageNumber > 5 && (
+
+ )}
+ {pageWindow.map(
+ (page) =>
+ page <= totalPages && (
+
+ {page}
+
+ )
+ )}
+ {totalPages > 5 && totalPages > pageWindow[4] && (
+
+ )}
+ >
+ );
+};
+
+export default Pagination;
diff --git a/client/src/pages/DetailsPage/UI/Person.tsx b/client/src/pages/DetailsPage/UI/Person.tsx
index c912963..c2a3847 100644
--- a/client/src/pages/DetailsPage/UI/Person.tsx
+++ b/client/src/pages/DetailsPage/UI/Person.tsx
@@ -1,13 +1,25 @@
+import { Link } from 'react-router-dom';
+
interface PersonProps {
- img: string;
+ // img: string;
name: string | undefined;
+ role?: string;
}
+// 디폴트 프로필사진
+const defaultProfile =
+ 'https://thebulletin.org/wp-content/themes/atomic-bulletin/resources/assets/images/person-dummy.jpg';
-const Person = ({ img, name }: PersonProps) => {
+const Person = ({ name, role }: PersonProps) => {
+ const handleClick = () => {
+ window.scrollTo(0, 0);
+ };
return (
-
-
-
{name}
+
+
+
+
{name}
+ {role &&
{role} 역
}
+
);
};
diff --git a/client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/ModalForm.tsx b/client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/ModalForm.tsx
new file mode 100644
index 0000000..3b93352
--- /dev/null
+++ b/client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/ModalForm.tsx
@@ -0,0 +1,141 @@
+import api from '../../../assets/api/axiosInstance';
+import RatingStars from 'react-rating-stars-component';
+import { useState } from 'react';
+import { useAppSelector } from '../../../../../redux-toolkit/hooks';
+import { selectMovieDetails } from '../../../../../redux-toolkit/slices/movieDetailSlice';
+import { ModalProps } from '../ReviewModal';
+import { getCookie } from '../../../../../utils/cookie'; // 로그인 기능 완성시 사용
+import ModalTag from './ModalTag/ModalTag';
+
+const tags: string[] = [
+ '감동',
+ '음악',
+ '힐링',
+ '킬링타임',
+ '모험',
+ '창의적',
+ '영상미',
+ '영감',
+ '긴장감',
+ '반전',
+];
+
+const ModalForm = ({ closeModal, movieId, review }: ModalProps) => {
+ const token = getCookie('jwtToken');
+ console.log(token);
+ const movieDetail = useAppSelector(selectMovieDetails);
+ const [selectedTags, setSelectedTags] = useState
(review ? review.tags : []);
+ const [reviewContent, setReviewContent] = useState(review ? review.content : '');
+ const [score, setScore] = useState(review ? review.score : 0);
+
+ // 폼에서 태그 추가
+ const addTagHandler = (tag: string) => {
+ const updatedTags = [...selectedTags];
+ if (updatedTags.includes(tag)) {
+ const index = updatedTags.indexOf(tag);
+ updatedTags.splice(index, 1);
+ } else {
+ updatedTags.push(tag);
+ }
+ setSelectedTags(updatedTags);
+ };
+
+ // 폼에서 리뷰내용 추가
+ const handleContentChange = (event: React.ChangeEvent) => {
+ setReviewContent(event.target.value);
+ };
+
+ // 폼에서 별점 추가
+ const handleStarClick = (newRating: number) => {
+ setScore(newRating);
+ };
+
+ // 리뷰 등록 및 수정(조건부 POST, PATCH 요청) // 예상 endpoint: `/movies/{movie-id}/reviews`(POST) // PATCH는 `/reviews/${review.reviewId}`
+ const handleReviewFormSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+
+ // patch, post 둘다 리뷰 전체데이터 전송 (새 리뷰, 수정된 리뷰)
+ const reviewData = {
+ score: score,
+ content: reviewContent,
+ tags: selectedTags,
+ genre: movieDetail?.movie.genre,
+ };
+ if (reviewData.content && reviewData.score !== 0) {
+ if (review) {
+ try {
+ const response = await api.patch(`/reviews/${review.reviewId}`, reviewData, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ closeModal();
+ console.log(response);
+ alert('수정되었습니다.');
+ window.location.reload();
+ } catch (err) {
+ console.error(err);
+ alert('에러가 발생했습니다. 다시 시도해주세요: ' + err);
+ }
+ } else {
+ try {
+ const response = await api.post(`/movies/${movieId}/reviews`, reviewData, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ closeModal();
+ console.log(response);
+ alert('등록되었습니다.');
+ window.location.reload();
+ // 해당 페이지 리뷰5개일땐, 다음페이지로 넘겨줌. 5개 미만일땐 해당페이지 새로고침 (나중에 적용)
+ } catch (err) {
+ console.error(err);
+ alert('에러가 발생했습니다. 다시 시도해주세요: ' + err);
+ }
+ }
+ } else {
+ alert('리뷰내용 또는 별점을 추가해주세요!');
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default ModalForm;
diff --git a/client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/ModalTag/ModalTag.tsx b/client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/ModalTag/ModalTag.tsx
similarity index 98%
rename from client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/ModalTag/ModalTag.tsx
rename to client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/ModalTag/ModalTag.tsx
index 2dcbaba..4af5202 100644
--- a/client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/ModalTag/ModalTag.tsx
+++ b/client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/ModalTag/ModalTag.tsx
@@ -23,7 +23,7 @@ const ModalTag = ({ tag, addTagHandler, isSelected }: ModalTagProps) => {
: 'mb-2 mr-2 w-20 bg-theme2 text-white hover:bg-theme3'
}
>
- {tag}
+ #{tag}
);
};
diff --git a/client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/react-rating-stars-component.d.ts b/client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/react-rating-stars-component.d.ts
similarity index 100%
rename from client/src/pages/DetailsPage/UI/CreateReviewModal/ModalForm/react-rating-stars-component.d.ts
rename to client/src/pages/DetailsPage/UI/ReviewModal/ModalForm/react-rating-stars-component.d.ts
diff --git a/client/src/pages/DetailsPage/UI/CreateReviewModal/CreateReviewModal.tsx b/client/src/pages/DetailsPage/UI/ReviewModal/ReviewModal.tsx
similarity index 56%
rename from client/src/pages/DetailsPage/UI/CreateReviewModal/CreateReviewModal.tsx
rename to client/src/pages/DetailsPage/UI/ReviewModal/ReviewModal.tsx
index 0de429b..38609a3 100644
--- a/client/src/pages/DetailsPage/UI/CreateReviewModal/CreateReviewModal.tsx
+++ b/client/src/pages/DetailsPage/UI/ReviewModal/ReviewModal.tsx
@@ -1,32 +1,33 @@
import ModalForm from './ModalForm/ModalForm';
-import { ReviewContent } from './../../assets/types/movieTypes';
+import { ReviewContent } from '../../assets/types/movieTypes';
type CloseModalFn = () => void;
-interface ModalProps {
+export interface ModalProps {
closeModal: CloseModalFn;
- movieId: string | undefined;
+ movieId?: string;
review?: ReviewContent;
}
-const CreateReviewModal = ({ closeModal, movieId, review }: ModalProps) => {
+const ReviewModal = ({ closeModal, movieId, review }: ModalProps) => {
const preventModalClick = (event: React.MouseEvent) => {
event.stopPropagation();
};
+
return (
);
};
-export default CreateReviewModal;
+export default ReviewModal;
diff --git a/client/src/pages/DetailsPage/UI/Tag.tsx b/client/src/pages/DetailsPage/UI/Tag.tsx
index 9505413..bbfdf45 100644
--- a/client/src/pages/DetailsPage/UI/Tag.tsx
+++ b/client/src/pages/DetailsPage/UI/Tag.tsx
@@ -1,9 +1,15 @@
+import { Link } from 'react-router-dom';
+
interface TagProps {
tag: string;
}
const Tag = ({ tag }: TagProps) => {
- return ;
+ return (
+
+ );
};
export default Tag;
diff --git a/client/src/pages/DetailsPage/UI/UserForComment.tsx b/client/src/pages/DetailsPage/UI/UserForComment.tsx
index 1d53fdc..19d0ce5 100644
--- a/client/src/pages/DetailsPage/UI/UserForComment.tsx
+++ b/client/src/pages/DetailsPage/UI/UserForComment.tsx
@@ -1,10 +1,12 @@
-import profileImg from '../assets/profile.jpg';
+// 디폴트 프로필사진
+const defaultProfile =
+ 'https://thebulletin.org/wp-content/themes/atomic-bulletin/resources/assets/images/person-dummy.jpg';
const UserForComment = () => {
return (
-
-
+
+
(유저 이름)
diff --git a/client/src/pages/DetailsPage/UI/UserForPost.tsx b/client/src/pages/DetailsPage/UI/UserForPost.tsx
index a1df3aa..939f6f7 100644
--- a/client/src/pages/DetailsPage/UI/UserForPost.tsx
+++ b/client/src/pages/DetailsPage/UI/UserForPost.tsx
@@ -1,13 +1,24 @@
-import profileImg from '../assets/profile.jpg';
import { ReviewProps } from '../Review/Review';
+// 디폴트 프로필사진
+const defaultProfile =
+ 'https://thebulletin.org/wp-content/themes/atomic-bulletin/resources/assets/images/person-dummy.jpg';
+
const UserForPost = ({ review }: ReviewProps) => {
+ console.log(review);
return (
-
-
+
+
-
{review.username}
+ {/* 유저관련 정보 아직 서버로부터 안들어옴 */}
+
{review.user.username ? review.user.username : 'user'}
);
};
diff --git a/client/src/pages/DetailsPage/assets/actor-1.jpg b/client/src/pages/DetailsPage/assets/actor-1.jpg
deleted file mode 100644
index a211741..0000000
Binary files a/client/src/pages/DetailsPage/assets/actor-1.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/actress-1.jpg b/client/src/pages/DetailsPage/assets/actress-1.jpg
deleted file mode 100644
index 1043441..0000000
Binary files a/client/src/pages/DetailsPage/assets/actress-1.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/api/axiosInstance.ts b/client/src/pages/DetailsPage/assets/api/axiosInstance.ts
index 081df0a..fb196fb 100644
--- a/client/src/pages/DetailsPage/assets/api/axiosInstance.ts
+++ b/client/src/pages/DetailsPage/assets/api/axiosInstance.ts
@@ -1,7 +1,10 @@
import axios from 'axios';
const api = axios.create({
- baseURL: import.meta.env.REACT_APP_API_URL,
+ baseURL: 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
});
export default api;
diff --git a/client/src/pages/DetailsPage/assets/api/movieApi.ts b/client/src/pages/DetailsPage/assets/api/movieApi.ts
deleted file mode 100644
index 81c2c6d..0000000
--- a/client/src/pages/DetailsPage/assets/api/movieApi.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// 실제 데이터 사용시 사용할 코드
-import axios from 'axios';
-// import api from './axiosInstance';
-// import { Review } from '../types/reviewTypes';
-import { MovieDataResponse } from '../types/movieTypes';
-
-// export const getMovies = async (): Promise
=> {
-// const response = await api.get('');
-// return response.data;
-// };
-
-// export const postReview = async (review: Review, movieId: string | undefined): Promise => {
-// const response = await api.post(`/movies/${movieId}/reviews`, review);
-// const redirectLocation: string = response.headers.location;
-// return redirectLocation;
-// };
-
-export const getMovies = async (): Promise => {
- const response = await axios.get('/mockupdata/moviedetails.json');
- return response.data;
-};
diff --git a/client/src/pages/DetailsPage/assets/director-img.jpg b/client/src/pages/DetailsPage/assets/director-img.jpg
deleted file mode 100644
index ec013e3..0000000
Binary files a/client/src/pages/DetailsPage/assets/director-img.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/mockup-main-img.jpg b/client/src/pages/DetailsPage/assets/mockup-main-img.jpg
deleted file mode 100644
index 0a766d1..0000000
Binary files a/client/src/pages/DetailsPage/assets/mockup-main-img.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/photo-1.jpg b/client/src/pages/DetailsPage/assets/photo-1.jpg
deleted file mode 100644
index ef79eb7..0000000
Binary files a/client/src/pages/DetailsPage/assets/photo-1.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/photo-2.jpg b/client/src/pages/DetailsPage/assets/photo-2.jpg
deleted file mode 100644
index a81e5c5..0000000
Binary files a/client/src/pages/DetailsPage/assets/photo-2.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/photo-3.jpg b/client/src/pages/DetailsPage/assets/photo-3.jpg
deleted file mode 100644
index 77d54c6..0000000
Binary files a/client/src/pages/DetailsPage/assets/photo-3.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/photo-4.jpg b/client/src/pages/DetailsPage/assets/photo-4.jpg
deleted file mode 100644
index 67fa24c..0000000
Binary files a/client/src/pages/DetailsPage/assets/photo-4.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/profile.jpg b/client/src/pages/DetailsPage/assets/profile.jpg
deleted file mode 100644
index e69da09..0000000
Binary files a/client/src/pages/DetailsPage/assets/profile.jpg and /dev/null differ
diff --git a/client/src/pages/DetailsPage/assets/types/movieTypes.ts b/client/src/pages/DetailsPage/assets/types/movieTypes.ts
index 3666968..4c8f88f 100644
--- a/client/src/pages/DetailsPage/assets/types/movieTypes.ts
+++ b/client/src/pages/DetailsPage/assets/types/movieTypes.ts
@@ -1,45 +1,63 @@
export interface Movie {
+ docId: string;
title: string;
+ titleEng: string;
description: string;
genre: string;
- running_time: string;
- poster_url: string;
+ runtime: string;
+ repRlsDate: string;
+ nation: string;
+ rating: string;
+ posterUrl: string;
score: number;
- review_count: 6;
- 스태프: Staff[];
- 배우: Actor[];
-}
-
-export interface Staff {
- director: string;
+ review_count: number;
+ directorNm: string;
+ trailer: null | string;
+ backdrop: null | string;
+ actors: Actor[];
+ stills: string[];
+ reviews: ReviewContent[];
}
export interface Actor {
actor: string;
-}
-
-export interface Review {
- rev: ReviewContent[];
+ role: string;
}
export interface ReviewContent {
+ docId: string;
+ reviewId: number;
score: number;
- username: string;
content: string;
+ likes: number;
tags: string[];
- like: number;
+ user: User; // 백엔드쪽에서 아직구현x
}
+export interface User {
+ memberId: number;
+ username: string;
+ profile_img: string | null;
+}
+
+// search 데이터에서도 사용(같은 형태)
export interface Recommend {
- id: number;
+ docId: string;
title: string;
- release_date: string;
+ repRlsDate: string;
score: number;
- poster_url: string;
+ posterUrl: string;
+}
+
+export interface PageInfo {
+ page: number;
+ size: number;
+ totalElements: number;
+ totalPages: number;
}
-export interface MovieDataResponse {
+export interface MovieDetailData {
movie: Movie;
- review: Review;
- recommend: Recommend[];
+ pageInfo: PageInfo;
+ recommended_movies: Recommend[];
}
diff --git a/client/src/pages/ErrorPage/ErrorPage.tsx b/client/src/pages/ErrorPage/ErrorPage.tsx
new file mode 100644
index 0000000..1a89ada
--- /dev/null
+++ b/client/src/pages/ErrorPage/ErrorPage.tsx
@@ -0,0 +1,19 @@
+import { Link } from 'react-router-dom';
+
+const ErrorPage = () => {
+ return (
+
+
+
OOPS!
+
요청하신 페이지를 찾을 수 없습니다.
+
+
+ );
+};
+
+export default ErrorPage;
diff --git a/client/src/pages/LoginPage/LoginPage.tsx b/client/src/pages/LoginPage/LoginPage.tsx
index e7c3e7f..1b3f145 100644
--- a/client/src/pages/LoginPage/LoginPage.tsx
+++ b/client/src/pages/LoginPage/LoginPage.tsx
@@ -1,7 +1,8 @@
import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
-import axios from 'axios';
+import api from '../../utils/api';
import SocialLogin from '../../components/SocialLogin';
+import { setCookie } from '../../utils/cookie';
const LoginPage: React.FC = () => {
const [userEmail, setUserEmail] = useState('');
@@ -10,7 +11,13 @@ const LoginPage: React.FC = () => {
const navigate = useNavigate();
- const URL = '';
+ const handleEmailChange = (e: React.ChangeEvent) => {
+ setUserEmail(e.target.value);
+ };
+
+ const handlePasswordChange = (e: React.ChangeEvent) => {
+ setUserPassword(e.target.value);
+ };
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
@@ -21,44 +28,40 @@ const LoginPage: React.FC = () => {
}
try {
- const response = await axios.post(
- `${URL}/login`, // API 주소
- JSON.stringify({
- email: userEmail,
- password: userPassword,
- }),
- {
- headers: {
- 'Content-Type': 'application/json',
- 'Access-Control-Allow-Origin': '*',
- withCredentials: true,
- },
- }
- );
-
- console.log(response.headers);
- window.alert('님, 환영합니다!');
- navigate('/');
+ const response = await api.post('/auth/login', {
+ email: userEmail,
+ password: userPassword,
+ });
+ const authHeader = response.headers.authorization;
+ if (authHeader && authHeader.startsWith('Bearer ')) {
+ setCookie('jwtToken', authHeader.substring(7));
+ }
+ if (response.status === 200) {
+ window.alert('환영합니다!');
+ navigate('/');
+ } else {
+ window.alert('아이디 또는 비밀번호를 다시 확인해 주세요.');
+ }
} catch (err) {
console.log(err);
- window.alert('로그인에 실패하였습니다.');
+ window.alert('아이디 또는 비밀번호를 다시 확인해 주세요.');
}
};
return (
-
-
+
+
+
);
};
diff --git a/client/src/pages/MainPage/Main.tsx b/client/src/pages/MainPage/Main.tsx
deleted file mode 100644
index 06e61f5..0000000
--- a/client/src/pages/MainPage/Main.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import MoviePoster from '../UI/MoivePoster';
-
-const movie = {
- title: 'Avengers: Endgame',
- releaseDate: '2019',
- rating: 8.4,
- bookmarked: true,
- posterUrl: 'https://m.media-amazon.com/images/I/91zzAMkVCUL._AC_UF894,1000_QL80_.jpg',
-};
-
-const Main = () => {
- return (
-
- );
-};
-
-export default Main;
diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/MainPage/MainPage.tsx
new file mode 100644
index 0000000..4324f3c
--- /dev/null
+++ b/client/src/pages/MainPage/MainPage.tsx
@@ -0,0 +1,15 @@
+import BoxOfficeMovies from '../UI/BoxOffice';
+import HighRatings from '../UI/HighRatings';
+import HighReviewCount from '../UI/HightReviewCount';
+
+const MainPage: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default MainPage;
diff --git a/client/src/pages/MyPage/Bookmarks/MyBookmarks.tsx b/client/src/pages/MyPage/Bookmarks/MyBookmarks.tsx
new file mode 100644
index 0000000..ed5d457
--- /dev/null
+++ b/client/src/pages/MyPage/Bookmarks/MyBookmarks.tsx
@@ -0,0 +1,58 @@
+import ShortMovieList from '../UI/ShortMovieList';
+import { useEffect, useState } from 'react';
+import axios from 'axios';
+import { getCookie } from '../../../utils/cookie';
+
+interface MyBookmarkProps {
+ id: number;
+}
+
+const MyBookmarks = ({ id }: MyBookmarkProps) => {
+ const token = getCookie('jwtToken');
+ const [bookmarks, setBookmark] = useState([]);
+ useEffect(() => {
+ const fetchBookmarks = async () => {
+ try {
+ const res = await axios.get(
+ `http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/members/${id}/bookmarks`,
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ if (res.status === 200) {
+ const data = res.data;
+ setBookmark(data);
+ } else {
+ console.log('Failed to fetch bookmark data:', res.data);
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ fetchBookmarks();
+ }, [id, token]);
+
+ // api 연결 후 수정
+ const bookmarkList = [...bookmarks];
+ const moviesToRender = bookmarkList.slice(0, 5);
+ const bookmarksCounter = bookmarkList.length;
+
+ return (
+
+
나의 북마크
+
+ {bookmarksCounter > 0 ? (
+
+ ) : (
+
마음에 드는 영화를 북마크해 보세요.
+ )}
+
+
+ );
+};
+
+export default MyBookmarks;
diff --git a/client/src/pages/MyPage/MyPage.tsx b/client/src/pages/MyPage/MyPage.tsx
new file mode 100644
index 0000000..84cbf8b
--- /dev/null
+++ b/client/src/pages/MyPage/MyPage.tsx
@@ -0,0 +1,76 @@
+import { useState, useEffect } from 'react';
+import axios from 'axios';
+import MyBookmarks from './Bookmarks/MyBookmarks';
+import MyReviews from './Reviews/MyReviews';
+import UserInfo from './UserInfo/UserInfo';
+import DeleteUserBtn from './UI/deleteUserBtn';
+import { User } from './assets/types/User';
+import { getCookie } from '../../utils/cookie';
+
+const MyPage = () => {
+ //TODO: hook으로 분리
+ const token = getCookie('jwtToken');
+ const [isLoading, setIsLoading] = useState(true);
+ // const [username, setName] = useState('name');
+ const [reviews, setReview] = useState([]);
+ const [reviewCounter, setReviewCounter] = useState(0);
+
+ const [user, setUser] = useState({
+ username: 'name',
+ reviews: reviewCounter,
+ memberId: 0,
+ });
+
+ useEffect(() => {
+ const fetchInfo = async () => {
+ try {
+ const res = await axios.get(
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/members/mypage',
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ if (res.status === 200) {
+ const data = res.data;
+ // console.log(data);
+ // setName(data.username);
+ setUser({
+ username: data.username,
+ reviews: 0,
+ memberId: data.memberId,
+ });
+ setIsLoading(false);
+ // console.log(user);
+ } else {
+ console.log('failed to fetch UserInfo:', res.data);
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ fetchInfo();
+ }, []);
+ if (isLoading) {
+ return Loading...
;
+ }
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default MyPage;
diff --git a/client/src/pages/MyPage/Reviews/MyReviews.tsx b/client/src/pages/MyPage/Reviews/MyReviews.tsx
new file mode 100644
index 0000000..4cd6589
--- /dev/null
+++ b/client/src/pages/MyPage/Reviews/MyReviews.tsx
@@ -0,0 +1,66 @@
+import { useEffect, useState } from 'react';
+import ShortMovieList from '../UI/ShortMovieList';
+import axios from 'axios';
+import { getCookie } from '../../../utils/cookie';
+
+interface MyReviewProps {
+ reviews: any;
+ setReview: any;
+ reviewCounter: number;
+ setReviewCount: any;
+}
+
+const MyReviews = ({ reviews, setReview, reviewCounter, setReviewCount }: MyReviewProps) => {
+ // TODO: 페이지네이션 컴포넌트 만들고 페이지네이션으로 변경
+ const token = getCookie('jwtToken');
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchReviews = async () => {
+ try {
+ const res = await axios.get(
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/members/reviews?page=1',
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ if (res.status === 200) {
+ const response = res.data;
+ const data = response.data;
+ setReview(data);
+ setReviewCount(reviews.length);
+ console.log('data:', data);
+ console.log('reviews: ', reviews);
+ setIsLoading(false);
+ } else {
+ console.log('Failed to fetch review data:', res.data);
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ fetchReviews();
+ }, [reviewCounter]);
+
+ if (isLoading) {
+ return Loading...
;
+ }
+ return (
+
+
내가 작성한 리뷰
+
+ {reviewCounter > 0 ? (
+
+ ) : (
+
아직 작성한 리뷰가 없어요.
+ )}
+
+
+ );
+};
+
+export default MyReviews;
diff --git a/client/src/pages/MyPage/UI/ProfileImg.tsx b/client/src/pages/MyPage/UI/ProfileImg.tsx
new file mode 100644
index 0000000..2d2ffb3
--- /dev/null
+++ b/client/src/pages/MyPage/UI/ProfileImg.tsx
@@ -0,0 +1,13 @@
+interface props {
+ url: string;
+}
+
+const ProfileImg = ({ url }: props) => {
+ return (
+
+
+
+ );
+};
+
+export default ProfileImg;
diff --git a/client/src/pages/MyPage/UI/ShortMovieList.tsx b/client/src/pages/MyPage/UI/ShortMovieList.tsx
new file mode 100644
index 0000000..2e059b0
--- /dev/null
+++ b/client/src/pages/MyPage/UI/ShortMovieList.tsx
@@ -0,0 +1,36 @@
+import MoviePoster from '../../UI/MoivePoster';
+
+interface movieListProps {
+ movies: any;
+}
+
+interface MoviePosterProps {
+ movie: {
+ docId: string;
+ title: string;
+ releaseDate: string;
+ score: number;
+ bookmarked: boolean;
+ posters: string;
+ };
+}
+
+const shortMovieList: React.FC = ({ movies }) => {
+ return (
+
+ {movies.map((movie: MoviePosterProps, index: number) => (
+
+ ))}
+
+ );
+};
+
+export default shortMovieList;
diff --git a/client/src/pages/MyPage/UI/deleteUserBtn.tsx b/client/src/pages/MyPage/UI/deleteUserBtn.tsx
new file mode 100644
index 0000000..e4374a8
--- /dev/null
+++ b/client/src/pages/MyPage/UI/deleteUserBtn.tsx
@@ -0,0 +1,36 @@
+import axios from 'axios';
+
+interface DeleteBtnProps {
+ id: number;
+}
+
+const DeleteUserBtn = ({ id }: DeleteBtnProps) => {
+ const deleteUser = async () => {
+ try {
+ const res = await axios.delete(
+ `http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/members/${id}`
+ );
+ if (res.status === 200) {
+ console.log('user deleted');
+ } else {
+ console.log('failed to delete user:', res);
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ const handleBtnClick = () => {
+ deleteUser();
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default DeleteUserBtn;
diff --git a/client/src/pages/MyPage/UserInfo/InfoEditModal.tsx b/client/src/pages/MyPage/UserInfo/InfoEditModal.tsx
new file mode 100644
index 0000000..7b943e1
--- /dev/null
+++ b/client/src/pages/MyPage/UserInfo/InfoEditModal.tsx
@@ -0,0 +1,86 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+
+interface InfoProps {
+ setModalOpen: any;
+ username: string;
+ password: string;
+ imgUrl: string;
+}
+
+const InfoEditModal = ({ setModalOpen, username, password, imgUrl }: InfoProps) => {
+ const [name, setName] = useState(username);
+ const [pw, setPw] = useState(password);
+ const [img, setImgUrl] = useState(imgUrl);
+
+ const cancelChange = () => {
+ setName(username);
+ setPw(password);
+ setImgUrl(imgUrl);
+ setModalOpen(false);
+ };
+
+ const handleNameChange = (e: React.ChangeEvent) => {
+ setName(e.target.value);
+ };
+
+ const handlePwChange = (e: React.ChangeEvent) => {
+ setPw(e.target.value);
+ };
+
+ const submitChange = () => {
+ // post 요청 보내기
+ axios({
+ method: 'post',
+ url: '/',
+ data: {
+ username: name,
+ password: pw,
+ image: img,
+ },
+ })
+ .then((res) => {
+ console.log(res);
+ console.log('수정 완료');
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ };
+
+ return (
+
+
정보 수정
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default InfoEditModal;
diff --git a/client/src/pages/MyPage/UserInfo/UserInfo.tsx b/client/src/pages/MyPage/UserInfo/UserInfo.tsx
new file mode 100644
index 0000000..25af082
--- /dev/null
+++ b/client/src/pages/MyPage/UserInfo/UserInfo.tsx
@@ -0,0 +1,56 @@
+import { useCallback, useState } from 'react';
+import { User } from '../assets/types/User';
+import ProfileImg from '../UI/ProfileImg';
+// import InfoEditModal from './InfoEditModal';
+// import axios from 'axios';
+
+//테스트용 유저 정보
+// const user: User = {
+// username: 'Chunsik',
+// password: '1234',
+// image: 'https://media.tenor.com/DtO_BhH5NUAAAAAC/chunsik-%EC%B6%98%EC%8B%9D.gif',
+// reviews: 4,
+// };
+
+interface userInfoProps {
+ info: User;
+}
+
+const UserInfo = ({ info }: userInfoProps) => {
+ const [isModalOpen, setModalOpen] = useState(false);
+ const openInfoEditModal = useCallback(() => {
+ setModalOpen(!isModalOpen);
+ }, []);
+
+ return (
+
+
+
+
+
+
{info.username}
+
+
+
{`작성한 리뷰: ${info.reviews}개`}
+
+
+ {/*
+ {isModalOpen && (
+
+ )}
+
*/}
+
+ );
+};
+
+export default UserInfo;
diff --git a/client/src/pages/MyPage/assets/api/MyBookmarkApi.ts b/client/src/pages/MyPage/assets/api/MyBookmarkApi.ts
new file mode 100644
index 0000000..2b686c1
--- /dev/null
+++ b/client/src/pages/MyPage/assets/api/MyBookmarkApi.ts
@@ -0,0 +1,12 @@
+// import axios from 'axios';
+import API from './axiosinstance';
+import { BookmarkDataResponse } from '../types/User';
+
+export async function getMyBookmarks(id: number) {
+ try {
+ const response = await API.get(`/members/${id}/reviews`);
+ return response.data;
+ } catch {
+ console.log('404 error');
+ }
+}
diff --git a/client/src/pages/MyPage/assets/api/axiosinstance.ts b/client/src/pages/MyPage/assets/api/axiosinstance.ts
new file mode 100644
index 0000000..9281467
--- /dev/null
+++ b/client/src/pages/MyPage/assets/api/axiosinstance.ts
@@ -0,0 +1,15 @@
+import axios from 'axios';
+
+// const API = axios.create({
+// baseURL: import.meta.env.REACT_APP_API_URL,
+// });
+
+const API = axios.create({
+ baseURL: 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ withCredentials: true,
+});
+
+export default API;
diff --git a/client/src/pages/MyPage/assets/types/User.ts b/client/src/pages/MyPage/assets/types/User.ts
new file mode 100644
index 0000000..9279844
--- /dev/null
+++ b/client/src/pages/MyPage/assets/types/User.ts
@@ -0,0 +1,27 @@
+export interface User {
+ username: string;
+ reviews: number;
+ memberId: number;
+}
+
+export interface UserInfoRes {
+ username: string;
+ profile_Img: string;
+ memberId: number;
+}
+
+export interface BookmarkDataResponse {
+ movie: {
+ docId: number;
+ title: string;
+ repRlsDate: string;
+ posterUrl: string;
+ };
+ rev: {
+ score: number;
+ username: string;
+ content: string;
+ tags: string[];
+ like: number;
+ };
+}
diff --git a/client/src/pages/RedirectPage/RedirectPage.tsx b/client/src/pages/RedirectPage/RedirectPage.tsx
new file mode 100644
index 0000000..8fa8bb4
--- /dev/null
+++ b/client/src/pages/RedirectPage/RedirectPage.tsx
@@ -0,0 +1,35 @@
+import axios from 'axios';
+import { useEffect } from 'react';
+import Spinner from '../../components/Spinner';
+
+//카카오로그인 리다이렉트 페이지라고 가정, 인가코드 받아서 서버에 넘기고 엑세스 토큰 받아오는 부분
+const RedirectPage = () => {
+ const getAccessToken = async (kakaoAuthCode: string): Promise => {
+ try {
+ const response = await axios.post('http://localhost:3000/login', { kakaoAuthCode }); //추후 주소 변경
+ const accessToken = response.data.accessToken;
+ // const user = response.data.user;
+ // console.log(user);
+ localStorage.setItem('AC_Token', accessToken);
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ useEffect(() => {
+ const kakaoAuthCode = new URL(window.location.href).searchParams.get('code');
+ // console.log('코드잘오니?:', kakaoAuthCode); 잘 오는 거 확인! 구현 완료 후 삭제
+ if (kakaoAuthCode) {
+ getAccessToken(kakaoAuthCode);
+ }
+ }, []);
+
+ return (
+
+ );
+};
+
+export default RedirectPage;
diff --git a/client/src/pages/SearchPage/SearchPage.tsx b/client/src/pages/SearchPage/SearchPage.tsx
new file mode 100644
index 0000000..c67ed32
--- /dev/null
+++ b/client/src/pages/SearchPage/SearchPage.tsx
@@ -0,0 +1,109 @@
+import api from '../DetailsPage/assets/api/axiosInstance';
+import { useSearchParams } from 'react-router-dom';
+import { useEffect, useState } from 'react';
+import { Recommend } from '../DetailsPage/assets/types/movieTypes';
+import Spinner from '../../components/Spinner';
+import ErrorPage from '../ErrorPage/ErrorPage';
+import MoviePoster from '../UI/MoivePoster';
+import searchErrImg from './assets/searchErr.png';
+
+const SearchPage = () => {
+ const [searchData, setSearchData] = useState([]);
+ const [highScoreData, setHighScoreData] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isError, setIsError] = useState(false);
+
+ const [searchParams] = useSearchParams();
+ const keyword = searchParams.get('keyword');
+
+ // 해당 검색데이터 get 요청 // 예상 endpoint: `/search?keyword=${keyword}`
+ // 목업 데이터 `/mockupdata/searchdata.json`
+ useEffect(() => {
+ const fetchSearchData = async () => {
+ try {
+ const searchResponse = await api.get(`/search?keyword=${keyword}`);
+
+ setSearchData(searchResponse.data.movie);
+ setHighScoreData(searchResponse.data.recommended_movies);
+ setIsLoading(false);
+ } catch (err) {
+ console.error(err);
+ setIsError(true);
+ setIsLoading(false);
+ }
+ };
+ fetchSearchData();
+ }, [keyword]);
+
+ return (
+ <>
+ {isError ? (
+
+ ) : isLoading ? (
+
+
+
+ ) : (
+
+
"{keyword}"
+ {/* 검색결과가 없을때 */}
+ {searchData.length === 0 ? (
+ <>
+
+
+
+
이런영화는 어떠세요?
+
+ {highScoreData.map((movie, index) => (
+
+ ))}
+
+ >
+ ) : (
+ <>
+ {/* 검색결과가 있을때 */}
+
검색 결과
+
+ {searchData.map((movie, index) => (
+
+ ))}
+
+
이런영화는 어떠세요?
+
+ {highScoreData.map((movie, index) => (
+
+ ))}
+
+ >
+ )}
+
+ )}
+ >
+ );
+};
+
+export default SearchPage;
diff --git a/client/src/pages/SearchPage/assets/searchErr.png b/client/src/pages/SearchPage/assets/searchErr.png
new file mode 100644
index 0000000..514b27b
Binary files /dev/null and b/client/src/pages/SearchPage/assets/searchErr.png differ
diff --git a/client/src/pages/SignupPage/SignupPage.tsx b/client/src/pages/SignupPage/SignupPage.tsx
index 3f2e3c5..c425a70 100644
--- a/client/src/pages/SignupPage/SignupPage.tsx
+++ b/client/src/pages/SignupPage/SignupPage.tsx
@@ -1,12 +1,14 @@
import { useState, useEffect } from 'react';
+import { isAxiosError } from 'axios';
import { useNavigate, Link } from 'react-router-dom';
-import axios from 'axios';
+import { emailValidationRegex, pwValidationRegex } from '../../constants/constants';
import SocialLogin from '../../components/SocialLogin';
+import api from '../../utils/api';
const SignupPage: React.FC = () => {
- const [displayName, setDisplayName] = useState('');
- const [displayNameValid, setDisplayNameValid] = useState(false);
- const [displayNameError, setDisplayNameError] = useState('');
+ const [userName, setuserName] = useState('');
+ const [userNameValid, setuserNameValid] = useState(false);
+ const [userNameError, setuserNameError] = useState('');
const [userEmail, setUserEmail] = useState('');
const [userEmailValid, setUserEmailValid] = useState(false);
@@ -22,29 +24,24 @@ const SignupPage: React.FC = () => {
const navigate = useNavigate();
- const URL = '';
-
useEffect(() => {
- if (displayName.length === 0) {
- setDisplayNameValid(false);
- setDisplayNameError('');
- } else if (displayName.length >= 2) {
- setDisplayNameValid(true);
- setDisplayNameError('');
+ if (userName.length === 0) {
+ setuserNameValid(false);
+ setuserNameError('');
+ } else if (userName.length >= 2) {
+ setuserNameValid(true);
+ setuserNameError('');
} else {
- setDisplayNameValid(false);
- setDisplayNameError('이름은 2자 이상이어야 합니다.');
+ setuserNameValid(false);
+ setuserNameError('이름은 2자 이상이어야 합니다.');
}
- }, [displayName]);
+ }, [userName]);
useEffect(() => {
- const regex1 =
- /^(([^<>()\\[\].,;:\s@"]+(\.[^<>()\\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
-
if (userEmail.length === 0) {
setUserEmailValid(false);
setUserEmailError('');
- } else if (regex1.test(userEmail)) {
+ } else if (emailValidationRegex.test(userEmail)) {
setUserEmailValid(true);
setUserEmailError('');
} else {
@@ -54,12 +51,10 @@ const SignupPage: React.FC = () => {
}, [userEmail]);
useEffect(() => {
- const regex2 = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
-
if (userPassword.length === 0) {
setUserPasswordValid(false);
setUserPasswordError('');
- } else if (regex2.test(userPassword)) {
+ } else if (pwValidationRegex.test(userPassword)) {
setUserPasswordValid(true);
setUserPasswordError('');
} else {
@@ -81,55 +76,64 @@ const SignupPage: React.FC = () => {
}
}, [confirmPassword, userPassword]);
- const isValid = userEmailValid && displayNameValid && userPasswordValid && confirmPasswordValid;
+ const isValid = userEmailValid && userNameValid && userPasswordValid && confirmPasswordValid;
+
+ const handleNameChange = (e: React.ChangeEvent) => {
+ setuserName(e.target.value);
+ };
+
+ const handleEmailChange = (e: React.ChangeEvent) => {
+ setUserEmail(e.target.value);
+ };
- const signupOnClickHandler = async (e: React.FormEvent) => {
+ const handlePasswordChange = (e: React.ChangeEvent) => {
+ setUserPassword(e.target.value);
+ };
+
+ const handleConfirmPasswordChange = (e: React.ChangeEvent) => {
+ setConfirmPassword(e.target.value);
+ };
+
+ const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
if (isValid) {
try {
- const response = await axios.post(
- `${URL}/signup`, //api 나오는 대로 변경
- JSON.stringify({
- name: displayName,
- email: userEmail,
- password: userPassword,
- }),
- {
- headers: {
- 'Content-Type': 'application/json',
- 'Access-Control-Allow-Origin': '*',
- withCredentials: true,
- },
- }
- );
-
+ const response = await api.post('/members', {
+ email: userEmail,
+ username: userName,
+ password: userPassword,
+ });
console.log(response.headers);
window.alert('회원가입에 성공하였습니다.');
navigate('/login');
} catch (err) {
- console.log(err);
- window.alert('회원가입에 실패하였습니다.');
+ if (isAxiosError(err) && err.response?.status === 409) {
+ window.alert('이미 사용 중인 이름 또는 이메일입니다.');
+ } else {
+ console.log(err);
+ window.alert('회원가입에 실패하였습니다.');
+ }
}
}
};
return (
-
-
- LOGO
- 회원가입
+
+
MovieLog
+
회원가입
+
setDisplayName(e.target.value)}
- className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-darkGray focus:outline-none"
+ value={userName}
+ onChange={handleNameChange}
+ className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-mainblack focus:outline-none"
/>
- {displayNameError &&
{displayNameError}
}
+ {userNameError &&
{userNameError}
}
@@ -138,8 +142,8 @@ const SignupPage: React.FC = () => {
id="userEmail"
placeholder="이메일 입력"
value={userEmail}
- onChange={(e) => setUserEmail(e.target.value)}
- className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-darkGray focus:outline-none"
+ onChange={handleEmailChange}
+ className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-mainblack focus:outline-none"
/>
{userEmailError &&
{userEmailError}
}
@@ -150,8 +154,8 @@ const SignupPage: React.FC = () => {
id="userPassword"
placeholder="비밀번호 입력"
value={userPassword}
- onChange={(e) => setUserPassword(e.target.value)}
- className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-darkGray focus:outline-none"
+ onChange={handlePasswordChange}
+ className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-mainblack focus:outline-none"
/>
= 8 ? 'text-darkBlue' : 'text-zinc-400'}`}>
@@ -178,8 +182,8 @@ const SignupPage: React.FC = () => {
id="confirmPassword"
placeholder="비밀번호 확인"
value={confirmPassword}
- onChange={(e) => setConfirmPassword(e.target.value)}
- className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-darkGray focus:outline-none"
+ onChange={handleConfirmPasswordChange}
+ className="w-full border-2 border-zinc-300 px-1 py-2 focus:border-b-2 focus:border-mainblack focus:outline-none"
/>
{
-
-
-
+
+
+
);
};
diff --git a/client/src/pages/UI/BoxOffice.tsx b/client/src/pages/UI/BoxOffice.tsx
new file mode 100644
index 0000000..b1def9d
--- /dev/null
+++ b/client/src/pages/UI/BoxOffice.tsx
@@ -0,0 +1,88 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import MoviePoster from './MoivePoster';
+
+interface Movie {
+ title: string;
+ docId: string;
+ repRlsDate: string;
+ score: number;
+ bookmarked: boolean;
+ posterUrl: string;
+}
+
+const BoxOfficeMovies: React.FC = () => {
+ const [showAllMovies, setShowAllMovies] = useState(false);
+
+ const [movies, setMovies] = useState
([]);
+
+ useEffect(() => {
+ const fetchMovies = async () => {
+ try {
+ const response = await axios.get(
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/main'
+ ); //엔드포인트
+
+ if (response.status === 200) {
+ const data = response.data;
+ const BoxOfficeTop5 = data.boxOffice;
+ setMovies(BoxOfficeTop5);
+ console.log(BoxOfficeTop5);
+ } else {
+ console.log('Failed to fetch movies:', response.data);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ }
+ };
+
+ fetchMovies();
+ }, []);
+
+ const handleShowMoreMovies = () => {
+ setShowAllMovies(!showAllMovies);
+ };
+
+ const renderMovies = () => {
+ const moviesToRender = showAllMovies ? movies : movies.slice(0, 5);
+ return (
+
+
+ 박스오피스 순위{' '}
+ {showAllMovies ? (
+
+ ) : (
+ <>>
+ )}
+
+
+ {moviesToRender.map((movie, index) => (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderMovies()}
+ {!showAllMovies && movies.length > 5 && (
+
+ )}
+
+ );
+};
+export default BoxOfficeMovies;
diff --git a/client/src/pages/UI/CategoryModal.tsx b/client/src/pages/UI/CategoryModal.tsx
new file mode 100644
index 0000000..18ffb17
--- /dev/null
+++ b/client/src/pages/UI/CategoryModal.tsx
@@ -0,0 +1,98 @@
+import { useState } from 'react';
+
+interface ModalProps {
+ onClose: () => void;
+ genre: 'string';
+ tag: 'string';
+}
+
+const CategoryModal: React.FC = ({ onClose }) => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const genres = [
+ '액션',
+ '범죄',
+ '느와르',
+ '드라마',
+ '로맨스',
+ '판타지',
+ 'SF',
+ '재난',
+ '코메디',
+ '공포',
+ ];
+ const tags = [
+ '감동',
+ '음악',
+ '힐링',
+ '킬링타임',
+ '모험',
+ '창의적',
+ '영상미',
+ '영감',
+ '긴장감',
+ '반전',
+ ];
+
+ const closeModal = () => {
+ setIsOpen(false);
+ onClose();
+ };
+ if (!isOpen) {
+ return null;
+ }
+
+ const handleGenreClick = (genre: string) => {
+ console.log(genre);
+ const queryString = `?genre=${genre}`;
+ console.log(queryString);
+
+ window.location.href = `/category/genre${queryString}`;
+ };
+
+ const handleTagClick = (tag: string) => {
+ console.log(tag);
+ const queryString = `?tag=${tag}`;
+ window.location.href = `/category/tag${queryString}`;
+ console.log(queryString);
+ };
+ console.log('selectedTag : ');
+
+ return (
+
+
+ 장르별
+ {genres.map((genre, index) => (
+
+ ))}
+
+
+ #태그별
+ {tags.map((tag, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default CategoryModal;
diff --git a/client/src/pages/UI/Footer.tsx b/client/src/pages/UI/Footer.tsx
index e69de29..0e9dbda 100644
--- a/client/src/pages/UI/Footer.tsx
+++ b/client/src/pages/UI/Footer.tsx
@@ -0,0 +1,13 @@
+const Footer = () => {
+ return (
+
+
MovieLog
+
Made by
+
+
FE 동빈, 윤경, 유빈, 승현
+
BE 어진, 세빈, 성재
+
+ );
+};
+
+export default Footer;
diff --git a/client/src/pages/UI/Header.tsx b/client/src/pages/UI/Header.tsx
index c30d32e..a282e6f 100644
--- a/client/src/pages/UI/Header.tsx
+++ b/client/src/pages/UI/Header.tsx
@@ -1,38 +1,98 @@
import { BsSearch } from 'react-icons/bs';
-
-// interface HeaderProps {
-// search: string;
-// }
-// const Header: React.FC = ({ search }) => {
+import { Link, useNavigate } from 'react-router-dom';
+import { useState } from 'react';
+import CategoryModal from './CategoryModal';
+import { getCookie } from '../../utils/cookie';
+import LogoutButton from '../../components/LogoutButton';
const Header = () => {
+ // 검색인풋 제출
+ const navigate = useNavigate();
+ const [searchInput, setSearchInput] = useState('');
+ const [isCategoryOpen, setisCategoryOpen] = useState(false);
+
+ const handleSearchInputChange = (e: React.ChangeEvent) => {
+ setSearchInput(e.target.value);
+ };
+ const handleSearchInputSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (searchInput.length > 1) {
+ navigate(`/search?keyword=${searchInput}`);
+ } else {
+ alert('검색어는 최소 두 글자 이상 입력해주세요.');
+ }
+ };
+
+ const OpenCategoryModal = () => {
+ setisCategoryOpen(!isCategoryOpen);
+ Boolean(getCookie('jwtToken'));
+ console.log(getCookie('jwtToken'));
+ console.log(document.cookie);
+ };
+
+ const CloseCategoryModal = () => {
+ setisCategoryOpen(false);
+ };
+
return (
-
+
-
Logo
-
Categories
+
+ MovieLog
+
+
+
+ {isCategoryOpen && (
+
+ )}
+
-
+
-
-
- Login
-
-
- Sign Up
-
+
+ {!getCookie('jwtToken') ? (
+
+
+ Login
+
+
+ Sign Up
+
+
+ ) : (
+
+ )}
);
};
-
export default Header;
diff --git a/client/src/pages/UI/HighRatings.tsx b/client/src/pages/UI/HighRatings.tsx
new file mode 100644
index 0000000..2c80b98
--- /dev/null
+++ b/client/src/pages/UI/HighRatings.tsx
@@ -0,0 +1,89 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import MoviePoster from './MoivePoster';
+
+interface Movie {
+ title: string;
+ docId: string;
+ repRlsDate: string;
+ score: number;
+ bookmarked: boolean;
+ posterUrl: string;
+}
+
+const HighRatings: React.FC = () => {
+ const [showAllMovies, setShowAllMovies] = useState(false);
+
+ const [movies, setMovies] = useState
([]);
+
+ useEffect(() => {
+ const fetchMovies = async () => {
+ try {
+ const response = await axios.get(
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/main'
+ ); //엔드포인트
+
+ if (response.status === 200) {
+ const data = response.data;
+ const HighRatingTop5 = data.topScore;
+ setMovies(HighRatingTop5);
+ console.log(HighRatingTop5);
+ } else {
+ console.log('Failed to fetch movies:', response.data);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ }
+ };
+
+ fetchMovies();
+ }, []);
+
+ const handleShowMoreMovies = () => {
+ setShowAllMovies(!showAllMovies);
+ };
+
+ const renderMovies = () => {
+ const moviesToRender = showAllMovies ? movies : movies.slice(0, 5);
+
+ return (
+
+
+ 별점 높은 순{' '}
+ {showAllMovies ? (
+
+ 더보기 접기
+
+ ) : (
+ <>>
+ )}
+
+
+ {moviesToRender.map((movie, index) => (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderMovies()}
+ {!showAllMovies && movies.length > 5 && (
+
+ 관련 영화 더보기
+
+ )}
+
+ );
+};
+export default HighRatings;
diff --git a/client/src/pages/UI/HightReviewCount.tsx b/client/src/pages/UI/HightReviewCount.tsx
new file mode 100644
index 0000000..af740bb
--- /dev/null
+++ b/client/src/pages/UI/HightReviewCount.tsx
@@ -0,0 +1,89 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import MoviePoster from './MoivePoster';
+
+interface Movie {
+ title: string;
+ docId: string;
+ repRlsDate: string;
+ score: number;
+ bookmarked: boolean;
+ posterUrl: string;
+}
+
+const HighReviewCount: React.FC = () => {
+ const [showAllMovies, setShowAllMovies] = useState(false);
+
+ const [movies, setMovies] = useState([]);
+
+ useEffect(() => {
+ const fetchMovies = async () => {
+ try {
+ const response = await axios.get(
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/main'
+ ); //엔드포인트
+
+ if (response.status === 200) {
+ const data = response.data;
+ const mostReviewTop5 = data.mostReview;
+ setMovies(mostReviewTop5);
+ console.log(mostReviewTop5);
+ } else {
+ console.log('Failed to fetch movies:', response.data);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ }
+ };
+
+ fetchMovies();
+ }, []);
+
+ const handleShowMoreMovies = () => {
+ setShowAllMovies(!showAllMovies);
+ };
+
+ const renderMovies = () => {
+ const moviesToRender = showAllMovies ? movies : movies.slice(0, 5);
+
+ return (
+
+
+ 리뷰 많은 순{' '}
+ {showAllMovies ? (
+
+ 더보기 접기
+
+ ) : (
+ <>>
+ )}
+
+
+ {moviesToRender.map((movie, index) => (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderMovies()}
+ {!showAllMovies && movies.length > 5 && (
+
+ 관련 영화 더보기
+
+ )}
+
+ );
+};
+export default HighReviewCount;
diff --git a/client/src/pages/UI/MoivePoster.tsx b/client/src/pages/UI/MoivePoster.tsx
index 16d2a41..8539ed5 100644
--- a/client/src/pages/UI/MoivePoster.tsx
+++ b/client/src/pages/UI/MoivePoster.tsx
@@ -1,47 +1,48 @@
import React, { useState } from 'react';
import { BsStarFill, BsStarHalf, BsStar } from 'react-icons/bs';
+import { Link } from 'react-router-dom';
interface MoviePosterProps {
+ movieId: string;
title: string;
releaseDate: string;
- rating: number;
+ score: number;
bookmarked: boolean;
posterUrl: string;
}
const MoviePoster: React.FC = ({
+ movieId,
title,
releaseDate,
- rating,
+ score,
bookmarked,
posterUrl,
}) => {
- const year = releaseDate.substring(releaseDate.length - 4, releaseDate.length); // 연도만 추출
+ // const year = releaseDate.substring(0, 4); // 연도만 추출(releaseDate.length - 4, releaseDate.length);
const [isBookmarked, setIsBookmarked] = useState(bookmarked);
const handleBookmarkToggle = () => {
setIsBookmarked((prevBookmarked) => !prevBookmarked);
};
- console.log(isBookmarked);
- const [hoverRating, setHoverRating] = useState(null);
+ const [hoverscore, setHoverscore] = useState(null);
const handleMouseEnter = (index: number) => {
- setHoverRating(index + 1);
+ setHoverscore(index + 1);
};
const handleMouseLeave = () => {
- setHoverRating(null);
+ setHoverscore(null);
};
- const renderRatingStars = () => {
- const roundedRating = Math.floor(rating / 2); // 9 => 9 /2 => 4.5
+ const renderscoreStars = () => {
+ const roundedscore = Math.floor(score); // 9 => 9 /2 => 4.5
const stars = [];
- console.log(roundedRating);
for (let i = 0; i < 5; i++) {
- if (i < roundedRating) {
- //roundedrating = 8
+ if (i < roundedscore) {
+ //roundedscore = 8
stars.push(
= ({
onMouseLeave={handleMouseLeave}
/>
);
- } else if (i === roundedRating && (rating / 2) % 1 >= 0.5) {
+ } else if (i === roundedscore && score % 1 >= 0.5) {
//0.5 있으니 반별추가...
stars.push(
= ({
stars.push(
handleMouseEnter(i)}
onMouseLeave={handleMouseLeave}
@@ -78,24 +79,33 @@ const MoviePoster: React.FC = ({
return stars;
};
+ const handleClick = () => {
+ window.scrollTo(0, 0);
+ };
+
return (
-
-
-
- {isBookmarked ? (
-
- ) : (
-
- )}
-
+
+
+
+
+
-
-
- {renderRatingStars()}
- {hoverRating &&
{rating}}
+
+ {isBookmarked ? (
+
+ ) : (
+
+ )}
+
+
+
+
{title}
+
{releaseDate}
+
+
+ {renderscoreStars()}
+ {hoverscore && {score}}
+
);
diff --git a/client/src/pages/UI/MovieGallery.tsx b/client/src/pages/UI/MovieGallery.tsx
deleted file mode 100644
index 45884e2..0000000
--- a/client/src/pages/UI/MovieGallery.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import MoviePoster from './MoivePoster';
-
-interface Category {
- name: string;
- movies: Movie[];
-}
-
-interface Movie {
- title: string;
- releaseDate: string;
- rating: number;
- bookmarked: boolean;
- posterUrl: string;
-}
-
-interface MovieGalleryProps {
- categories: Category[];
-}
-
-const MovieGallery: React.FC
= ({ categories }) => {
- const sortByRatingDescending = (movies: Movie[]): Movie[] => {
- return movies.sort((a, b) => b.rating - a.rating);
- };
-
- return (
-
- {categories.map((category) => {
- const sortedMovies = sortByRatingDescending(category.movies);
-
- return (
-
-
{category.name}
-
- {sortedMovies.slice(0, 5).map((movie) => (
-
- ))}
-
-
- );
- })}
-
- );
-};
-
-export default MovieGallery;
diff --git a/client/src/pages/UI/MovieList.tsx b/client/src/pages/UI/MovieList.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/pages/UI/MovieRecommend.tsx b/client/src/pages/UI/MovieRecommend.tsx
new file mode 100644
index 0000000..9b6d25e
--- /dev/null
+++ b/client/src/pages/UI/MovieRecommend.tsx
@@ -0,0 +1,89 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import MoviePoster from './MoivePoster';
+
+interface Movie {
+ title: string;
+ docId: string;
+ repRlsDate: string;
+ score: number;
+ bookmarked: boolean;
+ posterUrl: string;
+}
+
+const MovieRecommend: React.FC = () => {
+ const [showAllMovies, setShowAllMovies] = useState(false);
+
+ const [movies, setMovies] = useState([]);
+
+ useEffect(() => {
+ const fetchMovies = async () => {
+ try {
+ const response = await axios.get(
+ 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080/main'
+ ); //엔드포인트
+
+ if (response.status === 200) {
+ const data = response.data;
+ const MovieRecommendTop5 = data.topScore;
+ setMovies(MovieRecommendTop5);
+ console.log(MovieRecommendTop5);
+ } else {
+ console.log('Failed to fetch movies:', response.data);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ }
+ };
+
+ fetchMovies();
+ }, []);
+
+ const handleShowMoreMovies = () => {
+ setShowAllMovies(!showAllMovies);
+ };
+
+ const renderMovies = () => {
+ const moviesToRender = showAllMovies ? movies : movies.slice(0, 5);
+
+ return (
+
+
+ 이런 영화는 어떠신가요?{' '}
+ {showAllMovies ? (
+
+ 더보기 접기
+
+ ) : (
+ <>>
+ )}
+
+
+ {moviesToRender.map((movie, index) => (
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderMovies()}
+ {!showAllMovies && movies.length > 5 && (
+
+ 관련 영화 더보기
+
+ )}
+
+ );
+};
+export default MovieRecommend;
diff --git a/client/src/pages/UI/datalist.tsx b/client/src/pages/UI/datalist.tsx
new file mode 100644
index 0000000..5b27326
--- /dev/null
+++ b/client/src/pages/UI/datalist.tsx
@@ -0,0 +1,127 @@
+export const movies = [
+ {
+ title: '여기는 영화 이름',
+ genre: ['Action', 'Thriller'],
+ tags: ['Tag 1', 'Tag 2'],
+ releaseDate: '2022-01-01',
+ score: 4.5,
+ bookmarked: false,
+ posterUrl:
+ 'https://i.namu.wiki/i/_d6Ra_qW5y99EdBtAD3liIEitpayAFxeRpCt2wD_W6rfdt1VQ9sDT9S-838rihfsKn8VMh5P3rq1QQT50emB9g.webp',
+ review_count: 7, //or[ ]이라면 {id}.review_count.length
+ },
+ {
+ title: 'Movie 2',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://i.namu.wiki/i/_d6Ra_qW5y99EdBtAD3liIEitpayAFxeRpCt2wD_W6rfdt1VQ9sDT9S-838rihfsKn8VMh5P3rq1QQT50emB9g.webp',
+
+ review_count: 7,
+ },
+
+ {
+ title: 'Movie 3',
+ genre: ['Action', 'Thriller'],
+ tags: ['Tag 1', 'Tag 2'],
+ releaseDate: '2022-01-01',
+ score: 4.5,
+ bookmarked: false,
+ posterUrl:
+ 'https://i.namu.wiki/i/_d6Ra_qW5y99EdBtAD3liIEitpayAFxeRpCt2wD_W6rfdt1VQ9sDT9S-838rihfsKn8VMh5P3rq1QQT50emB9g.webp',
+ review_count: 7,
+ },
+ {
+ title: 'Movie 4',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://upload.wikimedia.org/wikipedia/ko/0/00/%EB%8B%A4%ED%81%AC_%EB%82%98%EC%9D%B4%ED%8A%B8_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg',
+ review_count: 7,
+ },
+
+ {
+ title: 'Movie 5',
+ genre: ['Action', 'Thriller'],
+ tags: ['Tag 1', 'Tag 2'],
+ releaseDate: '2022-01-01',
+ score: 4.5,
+ bookmarked: false,
+ posterUrl:
+ 'https://i.namu.wiki/i/_d6Ra_qW5y99EdBtAD3liIEitpayAFxeRpCt2wD_W6rfdt1VQ9sDT9S-838rihfsKn8VMh5P3rq1QQT50emB9g.webp',
+ review_count: 5,
+ },
+ {
+ title: 'Movie 6',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://i.namu.wiki/i/_d6Ra_qW5y99EdBtAD3liIEitpayAFxeRpCt2wD_W6rfdt1VQ9sDT9S-838rihfsKn8VMh5P3rq1QQT50emB9g.webp',
+ review_count: 2,
+ },
+ {
+ title: 'Movie 7',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://i.namu.wiki/i/_d6Ra_qW5y99EdBtAD3liIEitpayAFxeRpCt2wD_W6rfdt1VQ9sDT9S-838rihfsKn8VMh5P3rq1QQT50emB9g.webp',
+ review_count: 1,
+ },
+ {
+ title: 'Movie 8',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://upload.wikimedia.org/wikipedia/ko/0/00/%EB%8B%A4%ED%81%AC_%EB%82%98%EC%9D%B4%ED%8A%B8_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg',
+ review_count: 10,
+ },
+ {
+ title: 'Movie 9',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://upload.wikimedia.org/wikipedia/ko/0/00/%EB%8B%A4%ED%81%AC_%EB%82%98%EC%9D%B4%ED%8A%B8_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg',
+ review_count: 11,
+ },
+ {
+ title: 'Movie 10',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://upload.wikimedia.org/wikipedia/ko/0/00/%EB%8B%A4%ED%81%AC_%EB%82%98%EC%9D%B4%ED%8A%B8_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg',
+ review_count: 99,
+ },
+ {
+ title: 'Movie 11',
+ genre: ['Drama'],
+ tags: ['Tag 3', 'Tag 4'],
+ releaseDate: '2022-02-01',
+ score: 4.2,
+ bookmarked: true,
+ posterUrl:
+ 'https://upload.wikimedia.org/wikipedia/ko/0/00/%EB%8B%A4%ED%81%AC_%EB%82%98%EC%9D%B4%ED%8A%B8_%ED%8F%AC%EC%8A%A4%ED%84%B0.jpg',
+ review_count: 777777,
+ },
+ // ...
+];
diff --git a/client/src/redux-toolkit/slices/movieDetailSlice.ts b/client/src/redux-toolkit/slices/movieDetailSlice.ts
index 4d4e825..85e285c 100644
--- a/client/src/redux-toolkit/slices/movieDetailSlice.ts
+++ b/client/src/redux-toolkit/slices/movieDetailSlice.ts
@@ -1,10 +1,10 @@
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from '../store';
-import { MovieDataResponse } from '../../pages/DetailsPage/assets/types/movieTypes';
+import { MovieDetailData } from '../../pages/DetailsPage/assets/types/movieTypes';
interface MovieDetailState {
- data: MovieDataResponse | null;
+ data: MovieDetailData | null;
}
const initialState: MovieDetailState = {
@@ -15,7 +15,7 @@ export const movieDetailSlice = createSlice({
name: 'movieDetail',
initialState,
reducers: {
- fetchMovieSuccess: (state, action: PayloadAction) => {
+ fetchMovieSuccess: (state, action: PayloadAction) => {
state.data = action.payload;
},
},
diff --git a/client/src/utils/Login/kakaoLogin.tsx b/client/src/utils/Login/kakaoLogin.tsx
new file mode 100644
index 0000000..605ba44
--- /dev/null
+++ b/client/src/utils/Login/kakaoLogin.tsx
@@ -0,0 +1,7 @@
+import { KAKAO_AUTH_URL } from '../../constants/constants';
+
+const kakaoLoginRequestHandler = () => {
+ return window.location.assign(KAKAO_AUTH_URL);
+};
+
+export default kakaoLoginRequestHandler;
diff --git a/client/src/utils/api.ts b/client/src/utils/api.ts
new file mode 100644
index 0000000..00185a7
--- /dev/null
+++ b/client/src/utils/api.ts
@@ -0,0 +1,11 @@
+import axios from 'axios';
+
+const api = axios.create({
+ baseURL: 'http://ec2-54-180-85-209.ap-northeast-2.compute.amazonaws.com:8080',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ withCredentials: true,
+});
+
+export default api;
diff --git a/client/src/utils/cookie.ts b/client/src/utils/cookie.ts
new file mode 100644
index 0000000..23e2dc4
--- /dev/null
+++ b/client/src/utils/cookie.ts
@@ -0,0 +1,16 @@
+import { Cookies } from 'react-cookie';
+import type { CookieSetOptions } from 'universal-cookie';
+
+const cookies = new Cookies();
+
+export const setCookie = (name: string, value: string, option?: CookieSetOptions) => {
+ return cookies.set(name, value, { ...option });
+};
+
+export const getCookie = (name: string): string => {
+ return cookies.get(name);
+};
+
+export const removeCookie = (name: string): void => {
+ cookies.remove(name);
+};
diff --git a/client/tailwind.config.js b/client/tailwind.config.js
index a40cab0..038e584 100644
--- a/client/tailwind.config.js
+++ b/client/tailwind.config.js
@@ -6,6 +6,7 @@ export default {
bp1: { max: '640px' },
bp2: { max: '817px' },
bp3: { max: '980px' },
+ bp4: { min: '640px' },
},
extend: {
colors: {