diff --git a/.idea/transcendence.iml b/.idea/transcendence.iml index 24643cc3..260d9929 100644 --- a/.idea/transcendence.iml +++ b/.idea/transcendence.iml @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/frontend/assets/css/common/header.css b/frontend/assets/css/common/header.css index b7a32344..942eadba 100644 --- a/frontend/assets/css/common/header.css +++ b/frontend/assets/css/common/header.css @@ -19,11 +19,70 @@ header { width: fit-content; } +.header-wrapper .histories#left-side, .header-wrapper .histories#right-side { + display: flex; + align-items: center; + flex-direction: row; + width: 30%; + height: 100%; +} + +.header-wrapper .histories#left-side { + justify-content: flex-start; +} + +.header-wrapper .histories#right-side { + justify-content: flex-end; +} + +.header-wrapper .histories#left-side > img { + width: 3rem; + height: 3rem; + margin-right: 10px; +} + +.header-wrapper .histories#search-box { + display: flex; + width: fit-content; + height: 100%; + justify-content: center; + align-items: center; +} + +.header-wrapper .histories#search-box > input { + width: 15rem; + height: 50%; + border-radius: 1rem; + border: 4px #FF79C5 solid; + background: none; + color: white; + font-size: 1.5rem; + padding: 0 1rem; + font-family: Galmuri11, system-ui; +} + .header-wrapper .histories#title { + text-align: center; color: #29ABE2; } -.header-wrapper .histories#close-button a { +.header-wrapper .histories#user-avatar { + position: relative; + width: 4rem; + height: 4rem; + border: 4px solid white; + overflow: hidden; + margin-right: 5rem; +} + +.header-wrapper .histories#user-avatar > img { + position: absolute; + width: 6rem; + height: 6rem; + border-radius: 50%; +} + +.header-wrapper .histories#close-button { display: block; background-image: url("../../images/close.png"); background-position: center; diff --git a/frontend/assets/css/histories.css b/frontend/assets/css/histories.css new file mode 100644 index 00000000..bc835500 --- /dev/null +++ b/frontend/assets/css/histories.css @@ -0,0 +1,150 @@ +.histories#content-wrapper { + display: flex; + justify-content: center; + flex-direction: column; + height: 100%; + background-color: black; + background-image: url("../images/histories_frame.png"); + background-size: 100% 100%; + background-position: bottom; + background-repeat: no-repeat; + overflow: hidden; +} + +/************* Histories List *************/ + +.histories#list { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 66.6vh; + color: white; + padding: 10vh 6vw 3vh 6vw; +} + +.histories#list-wrapper { + display:grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(2, 1fr); + justify-content: center; + justify-items: center; + align-items: center; + grid-gap: 1vh; + width: 80%; + height: 100%; +} + +.histories.list-item { + display: flex; + justify-content: center; + align-items: baseline; + flex-direction: row; + border: 3px transparent solid; + border-radius: 1vw; + text-decoration: none; + width: 80%; + padding: 1vh 1vw; +} + +.histories.list-item:visited, .histories.list-item:link, .histories.list-item:active { + color: white; +} + +.histories.list-item .player { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + width: 10vw; + border-radius: 1vh; +} + +.histories.list-item .player > div:not(:first-child) { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 50%; +} + +.histories.list-item .histories.avatar { + display: flex; + justify-content: center; + align-items: center; + position: relative; + width: 8vw; + height: 8vw; + overflow: hidden; +} + +.histories.list-item .histories.avatar > img { + position: absolute; + top: 0.1vw; + width: 12vw; + height: 12vw; +} + +.histories.list-item .player > div:not(:last-child) { + margin-bottom: 1vh; +} + +.histories.list-item .game-mode { + width: 20%; +} + +.histories.list-item .histories.nickname { + font-family: Galmuri11-Bold, serif; + font-size: 1.3em; +} + +.histories.list-item .histories.rating { + font-family: Galmuri11, serif; + font-size: 1.1em; + text-align: center; +} + +/********** Histories Pagination **********/ + +.histories#pagination, .histories#pagination div { + display: inline-block; + height: 6vh; + text-align: center; +} + +.histories#pagination > .histories img { + height: 100%; + margin: 0 0.5vw; +} + +.histories#pagination > .histories#prev img { + transform: rotate(180deg); +} + +.histories#pagination#next { +} + +/********** Histories Mode Select **********/ + +.histories#mode { + display: flex; + justify-content: space-evenly; + align-items: center; + flex-direction: row; + height: 15.5vh; + font-size: 2em; +} + +.histories#mode div { + display: flex; + justify-content: center; + align-items: center; + color: white; + font-family: Galmuri11, serif; + text-decoration: none; +} + +.histories#mode > div img { + width: 7vh; + margin-right: 2vh; +} diff --git a/frontend/assets/css/histories/one_on_one.css b/frontend/assets/css/histories/one_on_one.css deleted file mode 100644 index 0696a7b9..00000000 --- a/frontend/assets/css/histories/one_on_one.css +++ /dev/null @@ -1,12 +0,0 @@ -.histories#content-wrapper { - display: flex; - justify-content: center; - flex-direction: column; - height: 100%; - background-color: black; - background-image: url("../../images/histories_frame.png"); - background-size: 100% 100%; - background-position: bottom; - background-repeat: no-repeat; - overflow: hidden; -} diff --git a/frontend/assets/images/avatar/blue.png b/frontend/assets/images/avatar/blue.png new file mode 100644 index 00000000..686dd3f5 Binary files /dev/null and b/frontend/assets/images/avatar/blue.png differ diff --git a/frontend/assets/images/avatar/green.png b/frontend/assets/images/avatar/green.png new file mode 100644 index 00000000..f375ecdf Binary files /dev/null and b/frontend/assets/images/avatar/green.png differ diff --git a/frontend/assets/images/avatar/pink.png b/frontend/assets/images/avatar/pink.png new file mode 100644 index 00000000..a9484eb8 Binary files /dev/null and b/frontend/assets/images/avatar/pink.png differ diff --git a/frontend/assets/images/avatar/red.png b/frontend/assets/images/avatar/red.png new file mode 100644 index 00000000..fc19a984 Binary files /dev/null and b/frontend/assets/images/avatar/red.png differ diff --git a/frontend/assets/images/avatar/yellow.png b/frontend/assets/images/avatar/yellow.png new file mode 100644 index 00000000..e58041f3 Binary files /dev/null and b/frontend/assets/images/avatar/yellow.png differ diff --git a/frontend/assets/images/custom_summary.png b/frontend/assets/images/custom_summary.png new file mode 100644 index 00000000..0d4bc781 Binary files /dev/null and b/frontend/assets/images/custom_summary.png differ diff --git a/frontend/assets/images/pagination.png b/frontend/assets/images/pagination.png new file mode 100644 index 00000000..f96bb744 Binary files /dev/null and b/frontend/assets/images/pagination.png differ diff --git a/frontend/assets/images/search.png b/frontend/assets/images/search.png new file mode 100644 index 00000000..24b88a2e Binary files /dev/null and b/frontend/assets/images/search.png differ diff --git a/frontend/assets/images/setting.png b/frontend/assets/images/setting.png new file mode 100644 index 00000000..a08db117 Binary files /dev/null and b/frontend/assets/images/setting.png differ diff --git a/frontend/index.html b/frontend/index.html index f4cf5b29..e4f40278 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,12 +1,13 @@ - - - ping pong - - - + + + ping pong + + + + diff --git a/frontend/main.js b/frontend/main.js index 9227678e..3d2f513a 100644 --- a/frontend/main.js +++ b/frontend/main.js @@ -1,6 +1,8 @@ import App from "./src/app.js"; +import Header from "./src/header/header.js"; import { $ } from "./src/utils/querySelector.js"; window.addEventListener("DOMContentLoaded", (e) => { + new Header($("#header")); new App($("#app")); }); diff --git a/frontend/node_modules/.vite/deps/_metadata.json b/frontend/node_modules/.vite/deps/_metadata.json index 708fd277..0b637970 100644 --- a/frontend/node_modules/.vite/deps/_metadata.json +++ b/frontend/node_modules/.vite/deps/_metadata.json @@ -1,8 +1,8 @@ { - "hash": "e742593a", - "configHash": "1bafe92a", + "hash": "84ee4730", + "configHash": "d00396b8", "lockfileHash": "09cce371", - "browserHash": "97f0dd92", + "browserHash": "a49f98bc", "optimized": {}, "chunks": {} } \ No newline at end of file diff --git a/frontend/src/constants/routeInfo.js b/frontend/src/constants/routeInfo.js index 409d5c14..86427981 100644 --- a/frontend/src/constants/routeInfo.js +++ b/frontend/src/constants/routeInfo.js @@ -1,15 +1,16 @@ import GameMode from '../pages/game-mode/page.js'; import Main from '../pages/main/page.js'; import Nickname from '../pages/nickname/page.js'; -import histories from "../pages/histories/page.js"; +import Histories from "../pages/histories/page.js"; import errorHeader from '../header/errorHeader/header.js'; +import HistoriesHeader from "../header/historiesHeader.js"; export const BASE_URL = 'http://localhost:3000'; /** * 원하는 경로에 따라 렌더링할 컴포넌트를 정의합니다. */ export const routes = [ - { path: /^\/$/, page: Main, header: errorHeader }, - { path: /^\/nickname$/, page: Nickname, header: errorHeader }, - { path: /^\/gamemode$/, page: GameMode, header: errorHeader }, - {path: /^\/histories$/, page: histories, header: errorHeader} -] \ No newline at end of file + { path: /^\/$/, page: Main, header: errorHeader }, + { path: /^\/nickname$/, page: Nickname, header: errorHeader }, + { path: /^\/gamemode$/, page: GameMode, header: errorHeader }, + { path: /^\/histories$/, page: Histories, header: HistoriesHeader} +] diff --git a/frontend/src/header/header.js b/frontend/src/header/header.js new file mode 100644 index 00000000..9d92bcea --- /dev/null +++ b/frontend/src/header/header.js @@ -0,0 +1,29 @@ +import Router from "../router.js"; +import HistoriesHeader from "./historiesHeader.js"; + +/** + * header 컴포넌트 + * @param {HTMLElement} $container + */ +export default function header($container) { + this.$container = $container; + + const init = () => { + new Router($container); + }; + + const render = () => { + this.$container.innerHTML = ` + `; + } + + init(); + switch (location.pathname) { + case "/histories": + new HistoriesHeader($container); + break; + default: + render(); + break; + } +} \ No newline at end of file diff --git a/frontend/src/header/historiesHeader.js b/frontend/src/header/historiesHeader.js new file mode 100644 index 00000000..cb2522c0 --- /dev/null +++ b/frontend/src/header/historiesHeader.js @@ -0,0 +1,48 @@ +import {click} from "../utils/clickEvent.js"; +import {hoverChangeCursor} from "../utils/hoverEvent.js"; + +/** + * 사용자 전적 페이지에 사용하는 header 컴포넌트 + * @param {HTMLElement} $container + */ +export default function HistoriesHeader($container) { + this.$container = $container; + + this.setState = () => { + this.render(); + } + + this.render = () => { + this.$container.innerHTML = ` +
+
+ + +
+
너의 기록은
+
+
+ +
+
+
+
+ ` + } + + /** + * 헤더의 레이아웃에 이벤트 리스너를 추가합니다. + */ + this.addEventListenersToLayout = () => { + const $closeButton = document.getElementById("close-button"); + click($closeButton, function () { + console.log("전적 창 닫기"); + }); + hoverChangeCursor($closeButton, "pointer"); + } + + this.render(); + this.addEventListenersToLayout(); +} diff --git a/frontend/src/pages/histories/page.js b/frontend/src/pages/histories/page.js index 0192c81d..97f498a3 100644 --- a/frontend/src/pages/histories/page.js +++ b/frontend/src/pages/histories/page.js @@ -1,4 +1,7 @@ -export default function histories($container) { +import {hoverChangeBorder, hoverChangeColor, hoverChangeCursor, hoverChangeFont} from "../../utils/hoverEvent.js"; +import {click} from "../../utils/clickEvent.js"; + +export default function Histories($container) { this.$container = $container; this.setState = () => { @@ -6,17 +9,202 @@ export default function histories($container) { } this.render = () => { + this.renderLayout(); + this.renderList(); + } + + /** + * 헤더, 그리고 개요-사용자 지정 모드-토너먼트 모드를 선택할 수 있는 버튼들을 렌더링합니다. + * 이는 전적 리스트의 페이지의 레이아웃으로 추후 공통 모듈로 분리할 수 있습니다. + */ + this.renderLayout = () => { if (document.getElementsByTagName("head") !== null) { document.getElementsByTagName("head")[0].insertAdjacentHTML( "beforeend", - '' + '' ); } this.$container.innerHTML = ` -
d +
+
+ +
+
+ summary + 개요 +
+
+ custom-mode + 사용자 지정 모드 +
+
+ tournament + 토너먼트 모드 +
+
+
+ `; + } + + /** + * 사용자 지정 모드의 전적 리스트를 렌더링합니다. + * 1. 플레이어 1의 정보를 렌더링합니다. + * 2. 게임 모드(1 vs 1 로고 또는 토너먼트 로고)를 렌더링합니다. + * 3. 플레이어 2의 정보를 렌더링합니다. + */ + this.renderList = () => { + let list = document.getElementById("list"); + list.innerHTML = ` +
+ ` + let listWrapper = document.getElementById("list-wrapper"); + listWrapper.innerHTML = ''; + let mockData = [ + { + player1: { + nickname: "hyojocho", + avatar: "../../../assets/images/avatar/red.png", + rating: 2130, + is_winner: true, + }, + player2: { + nickname: "yena", + avatar: "../../../assets/images/avatar/blue.png", + rating: 110, + is_winner: false, + }, + }, + { + player1: { + nickname: "hyojocho", + avatar: "../../../assets/images/avatar/red.png", + rating: 2130, + is_winner: true, + }, + player2: { + nickname: "yena", + avatar: "../../../assets/images/avatar/blue.png", + rating: 110, + is_winner: false, + }, + }, + { + player1: { + nickname: "hyojocho", + avatar: "../../../assets/images/avatar/red.png", + rating: 2130, + is_winner: true, + }, + player2: { + nickname: "yena", + avatar: "../../../assets/images/avatar/blue.png", + rating: 110, + is_winner: false, + }, + }, + { + player1: { + nickname: "hyojocho", + avatar: "../../../assets/images/avatar/red.png", + rating: 2130, + is_winner: true, + }, + player2: { + nickname: "donghyk2", + avatar: "../../../assets/images/avatar/green.png", + rating: 2120, + is_winner: false, + }, + }, + ]; // TODO: 백엔드로부터 데이터 받아오기 + for (let data of mockData) { + const listItemDiv = document.createElement("div"); + listItemDiv.className = "histories list-item"; + this.renderPlayer(listItemDiv, data.player1); + this.renderGameMode(listItemDiv); + this.renderPlayer(listItemDiv, data.player2); + hoverChangeBorder(listItemDiv, "3px solid transparent", "3px solid #29ABE2"); + hoverChangeCursor(listItemDiv, "pointer"); + listItemDiv.addEventListener("click", () => { + console.log("TODO => 전적 상세 페이지로 이동") + }); + listWrapper.appendChild(listItemDiv); + } + } + + /** + * 전적 리스트의 플레이어 정보를 렌더링합니다. + * @param listItemDiv 전적 리스트의 플레이어 정보를 렌더링할 리스트 아이템
엘리먼트 + * @param data 전적 리스트의 플레이어 정보 + */ + this.renderPlayer = (listItemDiv, data) => { + const playerDiv = document.createElement("div"); + playerDiv.className = "histories player"; + playerDiv.innerHTML = ` +
+ player1-avatar
+
${data.nickname}
+
Rating: ${data.rating}
`; + listItemDiv.appendChild(playerDiv); + } + + /** + * 전적 리스트의 게임 모드(1 vs 1 로고 또는 토너먼트 로고)를 렌더링합니다. + * @param listItemDiv 전적 리스트의 게임 모드를 렌더링할 리스트 아이템
엘리먼트 + */ + this.renderGameMode = (listItemDiv) => { + const gameModeDiv = document.createElement("div"); + gameModeDiv.className = "histories game-mode"; + gameModeDiv.innerHTML = ` + 1v1 + `; + listItemDiv.appendChild(gameModeDiv); + } + + /** + * 레이아웃 엘리먼트에 이벤트 리스너를 추가합니다. + */ + this.addEventListenersToLayout = () => { + const summary = document.getElementById("summary"); + const custom = document.getElementById("custom"); + const tournament = document.getElementById("tournament"); + const prev = document.getElementById("prev"); + const next = document.getElementById("next"); + + // 폰트 색상 변경 + hoverChangeColor(summary, "#ffffff", "#29ABE2"); + hoverChangeColor(custom, "#ffffff", "#29ABE2"); + hoverChangeColor(tournament, "#ffffff", "#29ABE2"); + + // 폰트 변경 + hoverChangeFont(summary, "Galmuri11, serif", "Galmuri11-Bold, serif"); + hoverChangeFont(custom, "Galmuri11, serif", "Galmuri11-Bold, serif"); + hoverChangeFont(tournament, "Galmuri11, serif", "Galmuri11-Bold, serif"); + + // 커서 변경 + hoverChangeCursor(summary, "pointer"); + hoverChangeCursor(custom, "pointer"); + hoverChangeCursor(tournament, "pointer"); + hoverChangeCursor(prev, "pointer"); + hoverChangeCursor(next, "pointer"); + + // click 이벤트 + click(summary, function () {console.log("TODO => 개요 페이지로 이동")}); + click(custom, function () {console.log("TODO => 사용자 지정 모드 페이지로 이동")}); + click(tournament, function () {console.log("TODO => 토너먼트 모드 페이지로 이동")}); + click(prev, function () {console.log("TODO => 이전 페이지로 이동")}); + click(next, function () {console.log("TODO => 다음 페이지로 이동")}); } this.render(); -} \ No newline at end of file + this.addEventListenersToLayout(); +} diff --git a/frontend/src/utils/clickEvent.js b/frontend/src/utils/clickEvent.js new file mode 100644 index 00000000..088a2c59 --- /dev/null +++ b/frontend/src/utils/clickEvent.js @@ -0,0 +1,4 @@ +// TODO => click 시의 동작은 여기서 정의 +export const click = (element, func) => { + element.addEventListener("click", func); +} diff --git a/frontend/src/utils/hoverEvent.js b/frontend/src/utils/hoverEvent.js new file mode 100644 index 00000000..5366dec4 --- /dev/null +++ b/frontend/src/utils/hoverEvent.js @@ -0,0 +1,58 @@ +/** + * 마우스 hover 시 폰트 색상 변경 + * @param element hover 효과를 넣고 싶은 html 엘리먼트 + * @param originalColor 원래 색상 + * @param hoverColor hover 시 색상 + */ +export const hoverChangeColor = (element, originalColor, hoverColor) => { + element.addEventListener("mouseover", () => { + element.style.color = hoverColor; + }); + element.addEventListener("mouseout", () => { + element.style.color = originalColor; + }); +} + +/** + * 마우스 hover 시 폰트 변경 + * @param element hover 효과를 넣고 싶은 html 엘리먼트 + * @param originalFont 원래 폰트 + * @param hoverFont hover 시 폰트 + */ +export const hoverChangeFont = (element, originalFont, hoverFont) => { + element.addEventListener("mouseover", () => { + element.style.fontFamily = hoverFont; + }); + element.addEventListener("mouseout", () => { + element.style.fontFamily = originalFont; + }); +} + +/** + * 마우스 hover 시 border 변경 + * @param element hover 효과를 넣고 싶은 html 엘리먼트 + * @param originalBorder 원래 border + * @param hoverBorder hover 시 border + */ +export const hoverChangeBorder = (element, originalBorder, hoverBorder) => { + element.addEventListener("mouseover", () => { + element.style.border = hoverBorder; + }); + element.addEventListener("mouseout", () => { + element.style.border = originalBorder; + }); +} + +/** + * 마우스 hover 시 cursor 변경 + * @param element hover 효과를 넣고 싶은 html 엘리먼트 + * @param cursor hover 시 cursor + */ +export const hoverChangeCursor = (element, cursor) => { + element.addEventListener("mouseover", () => { + element.style.cursor = cursor; + }); + element.addEventListener("mouseout", () => { + element.style.cursor = "default"; + }); +} diff --git a/nginx/config/owasp-crs/CHANGES.md b/nginx/config/owasp-crs/CHANGES.md index 9ad99c47..700bdb4c 100644 --- a/nginx/config/owasp-crs/CHANGES.md +++ b/nginx/config/owasp-crs/CHANGES.md @@ -562,7 +562,7 @@ Fixes and improvements: * Avoid embedded anchors in CRS rule 942330 (Allan Boll) * Update 942450 for less false positives, more tests (#1662) (Will Woodson) * Ensure single ranges are also checked (#1661) (Federico G. Schwindt) - * WordPress: also exclude posts/pages endpoint in subdirectories (Walter Hop) + * WordPress: also exclude posts/page endpoint in subdirectories (Walter Hop) * For bugs, also ask for the environment (#1657) (Federico G. Schwindt) * XenForo: fix incorrect escape (Walter Hop) * XenForo: additional exclusions (Walter Hop) @@ -761,7 +761,7 @@ Fixes and improvements: * Update RESPONSE-950-DATA-LEAKAGES.conf (Christoph Hansen) * Update RESPONSE-959-BLOCKING-EVALUATION.conf (Christoph Hansen) * Wordpress: add support for Gutenberg editor (siric_, Walter Hop) - * Wordpress: allow searching for any term in admin posts/pages overview (Walter Hop) + * Wordpress: allow searching for any term in admin posts/page overview (Walter Hop) * WordPress: exclude Gutenberg via rest_route (Walter Hop) * WordPress: exclude some more profile.php fields from RFI rule (Walter Hop) * WordPress: exclude SQL comment rule from _wp_http_referer (Walter Hop) diff --git a/nginx/config/owasp-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf b/nginx/config/owasp-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf index 5861bf5a..5ea74544 100644 --- a/nginx/config/owasp-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf +++ b/nginx/config/owasp-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf @@ -383,7 +383,7 @@ SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 3" "id:921016,phase:2,pass,nolog,skipAf # Forbid Request Range Header # -# It is possible abuse the HTTP Request Range Header to leak error pages +# It is possible abuse the HTTP Request Range Header to leak error page # and other information in very small snippets. # The easiest way to fight this is to deny the use of this header. # This is a viable option since the header is only used in rare circumstances diff --git a/nginx/config/owasp-crs/rules/iis-errors.data b/nginx/config/owasp-crs/rules/iis-errors.data index 4449d6ac..f1905589 100644 --- a/nginx/config/owasp-crs/rules/iis-errors.data +++ b/nginx/config/owasp-crs/rules/iis-errors.data @@ -1,4 +1,4 @@ -# This list comes from the default IIS error pages +# This list comes from the default IIS error page # To renerate get the files from a default installation and use: # grep -h '