diff --git a/frontend/src/app.js b/frontend/src/app.js index cf6654d..400d2b6 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -1,10 +1,17 @@ -import { initializeRouter } from "./core/router.js"; +import { initializeRouter, createRoutes } from "./core/router.js"; class App { app; - constructor() { this.app = document.querySelector("#app"); } + lan; + constructor() { + this.app = document.querySelector("#app"); + this.lan = { value: 0 }; + console.log("start!!"); + console.log(this.lan); + } } export const root = new App(); +export const routes = createRoutes(root); -initializeRouter(); \ No newline at end of file +initializeRouter(routes); \ No newline at end of file diff --git a/frontend/src/components/Edit-Profile.js b/frontend/src/components/Edit-Profile.js index cb01f25..c7f9c22 100644 --- a/frontend/src/components/Edit-Profile.js +++ b/frontend/src/components/Edit-Profile.js @@ -3,6 +3,45 @@ import { changeUrl } from "../core/router.js"; export class EditProfile extends Component { + translate() { + const languages = { + 0: { + headText: "Edit Profile", + nickText: "Nickname", + urlText: "Image URL", + twofaText: "Enable 2FA", + saveText: "Save Changes", + deleteText: "Delete Account", + deleteMsgText: "Are you sure you want to delete your account?", + yesText: "Yes", + noText: "No" + }, + 1: { + headText: "프로필 수정", + nickText: "닉네임", + urlText: "이미지 URL", + twofaText: "2단계 인증 활성화", + saveText: "변경 사항 저장", + deleteText: "계정 삭제", + deleteMsgText: "계정을 정말 삭제하시겠습니까?", + yesText: "예", + noText: "아니요" + }, + 2: { + headText: "プロフィール編集", + nickText: "ニックネーム", + urlText: "画像URL", + twofaText: "2FAを有効にする", + saveText: "変更を保存", + deleteText: "アカウント削除", + deleteMsgText: "本当にアカウントを削除しますか?", + yesText: "はい", + noText: "いいえ" + } + }; + this.translations = languages[this.props.lan.value]; + } + initState() { this.nickname = ""; this.img_url = ""; @@ -29,26 +68,27 @@ export class EditProfile extends Component { } template () { + const translations = this.translations; return `
-
Delete Account
+
${translations.deleteText}
- Are you sure you want to delete your account? + ${translations.deleteMsgText}
-
Yes
-
No
+
${translations.yesText}
+
${translations.noText}
- Edit Profile + ${translations.headText}
- +
${this.state.nickname}
@@ -56,12 +96,12 @@ export class EditProfile extends Component { Profile Image
- +
- + ${this.state.is_2FA ? `` : ``}
@@ -70,7 +110,7 @@ export class EditProfile extends Component {
- +
@@ -78,15 +118,15 @@ export class EditProfile extends Component { Profile Image
- +
- + ${this.state.is_2FA ? `` : ``}
- + diff --git a/frontend/src/components/Friends-Info.js b/frontend/src/components/Friends-Info.js index fedb71d..1d62773 100644 --- a/frontend/src/components/Friends-Info.js +++ b/frontend/src/components/Friends-Info.js @@ -3,7 +3,7 @@ import { Component } from "../core/Component.js"; export class FriendsInfo extends Component { template () { - const { is_online, nickname, img_url } = this.props; + const { is_online, nickname, img_url, profileText, removeText } = this.props; return `
${is_online ? `
` : `
`} @@ -13,8 +13,8 @@ export class FriendsInfo extends Component {
-
View Profile
-
Remove
+
${profileText}
+
${removeText}
`; } diff --git a/frontend/src/components/Friends-List.js b/frontend/src/components/Friends-List.js index b5bd240..906a06b 100644 --- a/frontend/src/components/Friends-List.js +++ b/frontend/src/components/Friends-List.js @@ -6,15 +6,45 @@ import { Input } from "./Input.js"; export class FriendsList extends Component { + translate() { + const languages = { + 0: { + headText: "Friends List", + addText: "Add", + profileText: "View Profile", + removeText: "Remove", + searchText: "Search for friends..." + }, + 1: { + headText: "친구 목록", + addText: "추가", + profileText: "프로필 보기", + removeText: "제거", + searchText: "친구 검색..." + }, + 2: { + headText: "友達リスト", + addText: "追加", + profileText: "プロフィール", + removeText: "削除", + searchText: "友達を探す..." + } + }; + + this.translations = languages[this.props.lan.value]; + + } + template () { this.info = null; // friend의 정보 this.search = null; // add 버튼 눌렀을때 나오는 검색창 객체 (Input) + const translations = this.translations; return `
-

Friends List

+

${translations.headText}

@@ -28,7 +58,7 @@ export class FriendsList extends Component {
-
Add
+
${translations.addText}
@@ -54,12 +84,12 @@ export class FriendsList extends Component { this.friends = data; // 응답에서 friends list 꺼내기 // 친구 목록에서 닉네임 리스트를 추출합니다. - const friendNicknameList = this.friends.map(friend => `${friend.nickname}#${friend.user_id}`); - + const friendNicknameList = this.friends.map(friend => `${friend.nickname}`); + const friendIdList = this.friends.map(friend => `${friend.user_id}`); // 친구 닉네임 리스트를 이용해 친구 목록 생성 const ulElement = document.querySelector("ul#friendsLists"); this.children.push(new List(ulElement, - { className: "fList", contents: friendNicknameList })); + { className: "fList", ids: friendIdList, contents: friendNicknameList })); }) .catch(error => { console.error('Fetch operation failed:', error); @@ -77,13 +107,12 @@ export class FriendsList extends Component { this.children.splice(index, 1); } } - - const part = event.target.id.split('#'); - const uid = parseInt(part[1]); + + const uid = parseInt(event.target.id); const friend = this.friends.find(user => user.user_id === uid); - // 새로운 FriendsInfo 인스턴스를 생성하고, this.children에 추가 - this.info = new FriendsInfo(ulElement, {is_online: friend.is_online, nickname: `${friend.nickname}#${friend.user_id}`, img_url: friend.img_url}); + this.info = new FriendsInfo(ulElement, {is_online: friend.is_online, nickname: `${friend.nickname}#${friend.user_id}`, + img_url: friend.img_url, profileText: this.translations.profileText, removeText: this.translations.removeText}); this.children.push(this.info); }); @@ -104,7 +133,7 @@ export class FriendsList extends Component { const index = this.children.indexOf(this.search); if (index !== -1) this.children.splice(index, 1); } - this.search = new Input(ulElement, {inputId: "searchInput", imageId: "addInputImage", img: "/img/plus.jpeg"}); + this.search = new Input(ulElement, {inputId: "searchInput", imageId: "addInputImage", img: "/img/plus.jpeg", searchText: this.translations.searchText}); this.children.push(this.search); }); diff --git a/frontend/src/components/Friends.js b/frontend/src/components/Friends.js index 9ae4280..088de2c 100644 --- a/frontend/src/components/Friends.js +++ b/frontend/src/components/Friends.js @@ -4,6 +4,6 @@ import { FriendsList } from "./Friends-List.js"; export class Friends extends Default { mounted(){ - new FriendsList(document.querySelector("div#contents")); + new FriendsList(document.querySelector("div#contents"), this.props); } } diff --git a/frontend/src/components/Home.js b/frontend/src/components/Home.js index eb0d068..4f5f2ff 100644 --- a/frontend/src/components/Home.js +++ b/frontend/src/components/Home.js @@ -4,6 +4,6 @@ import { Login } from "./Home-Login.js"; export class Home extends Default { mounted(){ - new Login(document.querySelector("div#contents")); + new Login(document.querySelector("div#contents"), this.props); } } diff --git a/frontend/src/components/Input.js b/frontend/src/components/Input.js index 79a7168..49c4b03 100644 --- a/frontend/src/components/Input.js +++ b/frontend/src/components/Input.js @@ -3,9 +3,10 @@ import { Component } from "../core/Component.js"; export class Input extends Component { template () { + console.log(this.props.searchText); return `
- +
diff --git a/frontend/src/components/List.js b/frontend/src/components/List.js index 6f63efb..def1ad6 100644 --- a/frontend/src/components/List.js +++ b/frontend/src/components/List.js @@ -3,12 +3,12 @@ import { Component } from "../core/Component.js"; export class List extends Component { template () { - const { className, contents } = this.props; + const { className, ids, contents } = this.props; return ` - ${contents.map(element => { + ${contents.map((element, index) => { const part = element.split('#'); const nickname = part[0]; - return `
  • ${nickname}
  • ` + return `
  • ${nickname}
  • ` }).join('')} `; } diff --git a/frontend/src/components/Main-Menu.js b/frontend/src/components/Main-Menu.js index 6630346..5d68a49 100644 --- a/frontend/src/components/Main-Menu.js +++ b/frontend/src/components/Main-Menu.js @@ -5,22 +5,48 @@ import { parseJWT } from "../core/jwt.js"; export class Menu extends Component { + translate() { + const languages = { + 0: { + gameMenuTexts: ["Local Game", "Multi Game", "AI", "Tournament"], + userMenuTexts: ["Friends", "Profile", "Logout"], + lanText: "Change Language" + }, + 1: { + gameMenuTexts: ["로컬 게임", "멀티 게임", "AI", "토너먼트"], + userMenuTexts: ["친구", "프로필", "로그아웃"], + lanText: "언어 변경" + }, + 2: { + gameMenuTexts: ["ローカルゲーム", "マルチゲーム", "AI", "トーナメント"], + userMenuTexts: ["友達", "プロフィール", "ログアウト"], + lanText: "言語を変更" + } + }; + + this.translations = languages[this.props.lan.value]; + + } + template () { const payload = parseJWT(); if (!payload) this.uid = null; else this.uid = payload.id; + const translations = this.translations; + return ` `; } mounted(){ - new List(document.querySelector("ul#gameMenu"), { className: "gameMode", contents: ["Local Game", "Multi Game", "AI", "Tournament"]}); - new List(document.querySelector("ul#userMenu"), { className: "showInfo", contents: ["Friends", "Profile", "Logout"]}); + new List(document.querySelector("ul#gameMenu"), { className: "gameMode", ids: ["Local Game", "Multi Game", "AI", "Tournament"], contents: this.translations.gameMenuTexts}); + new List(document.querySelector("ul#userMenu"), { className: "showInfo", ids: ["Friends", "Profile", "Logout"], contents: this.translations.userMenuTexts}); } setEvent () { @@ -28,6 +54,11 @@ export class Menu extends Component { changeUrl("/main/friends"); }); + this.addEvent('click', '#lanButton', () => { + this.props.lan.value = (this.props.lan.value + 1) % 3; + changeUrl("/main") + }); + this.addEvent('click', '#Profile', () => { if (this.uid) changeUrl(`/main/profile/${this.uid}`); else changeUrl("/"); diff --git a/frontend/src/components/Main.js b/frontend/src/components/Main.js index 32656df..d09f491 100644 --- a/frontend/src/components/Main.js +++ b/frontend/src/components/Main.js @@ -3,6 +3,7 @@ import { Menu } from "./Main-Menu.js"; export class Main extends Default { mounted(){ - new Menu(document.querySelector("div#contents")); + new Menu(document.querySelector("div#contents"), this.props); + console.log(this.props.lan.value); } } \ No newline at end of file diff --git a/frontend/src/components/Profile-Info.js b/frontend/src/components/Profile-Info.js index ade47f3..cb3ef78 100644 --- a/frontend/src/components/Profile-Info.js +++ b/frontend/src/components/Profile-Info.js @@ -4,8 +4,36 @@ import { MatchList } from "./Profile-List.js"; import { parseJWT } from "../core/jwt.js"; export class ProfileInfo extends Component { - + + translate() { + const languages = { + 0: { + headText: "Profile", + winText: "Win", + loseText: "Lose", + minText: "min", + editText: "edit" + }, + 1: { + headText: "프로필", + winText: "승리", + loseText: "패배", + minText: "분", + editText: "수정" + }, + 2: { + headText: "プロフィール", + winText: "勝ち", + loseText: "負け", + minText: "分", + editText: "編集" + } + }; + this.translations = languages[this.props.lan.value]; + } + initState() { + console.log(this.props.lan.value); const payload = parseJWT(); if (!payload) this.uid = null; else this.uid = payload.id; @@ -35,17 +63,18 @@ export class ProfileInfo extends Component { } template () { + const translations = this.translations; return `
    - Profile + ${translations.headText}
    - ${parseInt(this.props.uid) === this.uid ? `
    edit
    ` : ""} + ${parseInt(this.props.uid) === this.uid ? `
    ${translations.editText}
    ` : ""}
    ${this.state.user.nickname} @@ -66,14 +95,13 @@ export class ProfileInfo extends Component {
    - Win ${this.state.user.win} - Lose ${this.state.user.lose} + ${translations.winText} ${this.state.user.win} + ${translations.loseText} ${this.state.user.lose} (${this.state.rate}%)
      -
    @@ -86,7 +114,7 @@ export class ProfileInfo extends Component { } mounted() { - new MatchList(document.querySelector("ul#matches"), {matches: this.state.games}); + new MatchList(document.querySelector("ul#matches"), { matches: this.state.games, minText: this.translations.minText }); this.drawBackgroundCircle(); this.drawProgressCircle(); this.updatePercentage(); diff --git a/frontend/src/components/Profile-List.js b/frontend/src/components/Profile-List.js index cf2051b..ad11a7e 100644 --- a/frontend/src/components/Profile-List.js +++ b/frontend/src/components/Profile-List.js @@ -7,12 +7,21 @@ export class MatchList extends Component { if (!matches) return ""; return ` ${matches.map(element => { + // Date 객체로 변환 + const date = new Date(element.start_timestamp); + + // 월, 일, 시, 분 추출 + const month = date.getMonth() + 1; // getMonth()는 0부터 시작하므로 1을 더해줍니다. + const day = date.getDate(); + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `
  • Date -
    ${element.start_timestamp}
    -
    ${element.playtime}min
    +
    ${month}/${day} ${hours}:${minutes}
    +
    ${element.playtime}${this.props.minText}
    VS diff --git a/frontend/src/core/Component.js b/frontend/src/core/Component.js index 124c29b..74af623 100644 --- a/frontend/src/core/Component.js +++ b/frontend/src/core/Component.js @@ -2,7 +2,7 @@ import { observable, observe } from './observer.js'; export class Component { - state; props; $el; children; + state; props; $el; children; translations; constructor ($el, props) { this.$el = $el; @@ -13,6 +13,7 @@ export class Component { } setup() { + this.translate(); this.state = observable(this.initState()); observe(() => { this.render(); @@ -25,6 +26,7 @@ export class Component { template () { return ''; } render () { this.$el.innerHTML = this.template(); } setEvent () {} + translate() {} mounted () {} addEvent (eventType, selector, callback) { diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 9af46c9..e86e527 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -1,36 +1,32 @@ import { Home } from "../components/Home.js"; -import { root } from "../app.js"; +import { root, routes } from "../app.js"; import { Main } from "../components/Main.js"; import { Friends } from "../components/Friends.js"; import { Profile } from "../components/Profile.js"; import { TwoFA } from "../components/2FA.js"; import { Edit } from "../components/Edit.js"; -export const routes = { - "/": { - component: () => new Home(root.app), - props: {} - }, - "/main": { - component: () => new Main(root.app), - props: {} - }, - "/main/friends": { - component: () => new Friends(root.app), - props: {} - }, - "/main/profile/:uid": { - component: (props) => new Profile(root.app, props), - props: { uid: "" } - }, - "/2FA": { - component: () => new TwoFA(root.app), - props: {} - }, - "/main/profile/:uid/edit": { - component: (props) => new Edit(root.app, props), - props: { uid: "" } - } +export const createRoutes = (root) => { + return { + "/": { + component: (props) => new Home(root.app, props), + }, + "/main": { + component: (props) => new Main(root.app, props), + }, + "/main/friends": { + component: (props) => new Friends(root.app, props), + }, + "/main/profile/:uid": { + component: (props) => new Profile(root.app, props), + }, + "/2FA": { + component: (props) => new TwoFA(root.app, props), + }, + "/main/profile/:uid/edit": { + component: (props) => new Edit(root.app, props), + } + }; }; export const changeUrl = async (requestedUrl, usePushState = true) => { @@ -107,12 +103,13 @@ export async function parsePath(path) { const regex = new RegExp('^' + key.replace(/:\w+/g, '([\\w-]+)') + '$'); const match = path.match(regex); if (match) { - const props = { ...route.props }; + const props = { lan: root.lan, ...route.props }; const values = match.slice(1); const keys = key.match(/:\w+/g) || []; keys.forEach((key, index) => { props[key.substring(1)] = values[index]; }); + console.log(props); route.component(props); return; } diff --git a/frontend/style.css b/frontend/style.css index 40cce66..5c3e957 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -165,6 +165,7 @@ p#login:hover { div#menuBox { + position: relative; width: 100%; height: 100%; display: flex; @@ -173,6 +174,31 @@ div#menuBox { justify-content: center; } +div#lanButton { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + font-weight: 400; + border-radius: 10px; + width: 160px; + height: 50px; + bottom: 30px; + right: 30px; + font-size: 17px; + border-radius: 50px; + color: white; + cursor: pointer; + background: #357ABD; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); /* 기본 상태에서 그림자 없음 */ + transition: box-shadow 0.3s ease, transform 0.3s ease; +} + +div#lanButton:hover { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 호버 시 더 깊은 그림자 */ + transform: translateY(-2px); /* 살짝 위로 이동 */ +} + ul#gameMenu{ width: 100%; height: 50%; @@ -188,7 +214,7 @@ ul#gameMenu{ li.gameMode{ margin-left: 1%; margin-right: 1%; - width: 250px; + width: 280px; height: 230px; font-size: 2.7rem; border-radius: 50px; @@ -224,10 +250,10 @@ ul#userMenu{ li.showInfo{ margin-left: 1%; margin-right: 1%; - width: 120px; - height: 120px; + width: 160px; + height: 150px; border-radius: 50%; - font-size: 2rem; + font-size: 1.8rem; color: #31363F; cursor: pointer; text-align: center; @@ -628,7 +654,7 @@ div#profileHeaderBox { font-weight: 900; color:#31363F; font-size: 3.0rem; - width: 200px; + width: 300px; height: 100%; text-align: center; display: flex; @@ -872,21 +898,20 @@ span#date { } div#startTime { - width: 60%; - height: 40%; + width: 80%; + height: 45%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; padding-bottom: 5px; - font-size: 18px; + font-size: 16px; color:#31363F; font-weight: 700; - border-bottom: 0.5px solid #31363F; } div#playTime { - width: 100%; + width: 60%; height: 40%; display: flex; flex-direction: column; @@ -895,6 +920,7 @@ div#playTime { color:#656262; padding-top: 5px; font-weight: 700; + border-top: 0.5px solid #31363F; }