diff --git a/.pnp.cjs b/.pnp.cjs index ca2f71a..1e2c04d 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2868,6 +2868,37 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@nestjs/throttler", [\ + ["npm:5.1.0", {\ + "packageLocation": "./.yarn/cache/@nestjs-throttler-npm-5.1.0-12d7d9629c-8dc900ca1d.zip/node_modules/@nestjs/throttler/",\ + "packageDependencies": [\ + ["@nestjs/throttler", "npm:5.1.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:5.1.0", {\ + "packageLocation": "./.yarn/__virtual__/@nestjs-throttler-virtual-676666150b/0/cache/@nestjs-throttler-npm-5.1.0-12d7d9629c-8dc900ca1d.zip/node_modules/@nestjs/throttler/",\ + "packageDependencies": [\ + ["@nestjs/throttler", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:5.1.0"],\ + ["@nestjs/common", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:10.2.8"],\ + ["@nestjs/core", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:10.2.8"],\ + ["@types/nestjs__common", null],\ + ["@types/nestjs__core", null],\ + ["@types/reflect-metadata", null],\ + ["md5", "npm:2.3.0"],\ + ["reflect-metadata", "npm:0.1.13"]\ + ],\ + "packagePeers": [\ + "@nestjs/common",\ + "@nestjs/core",\ + "@types/nestjs__common",\ + "@types/nestjs__core",\ + "@types/reflect-metadata",\ + "reflect-metadata"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@nestjs/typeorm", [\ ["npm:10.0.0", {\ "packageLocation": "./.yarn/cache/@nestjs-typeorm-npm-10.0.0-74e9c291bc-716f7e9596.zip/node_modules/@nestjs/typeorm/",\ @@ -6949,6 +6980,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["charenc", [\ + ["npm:0.0.2", {\ + "packageLocation": "./.yarn/cache/charenc-npm-0.0.2-aca0c2f207-a45ec39363.zip/node_modules/charenc/",\ + "packageDependencies": [\ + ["charenc", "npm:0.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["chart.js", [\ ["npm:4.4.1", {\ "packageLocation": "./.yarn/cache/chart.js-npm-4.4.1-bea8f3ff67-ef0cd10118.zip/node_modules/chart.js/",\ @@ -7608,6 +7648,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["crypt", [\ + ["npm:0.0.2", {\ + "packageLocation": "./.yarn/cache/crypt-npm-0.0.2-033627d94f-adbf263441.zip/node_modules/crypt/",\ + "packageDependencies": [\ + ["crypt", "npm:0.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["crypto", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/crypto-npm-1.0.1-7cb8e3dca6-fcf7dbd68a.zip/node_modules/crypto/",\ @@ -9952,6 +10001,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["is-buffer", [\ + ["npm:1.1.6", {\ + "packageLocation": "./.yarn/cache/is-buffer-npm-1.1.6-08199d9ccc-ae18aa0b6e.zip/node_modules/is-buffer/",\ + "packageDependencies": [\ + ["is-buffer", "npm:1.1.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["is-callable", [\ ["npm:1.2.7", {\ "packageLocation": "./.yarn/cache/is-callable-npm-1.2.7-808a303e61-ceebaeb9d9.zip/node_modules/is-callable/",\ @@ -11563,6 +11621,18 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["md5", [\ + ["npm:2.3.0", {\ + "packageLocation": "./.yarn/cache/md5-npm-2.3.0-86c49d3915-14a21d597d.zip/node_modules/md5/",\ + "packageDependencies": [\ + ["md5", "npm:2.3.0"],\ + ["charenc", "npm:0.0.2"],\ + ["crypt", "npm:0.0.2"],\ + ["is-buffer", "npm:1.1.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["mdast-util-find-and-replace", [\ ["npm:3.0.1", {\ "packageLocation": "./.yarn/cache/mdast-util-find-and-replace-npm-3.0.1-284ae6ddf8-1faca98c4e.zip/node_modules/mdast-util-find-and-replace/",\ @@ -14705,6 +14775,7 @@ const RAW_RUNTIME_STATE = ["@nestjs/schematics", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:10.0.3"],\ ["@nestjs/swagger", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:7.1.16"],\ ["@nestjs/testing", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:10.2.8"],\ + ["@nestjs/throttler", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:5.1.0"],\ ["@nestjs/typeorm", "virtual:2499dbb93d824027565d71b0716c4fb8b548ad61955d0a0286bfb3c5b4058e227894b6691d96808c00f576db14870018375210362c26ee321ea99fd6ed041c74#npm:10.0.0"],\ ["@types/cookie-parser", "npm:1.4.6"],\ ["@types/express", "npm:4.17.21"],\ diff --git a/.yarn/cache/@nestjs-throttler-npm-5.1.0-12d7d9629c-8dc900ca1d.zip b/.yarn/cache/@nestjs-throttler-npm-5.1.0-12d7d9629c-8dc900ca1d.zip new file mode 100644 index 0000000..6feb014 Binary files /dev/null and b/.yarn/cache/@nestjs-throttler-npm-5.1.0-12d7d9629c-8dc900ca1d.zip differ diff --git a/.yarn/cache/charenc-npm-0.0.2-aca0c2f207-a45ec39363.zip b/.yarn/cache/charenc-npm-0.0.2-aca0c2f207-a45ec39363.zip new file mode 100644 index 0000000..10ef15c Binary files /dev/null and b/.yarn/cache/charenc-npm-0.0.2-aca0c2f207-a45ec39363.zip differ diff --git a/.yarn/cache/crypt-npm-0.0.2-033627d94f-adbf263441.zip b/.yarn/cache/crypt-npm-0.0.2-033627d94f-adbf263441.zip new file mode 100644 index 0000000..0de38b1 Binary files /dev/null and b/.yarn/cache/crypt-npm-0.0.2-033627d94f-adbf263441.zip differ diff --git a/.yarn/cache/is-buffer-npm-1.1.6-08199d9ccc-ae18aa0b6e.zip b/.yarn/cache/is-buffer-npm-1.1.6-08199d9ccc-ae18aa0b6e.zip new file mode 100644 index 0000000..082d5a6 Binary files /dev/null and b/.yarn/cache/is-buffer-npm-1.1.6-08199d9ccc-ae18aa0b6e.zip differ diff --git a/.yarn/cache/leva-npm-0.9.35-da054c7dd9-9555db5e0b.zip b/.yarn/cache/leva-npm-0.9.35-da054c7dd9-9555db5e0b.zip index b550a17..68dab39 100644 Binary files a/.yarn/cache/leva-npm-0.9.35-da054c7dd9-9555db5e0b.zip and b/.yarn/cache/leva-npm-0.9.35-da054c7dd9-9555db5e0b.zip differ diff --git a/.yarn/cache/md5-npm-2.3.0-86c49d3915-14a21d597d.zip b/.yarn/cache/md5-npm-2.3.0-86c49d3915-14a21d597d.zip new file mode 100644 index 0000000..6770dd3 Binary files /dev/null and b/.yarn/cache/md5-npm-2.3.0-86c49d3915-14a21d597d.zip differ diff --git a/.yarn/cache/react-joyride-npm-2.7.1-78984ee8a0-4aefbe8a6a.zip b/.yarn/cache/react-joyride-npm-2.7.1-78984ee8a0-4aefbe8a6a.zip index 54df1e4..6ea818c 100644 Binary files a/.yarn/cache/react-joyride-npm-2.7.1-78984ee8a0-4aefbe8a6a.zip and b/.yarn/cache/react-joyride-npm-2.7.1-78984ee8a0-4aefbe8a6a.zip differ diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 2e30fbc..6fdfc73 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/README.md b/README.md index e74cf09..4b8a3f2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,45 @@ -# 🌟 별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜ 🌟 +# 별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜ 🌟 + μžμ‹ λ§Œμ˜ μš°μ£Όμ— 기얡을 담은 별을 λ„μš°λŠ” μ›Ή μΆ”μ–΅ μ €μž₯μ†Œ + + +
+ +## λͺ©μ°¨ + +### [1. ν”„λ‘œμ νŠΈ μ†Œκ°œ]() + +- [<별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>λ₯Ό λ§Œλ“€κ²Œ 된 계기]() +- [κΈ°λŠ₯ μ„€λͺ…]() +- [ν”„λ‘œμ νŠΈ μ‹€ν–‰ 방법]() + +### [2. 기술 μŠ€νƒ]() + +### [3. 기술적 도전]() + +### [4. νŒ€μ› μ†Œκ°œ](#πŸƒβ€β™‚οΈ-νŒ€μ›-μ†Œκ°œ) + +### [5. νŒ€μ› 회고]() +
+## <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜> μ„œλΉ„μŠ€ μ†Œκ°œ + +### κΈ°λŠ₯ μ„€λͺ… + +1. 둜고 ν™”λ©΄ -> 둜그인 ν™”λ©΄ -> λ‘œκ·ΈμΈν•˜κ³  μ€ν•˜ λ“€μ–΄κ°€λŠ” κ²ƒκΉŒμ§€ +2. νšŒμ›κ°€μž…ν•˜κ³  μ€ν•˜ λ“€μ–΄κ°€λŠ” κ²ƒκΉŒμ§€ +3. μ½”μΉ˜λ§ˆν¬ 보여주기 +4. leva둜 μ‘°μ ˆν•˜λŠ” 것 보여주기 +5. μ€ν•˜ μˆ˜μ •ν•˜λŠ” 것 보여주기 +6. κΈ€μ“°κΈ° μ°½ 보여주기 +7. κΈ€μ“°κΈ° 이후 별 μ»€μŠ€ν…€ν•˜λŠ” μ°½ 보여주기 -> μ‹€μ œ μƒμ„±λœ 별 보여주기 +8. 별 ν΄λ¦­ν•΄μ„œ μˆ˜μ •ν•˜κ³  μ‚­μ œν•˜λŠ” ν™”λ©΄ +9. 곡유 λͺ¨λ‹¬μ—μ„œ 검색 μƒνƒœ μˆ˜μ •ν•˜λŠ” ν™”λ©΄ +10. 곡유 링크 λ³΅μ‚¬ν•΄μ„œ μ‹€μ œλ‘œ λ“€μ–΄κ°€λŠ” ν™”λ©΄ +11. λ‹€λ₯Έ μ‚¬λžŒ μ€ν•˜ κ²€μƒ‰ν•΄μ„œ λ“€μ–΄κ°€λŠ” ν™”λ©΄ + ### **"λ‚΄ μ‚Άμ˜ λ°˜μ§μ΄λŠ” 기얡듀을 μ€ν•˜λ‘œ λ§Œλ“€ 수 μžˆλ‹€λ©΄ μ–΄λ–¨κΉŒ?"** 남겨두고 싢은 μˆœκ°„μ„ 찍은 사진과, κ·Έ μˆœκ°„μ„ λ– μ˜¬λ¦¬λ©° 적은 글을 별에 λ‹΄μŠ΅λ‹ˆλ‹€. @@ -18,7 +55,6 @@ ### β˜€οΈΒ λ³„κΈ€ μž‘μ„±ν•˜κΈ° > μ†Œμ€‘ν•œ 기얡을 μžŠμ§€ μ•Šλ„λ‘ -> 사진과 글을 λ‹΄μ•„ 기얡을 κΈ°λ‘ν•˜κ³ , λ‚˜λ§Œμ˜ 별을 생성할 수 μžˆμ–΄μš”. @@ -29,7 +65,6 @@ ### πŸŒŒΒ μ€ν•˜ λ‘˜λŸ¬λ³΄κΈ° > 차곑차곑 μŒ“μ•„μ˜¨ 좔얡듀을 ν•œ λˆˆμ— λ³Ό 수 μžˆλ„λ‘ -> μž‘μ„±λœ 별글듀이 λͺ¨μ—¬ μ•„λ¦„λ‹€μš΄ μ€ν•˜κ°€ λΌμš”. @@ -40,7 +75,6 @@ ### πŸ”—Β μš°μ£Ό κ³΅μœ ν•˜κΈ° > λ‚΄ μ‚Άμ˜ 기둝듀을 μ†Œμ€‘ν•œ μ‚¬λžŒλ“€κ³Ό ν•¨κ»˜ λ‚˜λˆŒ 수 μžˆλ„λ‘ -> λ‚˜λ§Œμ˜ 우주λ₯Ό 링크λ₯Ό μ΄μš©ν•΄ κ³΅μœ ν•  수 μžˆμ–΄μš”. @@ -49,11 +83,43 @@
### πŸ’ͺ🏻 μžμ„Έν•œ ν”„λ‘œμ νŠΈ 진행과정은 wikiλ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”! + https://github.com/boostcampwm2023/web16-B1G1/wiki
-## 🌟 <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>λ₯Ό 같이 λ§Œλ“€μ–΄κ°€λŠ” μ‚¬λžŒλ“€ +## βš’οΈ 기술 μŠ€νƒ + +[πŸ”— wiki 기술 μŠ€νƒ μ†Œκ°œ λ°”λ‘œκ°€κΈ°](https://github.com/boostcampwm2023/web16-B1G1/wiki/%EA%B8%B0%EC%88%A0%EC%8A%A4%ED%83%9D-%EC%86%8C%EA%B0%9C) + + + + + +### 기술 μŠ€νƒ μ„ μ • μ΄μœ μ— κ΄€ν•œ νŒ€μ›λ“€μ˜ κΈ€ + +- [우리 νŒ€μ΄ Zustandλ₯Ό μ“°λŠ” 이유](https://velog.io/@greencloud/%EC%9A%B0%EB%A6%AC-%ED%8C%80%EC%9D%B4-Zustand%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0) +- [Emotion 선택 μ‹œ 고렀사항](https://velog.io/@200tiger/Emotion-%EC%84%A0%ED%83%9D%EC%8B%9C-%EA%B3%A0%EB%A0%A4%EC%82%AC%ED%95%AD) +- [Yarn berry둜 λͺ¨λ…Έλ ˆν¬ κ΅¬μ„±ν•˜κΈ°](https://velog.io/@minboykim/Yarn-berry%EB%A1%9C-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0) +- [Vite, μ™œ μ“°λŠ”κ±°μ§€?](https://velog.io/@minboykim/Vite-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B1%B0%EC%A7%80) +- [κΈ°μˆ μŠ€νƒ μ„ μ •μ΄μœ  (NestJS, TypeORM, Docker, GitHub Actions)](https://velog.io/@qkrwogk/%EA%B8%B0%EC%88%A0%EC%8A%A4%ED%83%9D-%EC%84%A0%EC%A0%95%EC%9D%B4%EC%9C%A0-NestJS-TypeORM-Docker-GitHub-Actions) + +
+ +## πŸ’ͺ🏻 기술적 도전 + +### FE + +**Three.js + React-Three-Fiberλ₯Ό μ‚¬μš©ν•œ 우주 곡간 κ΅¬ν˜„** + +### BE + +
+ +## πŸƒβ€β™‚οΈ νŒ€μ› μ†Œκ°œ + +[πŸ”— wiki νŒ€μ› μ†Œκ°œ λ°”λ‘œκ°€κΈ°](https://github.com/boostcampwm2023/web16-B1G1/wiki/%ED%8C%80%EC%9B%90-%EC%86%8C%EA%B0%9C) +
@@ -91,56 +157,34 @@ https://github.com/boostcampwm2023/web16-B1G1/wiki
-
- -## 🌟 νŒ€μ› μ†Œκ°œ ### πŸ™ J010 김가은 (FE) + - λΈ”λ‘œκ·Έ: https://velog.io/@greencloud +- κΉƒν—ˆλΈŒ: https://github.com/KimGaeun0806 - <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>μ—μ„œμ˜ λͺ©ν‘œ: ν”„λ‘œμ νŠΈ κ³Όμ • ν•˜λ‚˜ν•˜λ‚˜ λͺ¨λ‘ 기둝으둜 남기기. κΈ°μˆ λΈ”λ‘œκ·Έ μ—΄μ‹¬νžˆ 써보기 πŸ‘» -- TMI: μ΅œκ·Όμ— ν—¬μŠ€ μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€. πŸ’ͺ🏻 -- MBTI: ESTJ -- νŒ€μ›λ“€μ—κ²Œ ν•œ λ§ˆλ””: ν–‰λ³΅ν•˜μ„Έμš” ### 🐧 J016 김동민 (FE) + - λΈ”λ‘œκ·Έ: https://minboykim.github.io/ +- κΉƒν—ˆλΈŒ: https://github.com/MinboyKim - <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>μ—μ„œμ˜ λͺ©ν‘œ: μ’‹μ€μ‚¬λžŒλ“€κ³Ό μ’‹μ€μ‹œκ°„λ³΄λ‚΄κΈ° β˜•οΈ -- TMI: νŽ­κ·„μ΄ 세상을 μ§€λ°°ν• κ²λ‹ˆλ‹€. -- MBTI: ENTP -- νŒ€μ›λ“€μ—κ²Œ ν•œ λ§ˆλ””: μ•„μžμ•„μž ν™”μ΄νŒ… ### πŸ‘Ύ J053 λ°•μž¬ν•˜ (BE) + - λΈ”λ‘œκ·Έ: https://velog.io/@qkrwogk +- κΉƒν—ˆλΈŒ: https://github.com/qkrwogk - <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>μ—μ„œμ˜ λͺ©ν‘œ: λ”₯ λ‹€μ΄λΈŒ κ²½ν—˜! 🌊 -- TMI: μ € λ‚˜μ΄ μ•ˆλ§Žμ•„μš”γ… γ…  -- MBTI: ENTJ -- νŒ€μ›λ“€μ—κ²Œ ν•œ λ§ˆλ””: λͺ¨λ‘ κ±΄κ°•ν•©μ‹œλ‹€. ### ⚽️ J073 솑쀀섭 (BE) + - λΈ”λ‘œκ·Έ: https://velog.io/@songjseop +- κΉƒν—ˆλΈŒ: https://github.com/SongJSeop - <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>μ—μ„œμ˜ λͺ©ν‘œ: νŒ€μ›λ“€κ³Ό ν›„νšŒ μ—†λŠ” μ‹œκ°„ 보내기 -- TMI: μ—°μ•  3λ…„μ°¨ γ…‹γ…‹ ν’ˆμ ˆλ‚¨ λ„˜λ³΄μ§€ λ§ˆμ‹­μ‡Ό -- MBTI: ENFP -- νŒ€μ›λ“€μ—κ²Œ ν•œ λ§ˆλ””: 같이 좕ꡬ ν•  μ‚¬λžŒ? ### 🐰 J098 이백범 (FE) + - λΈ”λ‘œκ·Έ: https://velog.io/@200tiger +- κΉƒν—ˆλΈŒ: https://github.com/bananaba - <별 ν•˜λ‚˜μ— κΈ€ ν•˜λ‚˜>μ—μ„œμ˜ λͺ©ν‘œ: μž¬λ―ΈμžˆλŠ” κ²°κ³Όλ¬Ό λ§Œλ“€κΈ°! -- TMI: 쇼생크 νƒˆμΆœμ΄ 인생 μ˜ν™”μ—μš”! -- MBTI: INTP -- νŒ€μ›λ“€μ—κ²Œ ν•œ λ§ˆλ””: 쒋은 μ•„μΉ¨!
- -## 🌟 기술 μŠ€νƒ - - - -
- -| ꡬ뢄 | 기술 μŠ€νƒ | -| :---: | :----: | -| Frontend | React-Three-Fiber, Zustand, Emotion | -| Backend | TypeORM | -| DB | | -| CI/CD | | -| Deployment | Naver Cloud Platform | diff --git a/packages/client/src/entities/like/Like.tsx b/packages/client/src/entities/like/Like.tsx index afe400a..83875a6 100644 --- a/packages/client/src/entities/like/Like.tsx +++ b/packages/client/src/entities/like/Like.tsx @@ -1,8 +1,9 @@ import { useEffect, useReducer, useState } from 'react'; import { Heart } from 'lucide-react'; -import { Button } from 'shared/ui'; +import { AlertDialog, Button } from 'shared/ui'; import theme from 'shared/ui/styles/theme'; import instance from 'shared/apis/core/AxiosInterceptor'; +import { useNavigate } from 'react-router-dom'; interface PropsType { postId: string; @@ -46,6 +47,8 @@ export default function Like({ postId, count }: PropsType) { likeCount: count, }); const [isButtonDisabled, setButtonDisabled] = useState(false); + const [alert, setAlert] = useState(false); + const navigate = useNavigate(); useEffect(() => { const fetchLike = async () => { @@ -60,14 +63,21 @@ export default function Like({ postId, count }: PropsType) { const clickLike = async () => { if (isButtonDisabled) return; + setButtonDisabled(true); + + const path = location.pathname.split('/')[1]; + if (path === 'guest') { + setAlert(true); + setButtonDisabled(false); + return; + } try { - setButtonDisabled(true); - const res = await instance({ + await instance({ method: 'patch', url: `/post/${postId}/like`, }); - if (res.status === 200) dispatch({ type: LIKE }); + dispatch({ type: LIKE }); } finally { setButtonDisabled(false); } @@ -78,33 +88,48 @@ export default function Like({ postId, count }: PropsType) { try { setButtonDisabled(true); - const res = await instance({ + await instance({ method: 'patch', url: `/post/${postId}/unlike`, }); - if (res.status === 200) dispatch({ type: UNLIKE }); + dispatch({ type: UNLIKE }); } finally { setButtonDisabled(false); } }; return ( - + <> + + {alert && ( + setAlert(false)} + onClickActionButton={() => { + setAlert(false); + navigate('/login'); + }} + disabled={false} + /> + )} + ); } diff --git a/packages/client/src/entities/posts/ui/Post.tsx b/packages/client/src/entities/posts/ui/Post.tsx index 7ccdda7..2ddb058 100644 --- a/packages/client/src/entities/posts/ui/Post.tsx +++ b/packages/client/src/entities/posts/ui/Post.tsx @@ -6,8 +6,8 @@ import styled from '@emotion/styled'; import { useViewStore } from 'shared/store/useViewStore'; import * as THREE from 'three'; import { StarType } from 'shared/lib/types/star'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { useState } from 'react'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { useState, useEffect } from 'react'; import theme from 'shared/ui/styles/theme'; import Star from 'features/star/Star'; @@ -27,6 +27,12 @@ export default function Post({ data, postId, title }: PropsType) { const navigate = useNavigate(); const location = useLocation(); + const { postId: id } = useParams(); + + useEffect(() => { + if (id && Number(id) === postId) setTargetView(meshRef.current); + }, [id]); + const handleMeshClick = (e: ThreeEvent) => { e.stopPropagation(); diff --git a/packages/client/src/features/controls/Controls.tsx b/packages/client/src/features/controls/Controls.tsx index 6a73b4a..d1bb619 100644 --- a/packages/client/src/features/controls/Controls.tsx +++ b/packages/client/src/features/controls/Controls.tsx @@ -56,7 +56,7 @@ export default function Controls() { useEffect(() => { setCameraToCurrentView(currentView.distanceTo(state.camera.position)); - }, []); + }, [view]); useEffect(() => { setTargetView(null); @@ -77,7 +77,6 @@ export default function Controls() { distance.z = 0; if (distance.length() > LENGTH_LIMIT) distance.setLength(LENGTH_LIMIT); state.camera.position.add(distance); - setCameraToCurrentView(currentView.distanceTo(state.camera.position)); } if (targetPosition !== currentView) { diff --git a/packages/client/src/features/postModal/ui/PostModal.tsx b/packages/client/src/features/postModal/ui/PostModal.tsx index eae22af..8fb47f4 100644 --- a/packages/client/src/features/postModal/ui/PostModal.tsx +++ b/packages/client/src/features/postModal/ui/PostModal.tsx @@ -1,5 +1,5 @@ import { useViewStore } from 'shared/store/useViewStore'; -import { Button, Modal, ModalPortal, TextArea } from 'shared/ui'; +import { Button, Modal, TextArea } from 'shared/ui'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import styled from '@emotion/styled'; @@ -64,32 +64,31 @@ export default function PostModal() { } }; - const rightButton = ( - - ); - - const EditButton = ( - + const RightButton = ( + + + + ); const EditCancelButton = ( @@ -127,6 +126,7 @@ export default function PostModal() { setToast({ text: '글이 μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', type: 'success' }); setView('MAIN'); navigate('/home'); + setTargetView(null); } finally { setIsDeleteButtonDisabled(false); } @@ -147,11 +147,11 @@ export default function PostModal() { return ( data && ( - + <> } @@ -164,7 +164,7 @@ export default function PostModal() { )} {isEdit ? ( - + )} - + ) ); } @@ -233,10 +233,10 @@ const Container = styled.div` const TextContainer = styled.div` width: 40vw; - height: 100%; ${({ theme: { colors } }) => ({ color: colors.text.secondary, })} + word-break: break-all; & ol { padding-left: 40px; @@ -263,3 +263,8 @@ const ImageContainer = styled.div` justify-content: center; margin-bottom: 26px; `; + +const ButtonContainer = styled.div` + display: flex; + gap: 8px; +`; diff --git a/packages/client/src/features/star/Star.tsx b/packages/client/src/features/star/Star.tsx index 9adea16..bf67097 100644 --- a/packages/client/src/features/star/Star.tsx +++ b/packages/client/src/features/star/Star.tsx @@ -33,9 +33,9 @@ const Star = forwardRef((props, ref) => { } = props; useFrame((state, delta) => { - const cameraDistance = innerRef.current.position.distanceTo( - state.camera.position, - ); + const cameraDistance = innerRef.current + .getWorldPosition(new THREE.Vector3()) + .distanceTo(state.camera.position); const scale = Math.log((cameraDistance / DISTANCE_LIMIT) * Math.E); if (cameraDistance > DISTANCE_LIMIT) { @@ -62,11 +62,6 @@ const Star = forwardRef((props, ref) => { emissive={color} emissiveIntensity={brightness} /> - {/* */} {children} ); diff --git a/packages/client/src/features/writingModal/ui/WritingModal.tsx b/packages/client/src/features/writingModal/ui/WritingModal.tsx index b7b9472..6d5596d 100644 --- a/packages/client/src/features/writingModal/ui/WritingModal.tsx +++ b/packages/client/src/features/writingModal/ui/WritingModal.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import { Button, Modal } from 'shared/ui'; import TextArea from 'shared/ui/textArea/TextArea'; -import { ModalPortal } from 'shared/ui'; import Images from './Images'; import { useNavigate, useLocation } from 'react-router-dom'; import { useViewStore, usePostStore } from 'shared/store'; @@ -45,7 +44,7 @@ export default function WritingModal() { }; return ( - + <> {isClose && ( - + ); } diff --git a/packages/client/src/pages/Home/Home.tsx b/packages/client/src/pages/Home/Home.tsx index 1ec3641..abee5ab 100644 --- a/packages/client/src/pages/Home/Home.tsx +++ b/packages/client/src/pages/Home/Home.tsx @@ -13,14 +13,14 @@ import { } from 'widgets/galaxy/lib/constants'; import useCheckNickName from 'shared/hooks/useCheckNickName'; import { FullScreen, useFullScreenHandle } from 'react-full-screen'; -import styled from '@emotion/styled'; -import { keyframes } from '@emotion/react'; import UnderBar from 'widgets/underBar/UnderBar'; import UpperBar from 'widgets/upperBar/UpperBar'; import CoachMarker from 'features/coachMarker/CoachMarker'; export default function Home() { - const [isSwitching, setIsSwitching] = useState(false); + const [isSwitching, setIsSwitching] = useState<'warp' | 'fade' | 'end'>( + 'end', + ); const { text, type } = useToastStore(); const { nickName, status } = useCheckNickName(); @@ -30,9 +30,11 @@ export default function Home() { const custom = useCustomStore(); useEffect(() => { - setIsSwitching(true); - + if (!JSON.parse(sessionStorage.getItem('isReload') ?? 'false')) + setIsSwitching('warp'); if (nickName === '') return; + sessionStorage.setItem('isReload', 'false'); + getGalaxy(nickName).then((res) => { if (!res.spiral) setSpiral(SPIRAL); else { @@ -60,6 +62,14 @@ export default function Home() { }); }, [nickName]); + useEffect(() => { + const setReload = () => sessionStorage.setItem('isReload', 'true'); + + window.addEventListener('beforeunload', setReload); + + return () => window.removeEventListener('beforeunload', setReload); + }, []); + const keyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); @@ -80,10 +90,11 @@ export default function Home() { return ( - {status === 'new' && } - {isSwitching && } - {!isSwitching && } + {status === 'new' && } + {isSwitching !== 'end' && ( + + )} {text && {text}} @@ -93,24 +104,3 @@ export default function Home() { ); } - -const fadeout = keyframes` - 0% { - opacity: 1; - } - 100% { - opacity: 0; - display: none; - } -`; - -const FadeoutScreen = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 101; - background-color: white; - animation: ${fadeout} 0.5s linear forwards; -`; diff --git a/packages/client/src/pages/Landing/Landing.tsx b/packages/client/src/pages/Landing/Landing.tsx index 3aa0f79..e8c7645 100644 --- a/packages/client/src/pages/Landing/Landing.tsx +++ b/packages/client/src/pages/Landing/Landing.tsx @@ -1,25 +1,16 @@ import LandingScreen from 'widgets/landingScreen/LandingScreen'; -import { useState } from 'react'; import { useToastStore } from 'shared/store/useToastStore'; import { Toast } from 'shared/ui'; import { Outlet } from 'react-router-dom'; export default function Landing() { - const [mouse, setMouse] = useState([0.5, 0.5]); const { text, type } = useToastStore(); return ( -
{ - setMouse([ - e.clientX / window.innerWidth, - e.clientY / window.innerHeight, - ]); - }} - > + <> {text && {text}} - -
+ + ); } diff --git a/packages/client/src/shared/apis/search.ts b/packages/client/src/shared/apis/search.ts index d7da39d..215c0d7 100644 --- a/packages/client/src/shared/apis/search.ts +++ b/packages/client/src/shared/apis/search.ts @@ -8,3 +8,12 @@ export const getNickNames = async (nickName: string) => { return data; }; + +export const checkExistNickname = async (nickName: string) => { + const { data } = await instance({ + method: 'GET', + url: `/auth/check-nickname?nickname=${nickName}`, + }); + if (data === false) return Promise.reject(); + return data; +}; diff --git a/packages/client/src/shared/hooks/useCheckNickName.ts b/packages/client/src/shared/hooks/useCheckNickName.ts index d41eb1b..95e0cac 100644 --- a/packages/client/src/shared/hooks/useCheckNickName.ts +++ b/packages/client/src/shared/hooks/useCheckNickName.ts @@ -1,18 +1,18 @@ import { useEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import { getSignInInfo } from 'shared/apis'; import { getShareLinkHostNickName } from 'shared/apis/share'; +import Cookies from 'js-cookie'; export default function useCheckNickName() { const location = useLocation(); const [page, setPage] = useState(''); const [nickName, setNickName] = useState(''); const [status, setStatus] = useState(''); - const [owner, setOwner] = useState(''); + const { hostNickname } = useParams(); useEffect(() => { const path = location.pathname.split('/')[1]; - const hostNickName = location.pathname.split('/')[2]; switch (path) { case 'home': @@ -21,19 +21,19 @@ export default function useCheckNickName() { const res = await getSignInInfo(); setNickName(res.nickname); setStatus(res.status); - setOwner(res.nickname); + Cookies.set('userName', res.nickname); })(); break; case 'search': setPage('search'); - setNickName(hostNickName); + setNickName(hostNickname!); break; case 'guest': setPage('guest'); (async () => { - const res = await getShareLinkHostNickName(hostNickName); + const res = await getShareLinkHostNickName(hostNickname!); setNickName(res); })(); break; @@ -42,5 +42,5 @@ export default function useCheckNickName() { } }, [location]); - return { page, nickName, status, owner }; + return { page, nickName, status }; } diff --git a/packages/client/src/shared/ui/alert/Alert.tsx b/packages/client/src/shared/ui/alert/Alert.tsx index c9188f7..6a79f10 100644 --- a/packages/client/src/shared/ui/alert/Alert.tsx +++ b/packages/client/src/shared/ui/alert/Alert.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { Body02ME, Title01 } from '../styles'; import { css } from '@emotion/react'; -import { Button } from '..'; +import { Button, ModalPortal } from '..'; import { useState, useEffect } from 'react'; interface PropsTypes extends React.HTMLAttributes { @@ -29,23 +29,25 @@ export default function Alert({ title, description, ...args }: PropsTypes) { }, []); return ( - - - {title} - {description && {description}} - - - - - - + + + + {title} + {description && {description}} + + + + + + + ); } diff --git a/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx b/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx index 2ed1b0f..62e5499 100644 --- a/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx +++ b/packages/client/src/shared/ui/alertDialog/AlertDialog.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { Body02ME, Title01 } from '../styles'; import { css } from '@emotion/react'; -import { Button } from '..'; +import { Button, ModalPortal } from '..'; interface PropsTypes extends React.HTMLAttributes { title: string; @@ -24,32 +24,34 @@ export default function AlertDialog({ ...args }: PropsTypes) { return ( - - - {title} - {description && {description}} + + + + {title} + {description && {description}} - - - - - - + + + + + + + ); } diff --git a/packages/client/src/shared/ui/buttons/Button.tsx b/packages/client/src/shared/ui/buttons/Button.tsx index 3fe7d82..d03976a 100644 --- a/packages/client/src/shared/ui/buttons/Button.tsx +++ b/packages/client/src/shared/ui/buttons/Button.tsx @@ -24,6 +24,7 @@ const CustomButton = styled.button` gap: 2px; border-radius: 4px; box-shadow: 0px 2px 10px 5px rgba(13, 111, 252, 0.1); + white-space: nowrap; &:disabled { cursor: default; diff --git a/packages/client/src/shared/ui/buttons/IconButton.tsx b/packages/client/src/shared/ui/buttons/IconButton.tsx index d572ed3..39db246 100644 --- a/packages/client/src/shared/ui/buttons/IconButton.tsx +++ b/packages/client/src/shared/ui/buttons/IconButton.tsx @@ -18,6 +18,7 @@ const CustomButton = styled.button` padding: 8px; gap: 10px; border-radius: 8px; + white-space: nowrap; ${({ theme: { colors } }) => css` border: 1px solid ${colors.stroke.default}; diff --git a/packages/client/src/shared/ui/buttons/TextButton.tsx b/packages/client/src/shared/ui/buttons/TextButton.tsx index 874fc6e..afaf6c4 100644 --- a/packages/client/src/shared/ui/buttons/TextButton.tsx +++ b/packages/client/src/shared/ui/buttons/TextButton.tsx @@ -22,6 +22,7 @@ const CustomButton = styled.button` gap: 4px; background: none; border: none; + white-space: nowrap; ${({ size, theme: { colors } }) => css` ${size === 'm' ? Body02ME : Body03ME} diff --git a/packages/client/src/shared/ui/modal/Modal.tsx b/packages/client/src/shared/ui/modal/Modal.tsx index 0a9601c..6d28be9 100644 --- a/packages/client/src/shared/ui/modal/Modal.tsx +++ b/packages/client/src/shared/ui/modal/Modal.tsx @@ -3,9 +3,9 @@ import { Body02ME, Title02 } from '../styles'; import { ReactNode } from 'react'; import { css } from '@emotion/react'; import goBackIcon from '@icons/icon-back-32-white.svg'; -import { IconButton } from '..'; +import { IconButton, ModalPortal } from '..'; -interface PropsTypes extends React.HTMLAttributes { +interface PropsTypes extends React.HTMLAttributes { title: string; children: ReactNode; @@ -29,35 +29,37 @@ export default function Modal({ const isButtonExist = leftButton || rightButton; return ( - - - {onClickGoBack && ( - - λ’€λ‘œκ°€κΈ° λ²„νŠΌ - - )} - - - - - {title} - {topButton} - - - {description && {description}} - - - {children} - - {isButtonExist && ( - -
{leftButton}
-
{rightButton}
-
+ + + + {onClickGoBack && ( + + λ’€λ‘œκ°€κΈ° λ²„νŠΌ + )} -
-
-
+ + + + + {title} + {topButton} + + + {description && {description}} + + + {children} + + {isButtonExist && ( + +
{leftButton}
+
{rightButton}
+
+ )} +
+ + + ); } @@ -70,7 +72,7 @@ const Overlay = styled.div` z-index: 998; `; -const Layout = styled.div` +const Layout = styled.form` position: absolute; top: 50%; left: 50%; diff --git a/packages/client/src/shared/ui/textArea/TextArea.tsx b/packages/client/src/shared/ui/textArea/TextArea.tsx index d5f87df..8087dc9 100644 --- a/packages/client/src/shared/ui/textArea/TextArea.tsx +++ b/packages/client/src/shared/ui/textArea/TextArea.tsx @@ -98,9 +98,8 @@ const Tabs = styled.ul` } `; -const Tab = styled.li` +const Tab = styled.div` cursor: pointer; - list-style: none; `; const TextInput = styled.textarea<{ css: SerializedStyles }>` @@ -150,6 +149,7 @@ const Wrapper = styled.div<{ css: SerializedStyles }>` margin-top: 9px; margin-right: 9px; color: ${theme.colors.text.secondary}; + word-break: break-all; & ol { padding-left: 40px; diff --git a/packages/client/src/widgets/galaxyCustomModal/GalaxyCustomModal.tsx b/packages/client/src/widgets/galaxyCustomModal/GalaxyCustomModal.tsx index bdf2c51..258707f 100644 --- a/packages/client/src/widgets/galaxyCustomModal/GalaxyCustomModal.tsx +++ b/packages/client/src/widgets/galaxyCustomModal/GalaxyCustomModal.tsx @@ -26,9 +26,11 @@ export default function GalaxyCustomModal() { useRefresh('CUSTOM'); - const handleSubmit = async () => { + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); if (isSubmitButtonDisabled) return; setIsSubmitButtonDisabled(true); + const galaxyStyle = { spiral: galaxy.spiral !== spiral ? spiral : undefined, start: galaxy.start !== start ? start : undefined, @@ -40,50 +42,48 @@ export default function GalaxyCustomModal() { galaxy.setStart(start); galaxy.setThickness(thickness); galaxy.setZDist(zDist); - await postGalaxy(galaxyStyle); + + try { + await postGalaxy(galaxyStyle); + + setToast({ text: 'μ€ν•˜κ°€ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', type: 'success' }); + navigate('/home'); + setView('MAIN'); + } finally { + setIsSubmitButtonDisabled(false); + } }; return ( -
{ - e.preventDefault(); - await handleSubmit(); - setToast({ text: 'μ€ν•˜κ°€ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', type: 'success' }); - - setIsSubmitButtonDisabled(false); - navigate('/home'); - setView('MAIN'); + { + setDialog(true); }} + rightButton={} + leftButton={} + topButton={} + onSubmit={handleSubmit} > - { - setDialog(true); - }} - rightButton={} - leftButton={} - topButton={} - > - - - - - {dialog && ( - setDialog(false)} - onClickActionButton={() => { - navigate('/home'); - setView('MAIN'); - }} - disabled={false} - /> - )} - - + + + + + {dialog && ( + setDialog(false)} + onClickActionButton={() => { + navigate('/home'); + setView('MAIN'); + }} + disabled={false} + /> + )} +
); } diff --git a/packages/client/src/widgets/landingScreen/LandingScreen.tsx b/packages/client/src/widgets/landingScreen/LandingScreen.tsx index 25290ad..2632cf1 100644 --- a/packages/client/src/widgets/landingScreen/LandingScreen.tsx +++ b/packages/client/src/widgets/landingScreen/LandingScreen.tsx @@ -3,23 +3,33 @@ import BackgroundStars from 'features/backgroundStars/BackgroundStars.tsx'; import { Galaxy } from '../galaxy/index.ts'; import { EffectComposer, Bloom } from '@react-three/postprocessing'; import { CAMERA_POSITION, CAMERA_UP, CAMERA_FAR } from './lib/camera.ts'; +import { Group, Object3DEventMap } from 'three'; +import { useRef, useEffect } from 'react'; -interface PropsType { - mousePosition: number[]; -} - -export default function LandingScreen({ - mousePosition: [mouseX, mouseY], -}: PropsType) { +export default function LandingScreen() { + const galaxyRef = useRef>(null!); const camera = { position: CAMERA_POSITION, up: CAMERA_UP, far: CAMERA_FAR, }; + useEffect(() => { + const mouseHandler = (e: MouseEvent) => { + const mouseX = e.clientX / window.innerWidth; + const mouseY = e.clientY / window.innerHeight; + galaxyRef.current.rotation.x = (mouseY - 0.5) / 5; + galaxyRef.current.rotation.y = (mouseX - 0.5) / 5; + }; + + window.addEventListener('mousemove', mouseHandler); + + return () => window.removeEventListener('mousemove', mouseHandler); + }, []); + return (
- + - + diff --git a/packages/client/src/widgets/loginModal/LoginModal.tsx b/packages/client/src/widgets/loginModal/LoginModal.tsx index d8d34cd..b89c0ef 100644 --- a/packages/client/src/widgets/loginModal/LoginModal.tsx +++ b/packages/client/src/widgets/loginModal/LoginModal.tsx @@ -40,29 +40,26 @@ export default function LoginModal() { }; return ( -
} + rightButton={ + + } + leftButton={ navigate('/signup')} />} + onClickGoBack={() => navigate('/')} + style={{ width: '516px' }} onSubmit={(e) => { e.preventDefault(); handleLoginSubmit(); }} > - } - rightButton={ - - } - leftButton={ navigate('/signup')} />} - onClickGoBack={() => navigate('/')} - style={{ width: '516px' }} - > - - - + + ); } diff --git a/packages/client/src/widgets/nickNameSetModal/NickNameSetModal.tsx b/packages/client/src/widgets/nickNameSetModal/NickNameSetModal.tsx index f09974d..dceac19 100644 --- a/packages/client/src/widgets/nickNameSetModal/NickNameSetModal.tsx +++ b/packages/client/src/widgets/nickNameSetModal/NickNameSetModal.tsx @@ -15,28 +15,31 @@ export default function NickNameSetModal() { const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false); const handleSaveButton = async () => { - if (isSaveButtonDisabled) return; + if (isSaveButtonDisabled) return; setIsSaveButtonDisabled(true); - let response; - if (!platform) { - response = await postSignUp({ - username: id, - password: pw, - nickname: validNickName, - }); - } else { - response = await postSignUp({ - nickname: validNickName, - platform: platform, - }); - } + try { + let response; + if (!platform) { + response = await postSignUp({ + username: id, + password: pw, + nickname: validNickName, + }); + } else { + response = await postSignUp({ + nickname: validNickName, + platform: platform, + }); + } - if (response) { - navigate('/login'); - setToast({ text: 'νšŒμ›κ°€μž…μ΄ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', type: 'success' }); + if (response) { + navigate('/login'); + setToast({ text: 'νšŒμ›κ°€μž…μ΄ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', type: 'success' }); + } + } finally { + setIsSaveButtonDisabled(false); } - setIsSaveButtonDisabled(false); }; const handleGoBackButton = () => { diff --git a/packages/client/src/widgets/screen/Screen.tsx b/packages/client/src/widgets/screen/Screen.tsx index 8c9aea7..2c5747b 100644 --- a/packages/client/src/widgets/screen/Screen.tsx +++ b/packages/client/src/widgets/screen/Screen.tsx @@ -9,7 +9,7 @@ import { useCameraStore } from 'shared/store/useCameraStore.ts'; import { Posts } from 'entities/posts'; import styled from '@emotion/styled'; import { useViewStore } from 'shared/store'; -import { CameraLight } from './ui'; +import { CameraLight, LevaTheme } from './ui'; import { useState } from 'react'; import { PerformanceMonitor } from '@react-three/drei'; @@ -22,12 +22,12 @@ export default function Screen() { const { cameraToCurrentView, setCameraToCurrentView } = useCameraStore(); - const { intensity, mipmapBlur } = useControls('Bloom', { - intensity: { value: 0.4, min: 0, max: 1.5, step: 0.01 }, - mipmapBlur: { value: false }, + const { 밝기, λΈ”λŸ¬νš¨κ³Ό } = useControls('별 속성', { + 밝기: { value: 0.4, min: 0, max: 1.5, step: 0.01 }, + λΈ”λŸ¬νš¨κ³Ό: { value: false }, }); - const { wheelSpeed } = useControls('Controls', { - wheelSpeed: { value: 3, min: 0.1, max: 30, step: 0.1 }, + const { νœ μ†λ„ } = useControls('컨트둀', { + νœ μ†λ„: { value: 3, min: 0.1, max: 30, step: 0.1 }, }); const [dpr, setDpr] = useState(1); @@ -38,19 +38,18 @@ export default function Screen() { dpr={dpr} camera={camera} onWheel={(e) => - setCameraToCurrentView(cameraToCurrentView + e.deltaY * wheelSpeed) + setCameraToCurrentView(cameraToCurrentView + e.deltaY * νœ μ†λ„) } - frameloop="demand" > { - setDpr(0.5 + factor); + setDpr(0.5 + factor / 2); }} /> @@ -66,7 +65,14 @@ export default function Screen() {
-
); @@ -74,7 +80,7 @@ export default function Screen() { const LevaWrapper = styled.div` position: absolute; - top: 5%; + top: 37px; right: 50%; transform: translateX(50%); z-index: 100; diff --git a/packages/client/src/widgets/screen/ui/LevaTheme.ts b/packages/client/src/widgets/screen/ui/LevaTheme.ts new file mode 100644 index 0000000..90f9e5c --- /dev/null +++ b/packages/client/src/widgets/screen/ui/LevaTheme.ts @@ -0,0 +1,24 @@ +import { LevaCustomTheme } from 'leva/dist/declarations/src/styles'; +import theme from 'shared/ui/styles/theme'; + +const color = theme.colors; + +export const LevaTheme: LevaCustomTheme = { + colors: { + elevation1: color.background.bdp02, + elevation2: color.background.bdp01_80, + elevation3: color.background.bdp03, + accent1: color.stroke.focus, + accent2: color.primary.filled, + accent3: color.primary.pressed, + highlight1: color.stroke.sc, + highlight2: color.text.third, + highlight3: color.text.secondary, + }, + fontSizes: { + root: '12px', + }, + fonts: { + mono: 'pretendard_medium', + }, +}; diff --git a/packages/client/src/widgets/screen/ui/index.ts b/packages/client/src/widgets/screen/ui/index.ts index b7d9697..33950ca 100644 --- a/packages/client/src/widgets/screen/ui/index.ts +++ b/packages/client/src/widgets/screen/ui/index.ts @@ -1 +1,2 @@ export { default as CameraLight } from './CameraLight'; +export * from './LevaTheme'; diff --git a/packages/client/src/widgets/shareModal/ui/LinkContainer.tsx b/packages/client/src/widgets/shareModal/ui/LinkContainer.tsx index 1b91c6f..e10cd98 100644 --- a/packages/client/src/widgets/shareModal/ui/LinkContainer.tsx +++ b/packages/client/src/widgets/shareModal/ui/LinkContainer.tsx @@ -3,22 +3,22 @@ import { useToastStore } from 'shared/store'; import { Button, Input } from 'shared/ui'; import { useState, useEffect } from 'react'; import { getShareLink } from 'shared/apis/share'; -import useCheckNickName from 'shared/hooks/useCheckNickName'; +import Cookies from 'js-cookie'; export default function LinkContainer() { const [shareLink, setShareLink] = useState(''); const { setToast } = useToastStore(); - const { owner } = useCheckNickName(); + const user = Cookies.get('userName'); useEffect(() => { - if (!owner) return; + if (!user) return; (async () => { - const shareLinkData = await getShareLink(owner); + const shareLinkData = await getShareLink(user); setShareLink('https://www.xn--bj0b03z.site/' + 'guest/' + shareLinkData); })(); - }, [owner]); + }, [user]); const copyToClipboard = () => { navigator.clipboard.writeText(shareLink); diff --git a/packages/client/src/widgets/starCustomModal/StarCustomModal.tsx b/packages/client/src/widgets/starCustomModal/StarCustomModal.tsx index de6c87d..bb32cfe 100644 --- a/packages/client/src/widgets/starCustomModal/StarCustomModal.tsx +++ b/packages/client/src/widgets/starCustomModal/StarCustomModal.tsx @@ -57,25 +57,26 @@ export default function StarCustomModal() { const handleSubmit = async () => { if (isSubmitButtonDisabled) return; setIsSubmitButtonDisabled(true); - const existingStars = await getMyPost(); - const starData = { - shape: shapeTypes[shape], - color, - size, - brightness, - position: await generateStarPosition(existingStars, size), - }; - const formData = new FormData(); + try { + const existingStars = await getMyPost(); + const starData = { + shape: shapeTypes[shape], + color, + size, + brightness, + position: await generateStarPosition(existingStars, size), + }; + const formData = new FormData(); - formData.append('star', JSON.stringify(starData)); - formData.append('title', title); - formData.append('content', content); + formData.append('star', JSON.stringify(starData)); + formData.append('title', title); + formData.append('content', content); - if (files) { - for (let i = 0; i < files.length; i++) formData.append('file', files[i]); - } + if (files) { + for (let i = 0; i < files.length; i++) + formData.append('file', files[i]); + } - try { await sendPost(formData); setToast({ text: '별을 μƒμ„±ν–ˆμŠ΅λ‹ˆλ‹€.', type: 'success' }); setView('MAIN'); @@ -97,33 +98,32 @@ export default function StarCustomModal() { ); return ( -
e.preventDefault()}> - - - e.preventDefault()} + > + + + + + + + + - - - - - - - - - -
+ + + ); } diff --git a/packages/client/src/widgets/starCustomModal/ui/SentimentButton.tsx b/packages/client/src/widgets/starCustomModal/ui/SentimentButton.tsx index b8bad4e..a9c165d 100644 --- a/packages/client/src/widgets/starCustomModal/ui/SentimentButton.tsx +++ b/packages/client/src/widgets/starCustomModal/ui/SentimentButton.tsx @@ -17,9 +17,12 @@ export default function SentimentButton({ content, setColor }: PropsType) { const handleRecommendColor = async () => { if (isButtonDisabled) return; setIsButtonDisabled(true); - const res = await getSentimentColor(content); - if (res?.status === 200) setColor(res.data.color); - setIsButtonDisabled(false); + try { + const res = await getSentimentColor(content); + setColor(res.data.color); + } finally { + setIsButtonDisabled(false); + } }; return ( diff --git a/packages/client/src/widgets/underBar/UnderBar.tsx b/packages/client/src/widgets/underBar/UnderBar.tsx index fffb381..61408e8 100644 --- a/packages/client/src/widgets/underBar/UnderBar.tsx +++ b/packages/client/src/widgets/underBar/UnderBar.tsx @@ -3,7 +3,7 @@ import { Button } from 'shared/ui'; import { Title01 } from '../../shared/ui/styles'; import PlanetEditIcon from '@icons/icon-planetedit-24-white.svg'; import WriteIcon from '@icons/icon-writte-24-white.svg'; -import { BASE_URL, MAX_WIDTH1, MAX_WIDTH2 } from 'shared/lib/constants'; +import { MAX_WIDTH1, MAX_WIDTH2 } from 'shared/lib/constants'; import { useNavigate } from 'react-router-dom'; import Cookies from 'js-cookie'; import instance from 'shared/apis/core/AxiosInterceptor'; @@ -31,14 +31,18 @@ export default function UnderBar() { const handleLogoutButton = async () => { if (isLogoutButtonDisabled) return; setIsLogoutButtonDisabled(true); - await instance.get(`${BASE_URL}auth/signout`); - - Cookies.remove('accessToken'); - Cookies.remove('refreshToken'); - reset(); - - setIsLogoutButtonDisabled(false); - navigate('/'); + try { + await instance.get(`/auth/signout`); + + Cookies.remove('accessToken'); + Cookies.remove('refreshToken'); + Cookies.remove('userName'); + reset(); + + navigate('/'); + } finally { + setIsLogoutButtonDisabled(false); + } }; const handleShareButton = () => { @@ -60,55 +64,58 @@ export default function UnderBar() { {nickName}λ‹˜μ˜ μ€ν•˜ - + {page === 'home' && } - - - + {page !== 'guest' && ( + + )} + + {isMyPage && ( + + )} - - - μ€ν•˜ μˆ˜μ •ν•˜κΈ° - μ€ν•˜ μˆ˜μ •ν•˜κΈ° - - - κΈ€μ“°κΈ° - κΈ€μ“°κΈ° - - + {isMyPage && ( + + + μ€ν•˜ μˆ˜μ •ν•˜κΈ° + μ€ν•˜ μˆ˜μ •ν•˜κΈ° + + + κΈ€μ“°κΈ° + κΈ€μ“°κΈ° + + + )} ); @@ -144,7 +151,7 @@ const Layout = styled.div<{ view: string }>` const NameContainer = styled.div` display: flex; align-items: center; - gap: 24px; + gap: 16px; `; const ButtonsContainer = styled.div` diff --git a/packages/client/src/widgets/upperBar/UpperBar.tsx b/packages/client/src/widgets/upperBar/UpperBar.tsx index 6af6f24..9fd52f0 100644 --- a/packages/client/src/widgets/upperBar/UpperBar.tsx +++ b/packages/client/src/widgets/upperBar/UpperBar.tsx @@ -3,11 +3,11 @@ import { IconButton, Search } from 'shared/ui'; import goBackIcon from '@icons/icon-back-32-white.svg'; import { MAX_WIDTH1, MAX_WIDTH2 } from '@constants'; import { useState, useEffect } from 'react'; -import { getNickNames } from 'shared/apis/search'; -import { getIsAvailableNickName } from 'shared/apis'; +import { checkExistNickname, getNickNames } from 'shared/apis/search'; import { useToastStore, useViewStore } from 'shared/store'; import { useNavigate } from 'react-router-dom'; import useCheckNickName from 'shared/hooks/useCheckNickName'; +import Cookies from 'js-cookie'; export default function UpperBar() { // TODO: ui λΆ„λ¦¬ν•˜κΈ° @@ -17,8 +17,9 @@ export default function UpperBar() { const [isSearchButtonDisabled, setIsSearchButtonDisabled] = useState(false); const { setToast } = useToastStore(); - const { page, nickName, owner } = useCheckNickName(); + const { page, nickName } = useCheckNickName(); const { view } = useViewStore(); + const user = Cookies.get('userName'); const navigate = useNavigate(); @@ -44,7 +45,7 @@ export default function UpperBar() { const nickNameDatas = await getNickNames(debouncedSearchValue); const nickNames = nickNameDatas .map((data: { nickname: string; id: number }) => data.nickname) - .filter((nickName: string) => nickName !== owner) + .filter((nickName: string) => nickName !== user) .slice(0, 5); setSearchResults(nickNames); @@ -55,19 +56,21 @@ export default function UpperBar() { if (isSearchButtonDisabled) return; setIsSearchButtonDisabled(true); try { - await getIsAvailableNickName(searchValue); - setToast({ text: 'μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λ‹‰λ„€μž„μž…λ‹ˆλ‹€.', type: 'error' }); - } catch (error) { - if (searchValue === owner) + await checkExistNickname(searchValue); + if (searchValue === user) return setToast({ text: 'λ‚΄ μ€ν•˜λ‘œλŠ” 이동할 수 μ—†μŠ΅λ‹ˆλ‹€.', type: 'error', }); - navigate(`/search/${searchValue}`); setSearchValue(''); setDebouncedSearchValue(''); setSearchResults([]); + } catch (error) { + setToast({ + text: 'μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μœ μ €μž…λ‹ˆλ‹€.', + type: 'error', + }); } finally { setIsSearchButtonDisabled(false); } @@ -93,16 +96,18 @@ export default function UpperBar() { λ’€λ‘œκ°€κΈ° -
- -
+ {page !== 'guest' && ( +
+ +
+ )} ); } diff --git a/packages/client/src/widgets/warpScreen/WarpScreen.tsx b/packages/client/src/widgets/warpScreen/WarpScreen.tsx index ebc075a..7085471 100644 --- a/packages/client/src/widgets/warpScreen/WarpScreen.tsx +++ b/packages/client/src/widgets/warpScreen/WarpScreen.tsx @@ -12,12 +12,15 @@ import { import { EffectComposer, Bloom } from '@react-three/postprocessing'; import SpaceWarp from './ui/SpaceWarp'; import BrightSphere from './ui/BrightSphere'; +import styled from '@emotion/styled'; +import { keyframes } from '@emotion/react'; interface PropsType { - setIsSwitching: React.Dispatch>; + isSwitching: 'warp' | 'fade' | 'end'; + setIsSwitching: React.Dispatch>; } -export default function WarpScreen({ setIsSwitching }: PropsType) { +export default function WarpScreen({ isSwitching, setIsSwitching }: PropsType) { const camera = { position: SPACE_WARP_CAMERA_POSITION, up: SPACE_WARP_CAMERA_UP, @@ -31,6 +34,7 @@ export default function WarpScreen({ setIsSwitching }: PropsType) { zIndex: 999, backgroundColor: '#000000', }; + if (isSwitching === 'fade') return ; return ( @@ -50,3 +54,24 @@ export default function WarpScreen({ setIsSwitching }: PropsType) { ); } + +const fadeout = keyframes` + 0% { + opacity: 1; + } + 100% { + opacity: 0; + display: none; + } +`; + +const FadeoutScreen = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 101; + background-color: white; + animation: ${fadeout} 0.5s linear forwards; +`; diff --git a/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx b/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx index 19f00bc..9764c1f 100644 --- a/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx +++ b/packages/client/src/widgets/warpScreen/ui/SpaceWarp.tsx @@ -34,7 +34,7 @@ const geSpaceWarpLinesInfo = () => { }; interface PropsType { - setIsSwitching: React.Dispatch>; + setIsSwitching: React.Dispatch>; } export default function SpaceWarp({ setIsSwitching }: PropsType) { @@ -43,8 +43,10 @@ export default function SpaceWarp({ setIsSwitching }: PropsType) { useFrame((state, delta) => { if (state.camera.position.y <= 0) { state.scene.background = new THREE.Color(0xffffff); - setIsSwitching(false); - } else state.camera.position.y -= 75000 * delta; + setIsSwitching('fade'); + return; + } + state.camera.position.y -= 75000 * delta; }); return ( diff --git a/packages/server/package.json b/packages/server/package.json index 212fcff..9cd4c33 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -28,6 +28,7 @@ "@nestjs/passport": "^10.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.16", + "@nestjs/throttler": "^5.1.0", "@nestjs/typeorm": "^10.0.0", "@types/multer": "^1.4.11", "@types/sharp": "^0.32.0", diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index 25e4be2..22765b6 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -9,6 +9,7 @@ import { StarModule } from './star/star.module'; import { GalaxyModule } from './galaxy/galaxy.module'; import { AdminModule } from './admin/admin.module'; import { SentimentModule } from './sentiment/sentiment.module'; +import { ThrottlerModule } from '@nestjs/throttler'; @Module({ imports: [ @@ -20,6 +21,12 @@ import { SentimentModule } from './sentiment/sentiment.module'; GalaxyModule, AdminModule, SentimentModule, + ThrottlerModule.forRoot([ + { + ttl: 10000, // 10μ΄ˆμ— + limit: 5, // 5λ²ˆκΉŒμ§€ μš”μ²­ κ°€λŠ₯ + }, + ]), ], }) export class AppModule {} diff --git a/packages/server/src/board/board.controller.ts b/packages/server/src/board/board.controller.ts index 48f856a..a2bcfe6 100644 --- a/packages/server/src/board/board.controller.ts +++ b/packages/server/src/board/board.controller.ts @@ -36,6 +36,7 @@ import { DeleteResult } from 'typeorm'; import { GetIsLikedSwaggerDecorator } from './decorators/swagger/get-is-liked-swagged.decorator'; import { HttpExceptionFilter } from '../exception-filter/http.exception-filter'; import { BoardService } from './board.service'; +import { ThrottlerGuard } from '@nestjs/throttler'; @Controller('post') @UseInterceptors(LogInterceptor) @@ -46,6 +47,7 @@ export class BoardController { @Post() @UseGuards(CookieAuthGuard) + @UseGuards(ThrottlerGuard) @UseInterceptors(FilesInterceptor('file', 5)) @UsePipes(ValidationPipe) @CreateBoardSwaggerDecorator() @@ -113,6 +115,7 @@ export class BoardController { @Patch(':id/like') @UseGuards(CookieAuthGuard) + @UseGuards(ThrottlerGuard) @UsePipes(ValidationPipe) @PatchLikeSwaggerDecorator() async patchLike( diff --git a/packages/server/src/sentiment/sentiment.controller.ts b/packages/server/src/sentiment/sentiment.controller.ts index a9dce9e..4342bc4 100644 --- a/packages/server/src/sentiment/sentiment.controller.ts +++ b/packages/server/src/sentiment/sentiment.controller.ts @@ -1,6 +1,7 @@ -import { Body, Controller, HttpCode, Post } from '@nestjs/common'; +import { Body, Controller, HttpCode, Post, UseGuards } from '@nestjs/common'; import { SentimentService } from './sentiment.service'; import { GetSentimentDto } from './dto/get-sentiment.dto'; +import { ThrottlerGuard } from '@nestjs/throttler'; @Controller('sentiment') export class SentimentController { @@ -8,6 +9,7 @@ export class SentimentController { @Post() @HttpCode(200) + @UseGuards(ThrottlerGuard) getSentiment(@Body() body: GetSentimentDto) { return this.sentimentService.getSentiment(body); } diff --git a/yarn.lock b/yarn.lock index ee669ab..32e883d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1858,6 +1858,19 @@ __metadata: languageName: node linkType: hard +"@nestjs/throttler@npm:^5.1.0": + version: 5.1.0 + resolution: "@nestjs/throttler@npm:5.1.0" + dependencies: + md5: "npm:^2.2.1" + peerDependencies: + "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + "@nestjs/core": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 + checksum: 8dc900ca1d2ad303d05b248ab72bfd3d8851b76bb8be407523cd25d7285e3da19160d7023d9a069a9ef0965e2851a7b3407e677ebb36edcc4b42a90d497e21a0 + languageName: node + linkType: hard + "@nestjs/typeorm@npm:^10.0.0": version: 10.0.0 resolution: "@nestjs/typeorm@npm:10.0.0" @@ -4908,6 +4921,13 @@ __metadata: languageName: node linkType: hard +"charenc@npm:0.0.2": + version: 0.0.2 + resolution: "charenc@npm:0.0.2" + checksum: a45ec39363a16799d0f9365c8dd0c78e711415113c6f14787a22462ef451f5013efae8a28f1c058f81fc01f2a6a16955f7a5fd0cd56247ce94a45349c89877d8 + languageName: node + linkType: hard + "chart.js@npm:^4.4.1": version: 4.4.1 resolution: "chart.js@npm:4.4.1" @@ -5474,6 +5494,13 @@ __metadata: languageName: node linkType: hard +"crypt@npm:0.0.2": + version: 0.0.2 + resolution: "crypt@npm:0.0.2" + checksum: adbf263441dd801665d5425f044647533f39f4612544071b1471962209d235042fb703c27eea2795c7c53e1dfc242405173003f83cf4f4761a633d11f9653f18 + languageName: node + linkType: hard + "crypto@npm:^1.0.1": version: 1.0.1 resolution: "crypto@npm:1.0.1" @@ -7552,6 +7579,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:~1.1.6": + version: 1.1.6 + resolution: "is-buffer@npm:1.1.6" + checksum: ae18aa0b6e113d6c490ad1db5e8df9bdb57758382b313f5a22c9c61084875c6396d50bbf49315f5b1926d142d74dfb8d31b40d993a383e0a158b15fea7a82234 + languageName: node + linkType: hard + "is-callable@npm:^1.1.3": version: 1.2.7 resolution: "is-callable@npm:1.2.7" @@ -8894,6 +8928,17 @@ __metadata: languageName: node linkType: hard +"md5@npm:^2.2.1": + version: 2.3.0 + resolution: "md5@npm:2.3.0" + dependencies: + charenc: "npm:0.0.2" + crypt: "npm:0.0.2" + is-buffer: "npm:~1.1.6" + checksum: 14a21d597d92e5b738255fbe7fe379905b8cb97e0a49d44a20b58526a646ec5518c337b817ce0094ca94d3e81a3313879c4c7b510d250c282d53afbbdede9110 + languageName: node + linkType: hard + "mdast-util-find-and-replace@npm:^3.0.0": version: 3.0.1 resolution: "mdast-util-find-and-replace@npm:3.0.1" @@ -11523,6 +11568,7 @@ __metadata: "@nestjs/schematics": "npm:^10.0.0" "@nestjs/swagger": "npm:^7.1.16" "@nestjs/testing": "npm:^10.0.0" + "@nestjs/throttler": "npm:^5.1.0" "@nestjs/typeorm": "npm:^10.0.0" "@types/cookie-parser": "npm:^1.4.6" "@types/express": "npm:^4.17.17"