From 4b41aee65900d10832e92e9bce37c9fa1cebc633 Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 4 Nov 2024 17:01:32 +0900 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20[BE]=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..5d5a45d --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +name: Deploy To EC2 + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Github Repository 파일 불러오기 + uses: actions/checkout@v4 + + - name: Node 설치 + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: 의존성(라이브러리) 설치 + run: npm ci + + - name: .env 파일 만들기 + run: | + echo '${{ secrets.ENV }}' > .env + + - name: 테스트 코드 실행 + run: npm run test + + - name: 빌드 + run: npm run build + + - name: github-action 컴퓨터에서 압축하기 + run: tar -czvf project.tar.gz dist .env package.json package-lock.json + + - name: SCP로 EC2에 빌드된 파일 전송하기 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + source: project.tar.gz + target: /root/nest-server/tobe + + - name: SSH로 EC2에 접속하기 + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + script_stop: true + script: | + rm -rf /root/nest-server/current + mkdir /root/nest-server/current + mv /root/nest-server/tobe/project.tar.gz /root/nest-server/current/project.tar.gz + cd /root/nest-server/current + tar -xvf project.tar.gz + npm i + pm2 kill + pm2 start dist/main.js --name "backend-server" \ No newline at end of file From 99a344efbbd73289fa88de137b4a3a7f77e153fb Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 4 Nov 2024 17:05:32 +0900 Subject: [PATCH 02/18] =?UTF-8?q?chore:=20[BE]=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EB=9D=84=EC=96=B4=EC=93=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5d5a45d..8af5b0e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,7 +22,7 @@ jobs: - name: .env 파일 만들기 run: | - echo '${{ secrets.ENV }}' > .env + echo '${{ secrets.ENV }}' > .env - name: 테스트 코드 실행 run: npm run test From ab4745fdceba0f7f2af3cf047bdec34a7ea9a24c Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 4 Nov 2024 17:14:12 +0900 Subject: [PATCH 03/18] =?UTF-8?q?fix:=20[BE]=20=EC=9E=90=EB=8F=99=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EA=B2=BD=EB=A1=9C=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit package.json이 root/BE 경로에 있음 --- .github/workflows/deploy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8af5b0e..4c9c179 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,6 +8,10 @@ on: jobs: deploy: runs-on: ubuntu-latest + defaults: + run: + working-directory: ./BE # BE 디렉토리를 작업 디렉토리로 설정 + steps: - name: Github Repository 파일 불러오기 uses: actions/checkout@v4 From c267c9d25ba8c1a91a065f6951d7d246c6e33127 Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 4 Nov 2024 17:32:27 +0900 Subject: [PATCH 04/18] =?UTF-8?q?fix:=20[BE]=20=EC=9E=90=EB=8F=99=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 압축할파일 수정 --- .github/workflows/deploy.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4c9c179..47e840f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,7 +11,7 @@ jobs: defaults: run: working-directory: ./BE # BE 디렉토리를 작업 디렉토리로 설정 - + steps: - name: Github Repository 파일 불러오기 uses: actions/checkout@v4 @@ -35,7 +35,24 @@ jobs: run: npm run build - name: github-action 컴퓨터에서 압축하기 - run: tar -czvf project.tar.gz dist .env package.json package-lock.json + run: | + # 현재 위치 확인 (디버깅용) + pwd + + # BE와 FE 모든 필요 파일들을 함께 압축 + tar -czvf project.tar.gz \ + BE/dist \ + BE/package.json \ + BE/package-lock.json \ + BE/.env \ + FE/build \ + FE/package.json \ + FE/package-lock.json \ + FE/.env + + # 압축 파일 내용 확인 (디버깅용) + echo "Created archive with contents:" + tar -tvf project.tar.gz - name: SCP로 EC2에 빌드된 파일 전송하기 uses: appleboy/scp-action@v0.1.7 From 57b88863e29e4cc61024ce480882cf15c06267e2 Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Mon, 4 Nov 2024 17:43:58 +0900 Subject: [PATCH 05/18] =?UTF-8?q?chore:=20[BE]=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fe, be 의존성 각각설치 --- .github/workflows/deploy.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 47e840f..d0c6f23 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,9 +8,9 @@ on: jobs: deploy: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./BE # BE 디렉토리를 작업 디렉토리로 설정 +# defaults: +# run: +# working-directory: ./BE # BE 디렉토리를 작업 디렉토리로 설정 steps: - name: Github Repository 파일 불러오기 @@ -21,7 +21,12 @@ jobs: with: node-version: "20" - - name: 의존성(라이브러리) 설치 + - name: BE 의존성 설치 + working-directory: ./BE + run: npm ci + + - name: FE 의존성 설치 + working-directory: ./FE run: npm ci - name: .env 파일 만들기 From 7f31922f15fb3a641664173b72e9bca38782c748 Mon Sep 17 00:00:00 2001 From: jw Date: Mon, 4 Nov 2024 22:14:47 +0900 Subject: [PATCH 06/18] =?UTF-8?q?docs:=20pr=5Ftemplate=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/pull_request_template.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 466e3eb..2c06430 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,7 @@ -- 제목 : feat(issue 번호): 기능명 - ex) feat(17): pull request template 작성 +- 제목 : [BE/FE] feat#이슈번호 기능명 + ex) [BE] feat#156 자동배포 구현 (확인 후 지워주세요) -▲ PR 제목 관련 추가 논의 후 수정 - ## ➕ 이슈 번호 - #이슈번호 @@ -22,14 +20,13 @@
- - -## 🎯 리뷰 요구사항 +## 🎯 리뷰 요구사항 (선택) - 특별히 봐줬으면 하는 부분이 있다면 적어주세요
## ✅ Check List + - [ ] merge할 브랜치의 위치를 확인했나요? - [ ] Label을 지정했나요? From aa86de34dd357c14721c34f8891ac81a89ac1fd2 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 16:35:47 +0900 Subject: [PATCH 07/18] =?UTF-8?q?chore:=20=EC=A0=88=EB=8C=80=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20@types/node=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/package-lock.json | 18 ++++++++++++++++++ FE/package.json | 1 + FE/tsconfig.app.json | 7 ++++++- FE/tsconfig.node.json | 7 ++++++- FE/vite.config.ts | 13 ++++++++++--- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/FE/package-lock.json b/FE/package-lock.json index 46bdf47..4c94a40 100644 --- a/FE/package-lock.json +++ b/FE/package-lock.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@eslint/js": "^9.13.0", + "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", @@ -1789,6 +1790,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.8" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -4776,6 +4787,13 @@ } } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/FE/package.json b/FE/package.json index 3e8d37a..933c281 100644 --- a/FE/package.json +++ b/FE/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@eslint/js": "^9.13.0", + "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", diff --git a/FE/tsconfig.app.json b/FE/tsconfig.app.json index f867de0..9e42e38 100644 --- a/FE/tsconfig.app.json +++ b/FE/tsconfig.app.json @@ -20,7 +20,12 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"] } diff --git a/FE/tsconfig.node.json b/FE/tsconfig.node.json index abcd7f0..6655e90 100644 --- a/FE/tsconfig.node.json +++ b/FE/tsconfig.node.json @@ -18,7 +18,12 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "include": ["vite.config.ts"] } diff --git a/FE/vite.config.ts b/FE/vite.config.ts index 8b0f57b..0604559 100644 --- a/FE/vite.config.ts +++ b/FE/vite.config.ts @@ -1,7 +1,14 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], -}) + resolve: { + alias: { + '@': path.resolve(process.cwd(), 'src') + } + }, + publicDir: 'public' +}); From 6d7bc85987991b1f414b3862774d05a30e1e88c3 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 16:52:03 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20tailwind.config=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/tailwind.config.js | 50 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/FE/tailwind.config.js b/FE/tailwind.config.js index de7dd6f..178b1d8 100644 --- a/FE/tailwind.config.js +++ b/FE/tailwind.config.js @@ -6,7 +6,53 @@ export default { "./node_modules/@mui/**/*.{js,ts,jsx,tsx}" ], theme: { - extend: {}, + extend: { + colors: { + main: '#7890E7' + }, + fontSize: { + l: '1.5rem', + m: '1rem', + r: '0.75rem', + s: '0.625rem', + }, + textColor: { + default: '#5F6E76', + weak: '#879298' + }, + backgroundColor: { + surface: { + default: 'white', + alt: '#F5F7F9' + } + }, + borderColor: { + default: '#879298' + }, + borderRadius: { + m: '1rem', + s: '0.5rem' + }, + dropShadow: { + popup: '0 4px 2px rgba(20, 33, 43, 0.02)' + } + }, }, - plugins: [], + plugins: [ + function ({ addUtilities, theme }) { + const newUtilities = { + '.component-default': { + borderWidth: '1px', + borderColor: '#879298', + borderRadius: theme('borderRadius.m'), + backgroundColor: theme('backgroundColor.default'), + }, + '.component-popup': { + borderRadius: theme('borderRadius.m'), + dropShadow: theme('dropShadow.popup'), + }, + }; + addUtilities(newUtilities, ['responsive', 'hover']); + } + ], } \ No newline at end of file From 0f9e39adeb17f7ae167c658f11adc2c73e6c4feb Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:17:34 +0900 Subject: [PATCH 09/18] =?UTF-8?q?fix:=20=ED=85=8C=EC=9D=BC=EC=9C=88?= =?UTF-8?q?=EB=93=9C=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/tailwind.config.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/FE/tailwind.config.js b/FE/tailwind.config.js index 178b1d8..b447f52 100644 --- a/FE/tailwind.config.js +++ b/FE/tailwind.config.js @@ -27,14 +27,14 @@ export default { } }, borderColor: { - default: '#879298' + default: 'E9E9E9' }, borderRadius: { m: '1rem', s: '0.5rem' }, dropShadow: { - popup: '0 4px 2px rgba(20, 33, 43, 0.02)' + default: '0 4px 2px rgba(20, 33, 43, 0.02)' } }, }, @@ -43,14 +43,20 @@ export default { const newUtilities = { '.component-default': { borderWidth: '1px', - borderColor: '#879298', + borderColor: theme('borderColor.default'), borderRadius: theme('borderRadius.m'), - backgroundColor: theme('backgroundColor.default'), + backgroundColor: theme('backgroundColor.surface.default'), }, '.component-popup': { borderRadius: theme('borderRadius.m'), - dropShadow: theme('dropShadow.popup'), + backgroundColor: theme('backgroundColor.surface.default'), + dropShadow: theme('dropShadow.default'), }, + '.center': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + } }; addUtilities(newUtilities, ['responsive', 'hover']); } From 67b5977716acd39c041e8aabbe9ce250edb4093d Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:18:08 +0900 Subject: [PATCH 10/18] =?UTF-8?q?design:=20=ED=8F=B0=ED=8A=B8=20=EB=B0=8F?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/index.css | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/FE/src/index.css b/FE/src/index.css index bd6213e..ca11f3a 100644 --- a/FE/src/index.css +++ b/FE/src/index.css @@ -1,3 +1,41 @@ +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-dynamic-subset.min.css'); + @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer base { + html { + font-family: 'Pretendard', ui-sans-serif, system-ui; + box-sizing: border-box; + color: #5f6e76; + } + *, + *::before, + *::after { + box-sizing: inherit; + } +} + +::-webkit-scrollbar { + width: 14px; + height: 14px; +} + +::-webkit-scrollbar-thumb { + outline: none; + border-radius: 10px; + border: 4px solid transparent; + box-shadow: inset 6px 6px 0 rgba(34, 34, 34, 0.2); + min-height: 100px; +} + +::-webkit-scrollbar-thumb:hover { + border: 4px solid transparent; + box-shadow: inset 6px 6px 0 rgba(34, 34, 34, 0.4); +} + +::-webkit-scrollbar-track { + box-shadow: none; + background-color: transparent; +} From 3595de16f7460776c182549b39e3aaa6a82ed160 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:19:43 +0900 Subject: [PATCH 11/18] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=EB=B0=A9=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/ClipboardCopy.tsx | 26 ++++++++++ FE/src/components/GameHeader.tsx | 29 +++++++++++ FE/src/components/HeaderBar.tsx | 7 +++ FE/src/components/Modal.tsx | 62 ++++++++++++++++++++++++ FE/src/components/ParticipantDisplay.tsx | 21 ++++++++ FE/src/components/QuizOptionBoard.tsx | 32 ++++++++++++ FE/src/components/QuizView.tsx | 21 ++++++++ FE/src/pages/GamePage.tsx | 52 ++++++++++++++++++++ 8 files changed, 250 insertions(+) create mode 100644 FE/src/components/ClipboardCopy.tsx create mode 100644 FE/src/components/GameHeader.tsx create mode 100644 FE/src/components/HeaderBar.tsx create mode 100644 FE/src/components/Modal.tsx create mode 100644 FE/src/components/ParticipantDisplay.tsx create mode 100644 FE/src/components/QuizOptionBoard.tsx create mode 100644 FE/src/components/QuizView.tsx create mode 100644 FE/src/pages/GamePage.tsx diff --git a/FE/src/components/ClipboardCopy.tsx b/FE/src/components/ClipboardCopy.tsx new file mode 100644 index 0000000..7fc2247 --- /dev/null +++ b/FE/src/components/ClipboardCopy.tsx @@ -0,0 +1,26 @@ +import { Button } from '@mui/material'; + +type ClipboardCopyProps = { + valueToCopy: string; + message: string; + className?: string; +}; + +export const ClipboardCopy: React.FC = ({ valueToCopy, message }) => { + const handleCopyToClipboard = (): void => { + navigator.clipboard + .writeText(valueToCopy) + .then(() => { + alert('클립보드에 복사되었습니다: '); + }) + .catch((err) => { + console.error('복사 실패:', err); + }); + }; + + return ( +
+ +
+ ); +}; diff --git a/FE/src/components/GameHeader.tsx b/FE/src/components/GameHeader.tsx new file mode 100644 index 0000000..1382324 --- /dev/null +++ b/FE/src/components/GameHeader.tsx @@ -0,0 +1,29 @@ +import { ClipboardCopy } from './ClipboardCopy'; +import Card from '@mui/material/Card'; +import { QuizPreview } from './QuizView'; + +export const GameHeader = () => { + // 임시값 + const pinNum = '123456'; + const linkURL = 'naver.com'; + return ( + +
+ + +
+
+ 퀴즈이름22 +
+ +
+ + +
+
+ ); +}; diff --git a/FE/src/components/HeaderBar.tsx b/FE/src/components/HeaderBar.tsx new file mode 100644 index 0000000..120c638 --- /dev/null +++ b/FE/src/components/HeaderBar.tsx @@ -0,0 +1,7 @@ +export const HeaderBar = () => { + return ( +
+ QuizGround +
+ ); +}; diff --git a/FE/src/components/Modal.tsx b/FE/src/components/Modal.tsx new file mode 100644 index 0000000..d6bfbe4 --- /dev/null +++ b/FE/src/components/Modal.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; + +type ModalProps = { + isOpen: boolean; + title: string; + placeholder?: string; + initialValue?: string; + onClose: () => void; + onSubmit: (value: string) => void; +}; + +export const Modal: React.FC = ({ + isOpen, + title, + placeholder, + initialValue = '', + onClose, + onSubmit +}) => { + const [inputValue, setInputValue] = useState(initialValue); + + if (!isOpen) return null; + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleSubmit = () => { + onSubmit(inputValue); + setInputValue(''); // 입력값 초기화 + }; + + return ( +
+
+

{title}

+ +
+ + {/* 닉네임 등록 모달에서 취소버튼은 없어야할것같은데 일단 넣어둠 */} + +
+
+
+ ); +}; diff --git a/FE/src/components/ParticipantDisplay.tsx b/FE/src/components/ParticipantDisplay.tsx new file mode 100644 index 0000000..60ea9cf --- /dev/null +++ b/FE/src/components/ParticipantDisplay.tsx @@ -0,0 +1,21 @@ +const samplePalyer = Array(100) + .fill(null) + .map((_, i) => ({ id: i, name: 'user' + i })); + +const ParticipantDisplay = () => { + return ( +
+
참가자
+
+ {samplePalyer.map((e, i) => ( +
+
{i + 1 + '. ' + e.name}
+ +
+ ))} +
+
+ ); +}; + +export default ParticipantDisplay; diff --git a/FE/src/components/QuizOptionBoard.tsx b/FE/src/components/QuizOptionBoard.tsx new file mode 100644 index 0000000..8db97cf --- /dev/null +++ b/FE/src/components/QuizOptionBoard.tsx @@ -0,0 +1,32 @@ +type Params = { + options: string[]; +}; + +const optionColors = [ + '#FF9AA2', // pastel red + '#FFB3BA', // pastel pink + '#FFDAC1', // pastel peach + '#FFE156', // pastel yellow + '#E2F0CB', // pastel green + '#B5EAD7', // pastel mint + '#C7CEEA', // pastel blue + '#A0C4FF', // pastel light blue + '#B9D8FF', // pastel lavender + '#C3B3E0' // pastel purple +]; + +export const QuizOptionBoard = ({ options }: Params) => { + return ( +
+ {options.map((option, i) => ( +
+ {i + 1 + '. ' + option} +
+ ))} +
+ ); +}; diff --git a/FE/src/components/QuizView.tsx b/FE/src/components/QuizView.tsx new file mode 100644 index 0000000..9b9afce --- /dev/null +++ b/FE/src/components/QuizView.tsx @@ -0,0 +1,21 @@ +const sampleQuizImage = + 'https://i.namu.wiki/i/fcBRfQZOo2eCcLsPe63ZCKbzOBizhxvSKUrzEBqaMfTMSOe8I81p9s2SY_YxDxCEArNkSh_mwUTrnqX6ITkfUp3ey-p2xz1I6hk1oIxKEH-n3RFlgczUZFTxiu5xnvQUKPEo8BIOiKclL0-kJgi79w.webp'; + +type Props = { + title: string; + description: string; +}; + +export const QuizPreview = ({ title, description }: Props) => { + return ( +
+
+ +
+
+
{title}
+
{description}
+
+
+ ); +}; diff --git a/FE/src/pages/GamePage.tsx b/FE/src/pages/GamePage.tsx new file mode 100644 index 0000000..bec9a75 --- /dev/null +++ b/FE/src/pages/GamePage.tsx @@ -0,0 +1,52 @@ +import Chat from '@/components/Chat'; +import ParticipantDisplay from '@/components/ParticipantDisplay'; +import { QuizOptionBoard } from '@/components/QuizOptionBoard'; +import { Modal } from '../components/Modal'; +import { useState } from 'react'; +import { GameHeader } from '@/components/GameHeader'; +import { HeaderBar } from '@/components/HeaderBar'; +// import { socketService } from '../api/socket'; + +export const GamePage = () => { + const [playerName, setPlayerName] = useState('asdf'); + const [isModalOpen, setIsModalOpen] = useState(true); + + const handleNameSubmit = (name: string) => { + setPlayerName(name); + // 닉네임 설정 소켓 요청 + // socketService.sendPlayerName(name); + setIsModalOpen(false); // 이름이 설정되면 모달 닫기 + }; + + return ( + <> + +
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ + setIsModalOpen(false)} + onSubmit={handleNameSubmit} + /> +
+
+ + ); +}; From 0a9d7eca1d25398b9edde0d0bfc8948310120cd4 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:20:00 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=B0=A9=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FE/src/App.tsx b/FE/src/App.tsx index 5e28b9c..7ef2cdb 100644 --- a/FE/src/App.tsx +++ b/FE/src/App.tsx @@ -1,6 +1,7 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { MainPage } from './pages/MainPage'; import { GameSetupPage } from './pages/GameSetupPage'; +import { GamePage } from './pages/GamePage'; function App() { return ( @@ -8,6 +9,7 @@ function App() { } /> } /> + } /> not found} /> From d9a04959c299520665ae1e0d8ba43770943f2bef Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:22:02 +0900 Subject: [PATCH 13/18] =?UTF-8?q?fix:=20=ED=83=80=EC=9E=85=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B9=A8=EA=B0=84=EC=A4=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/api/socket.ts | 32 ++++++++++++++++++-------------- FE/src/pages/GameSetupPage.tsx | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/FE/src/api/socket.ts b/FE/src/api/socket.ts index a259a14..96c055a 100644 --- a/FE/src/api/socket.ts +++ b/FE/src/api/socket.ts @@ -1,26 +1,26 @@ import { io, Socket } from 'socket.io-client'; import SocketEvents from '../constants/socketEvents'; -type SocketEvent = (typeof SocketEvents)[keyof typeof SocketEvents]; +// type SocketEvent = (typeof SocketEvents)[keyof typeof SocketEvents]; -interface ChatMessage { +type ChatMessage = { userId: string; message: string; -} +}; -interface CreateRoomPayload { +type CreateRoomPayload = { roomName: string; maxPlayers: number; gameMode: string; isPublic: boolean; -} +}; // 이벤트의 데이터 타입을 정의 -interface SocketDataMap { - chatMessage: ChatMessage; - createRoom: CreateRoomPayload; - // 다른 이벤트의 데이터 타입을 추가 -} +// type SocketDataMap = { +// chatMessage: ChatMessage; +// createRoom: CreateRoomPayload; +// // 다른 이벤트의 데이터 타입을 추가 +// }; class SocketService { private socket: Socket; @@ -44,10 +44,14 @@ class SocketService { } // 이벤트 수신 메서드 - on(event: T, callback: (data: SocketDataMap[T]) => void) { - this.socket.on(event, (data: SocketDataMap[T]) => { - callback(data); - }); + // on(event: T, callback: (data: SocketDataMap[T]) => void) { + // this.socket.on(event, (data: SocketDataMap[T]) => { + // callback(data); + // }); + // } + + sendPlayerName(name: string) { + this.socket.emit(SocketEvents.SET_PLAYER_NAME, { playerName: name }); } // 메시지 전송 메서드 diff --git a/FE/src/pages/GameSetupPage.tsx b/FE/src/pages/GameSetupPage.tsx index 59cbec4..ea961d8 100644 --- a/FE/src/pages/GameSetupPage.tsx +++ b/FE/src/pages/GameSetupPage.tsx @@ -57,7 +57,7 @@ export const GameSetupPage = () => { max={MAX_PLAYERS} step={1} value={maxPlayers} - onChange={(_, newValue) => setMaxPlayers(newValue)} + onChange={(_, newValue) => setMaxPlayers(newValue as number)} > 게임 모드 선택 From 5c56d47a0529719358785937b718a7d7c39589aa Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:22:34 +0900 Subject: [PATCH 14/18] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Chat.tsx | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 FE/src/components/Chat.tsx diff --git a/FE/src/components/Chat.tsx b/FE/src/components/Chat.tsx new file mode 100644 index 0000000..7e753ee --- /dev/null +++ b/FE/src/components/Chat.tsx @@ -0,0 +1,61 @@ +const sampleChat = Array(100) + .fill(null) + .map((_, i) => ({ name: 'user' + i, message: 'messagemessagemessagemessagemessagemessage' })); + +// type ChatProps = { +// cket: Socket; + +/* +const [messages, setMessages] = useState>([]); + const [inputValue, setInputValue] = useState(''); + + useEffect(() => { + // 서버에서 메시지를 받을 때 + socket.on('chat message', (message) => { + setMessages((prevMessages) => [...prevMessages, message]); + }); + + return () => { + socket.off('chat message'); + }; + }, [socket]); + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); // 폼 제출 기본 동작 방지 + + if (inputValue.trim()) { + const newMessage = { name: 'currentUser', message: inputValue }; // 현재 사용자 이름 설정 + socket.emit('chat message', newMessage); // 메시지를 서버로 전송 + setInputValue(''); // 입력값 초기화 + } + }; +*/ + +const Chat = () => { + return ( +
+
메시지
+
+ {sampleChat.map((e, i) => ( +
+ {e.name} + {e.message} +
+ ))} +
+
+ +
+
+ ); +}; + +export default Chat; From 839245dd9d4055dd2f7f26496bdddce0addf2386 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 22:23:31 +0900 Subject: [PATCH 15/18] =?UTF-8?q?fix:=20=EC=86=8C=EC=BC=93=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20SET=5FPLAYER=5FNAME=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/api/socket.ts | 4 ---- FE/src/pages/GamePage.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/FE/src/api/socket.ts b/FE/src/api/socket.ts index 96c055a..2640a67 100644 --- a/FE/src/api/socket.ts +++ b/FE/src/api/socket.ts @@ -50,10 +50,6 @@ class SocketService { // }); // } - sendPlayerName(name: string) { - this.socket.emit(SocketEvents.SET_PLAYER_NAME, { playerName: name }); - } - // 메시지 전송 메서드 sendChatMessage(message: ChatMessage) { this.socket.emit(SocketEvents.CHAT_MESSAGE, message); diff --git a/FE/src/pages/GamePage.tsx b/FE/src/pages/GamePage.tsx index bec9a75..9866784 100644 --- a/FE/src/pages/GamePage.tsx +++ b/FE/src/pages/GamePage.tsx @@ -14,7 +14,7 @@ export const GamePage = () => { const handleNameSubmit = (name: string) => { setPlayerName(name); // 닉네임 설정 소켓 요청 - // socketService.sendPlayerName(name); + // socketService.joinRoom(name); setIsModalOpen(false); // 이름이 설정되면 모달 닫기 }; From bd735f9a2d79a6b33e487e85a83ab63c4700fcf3 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Tue, 5 Nov 2024 23:21:41 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=B0=A9?= =?UTF-8?q?=EC=97=90=EC=84=9C=20url=EB=A1=9C=20gameId=20=EB=B0=9B=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/App.tsx | 2 +- FE/src/pages/GamePage.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/FE/src/App.tsx b/FE/src/App.tsx index 7ef2cdb..7f91482 100644 --- a/FE/src/App.tsx +++ b/FE/src/App.tsx @@ -9,7 +9,7 @@ function App() { } /> } /> - } /> + } /> not found} /> diff --git a/FE/src/pages/GamePage.tsx b/FE/src/pages/GamePage.tsx index 9866784..cc544df 100644 --- a/FE/src/pages/GamePage.tsx +++ b/FE/src/pages/GamePage.tsx @@ -5,16 +5,18 @@ import { Modal } from '../components/Modal'; import { useState } from 'react'; import { GameHeader } from '@/components/GameHeader'; import { HeaderBar } from '@/components/HeaderBar'; -// import { socketService } from '../api/socket'; +import { useParams } from 'react-router-dom'; +import { socketService } from '@/api/socket'; export const GamePage = () => { - const [playerName, setPlayerName] = useState('asdf'); + const [playerName, setPlayerName] = useState(''); const [isModalOpen, setIsModalOpen] = useState(true); + const pin = useParams(); const handleNameSubmit = (name: string) => { setPlayerName(name); // 닉네임 설정 소켓 요청 - // socketService.joinRoom(name); + socketService.joinRoom(String(pin), name); setIsModalOpen(false); // 이름이 설정되면 모달 닫기 }; From a815f694b47b6068486b0814633cdd844fde6412 Mon Sep 17 00:00:00 2001 From: ijun17 Date: Wed, 6 Nov 2024 01:40:43 +0900 Subject: [PATCH 17/18] =?UTF-8?q?refactor:=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EB=B0=A9=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/constants/roomConfig.ts | 7 ++++++ FE/src/pages/GameSetupPage.tsx | 46 +++++++++++++++------------------- 2 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 FE/src/constants/roomConfig.ts diff --git a/FE/src/constants/roomConfig.ts b/FE/src/constants/roomConfig.ts new file mode 100644 index 0000000..d7aaa29 --- /dev/null +++ b/FE/src/constants/roomConfig.ts @@ -0,0 +1,7 @@ +const RoomConfig = { + MAX_PLAYERS: 500, + MIN_PLAYERS: 1, + DEFAULT_PLAYERS: 50 +} as const; + +export default RoomConfig; diff --git a/FE/src/pages/GameSetupPage.tsx b/FE/src/pages/GameSetupPage.tsx index ea961d8..c676936 100644 --- a/FE/src/pages/GameSetupPage.tsx +++ b/FE/src/pages/GameSetupPage.tsx @@ -10,35 +10,29 @@ import { TextField } from '@mui/material'; import { useState } from 'react'; -import { socketService } from '../api/socket'; - -const MAX_PLAYERS = 500; -const MIN_PLAYERS = 2; -const DEFAULT_PLAYERS = 50; +import { socketService } from '@/api/socket'; +import RoomConfig from '@/constants/roomConfig'; export const GameSetupPage = () => { - const [roomName, setRoomName] = useState(''); - const [maxPlayers, setMaxPlayers] = useState(DEFAULT_PLAYERS); - const [selectGameMode, setSelectGameMode] = useState('ranking'); + const [title, setTitle] = useState(''); + const [maxPlayerCount, setMaxPlayerCount] = useState(RoomConfig.DEFAULT_PLAYERS); + const [gameMode, setGameMode] = useState<'SURVIVAL' | 'RANKING'>('RANKING'); const [roomPublic, setRoomPublic] = useState(true); const handleModeChange = (e: React.ChangeEvent) => { - setSelectGameMode(e.target.value); + const value = e.target.value === 'RANKING' ? 'RANKING' : 'SURVIVAL'; + setGameMode(value); }; const handleSubmit = async () => { const roomData = { - roomName, - maxPlayers, - gameMode: selectGameMode, + title, + maxPlayerCount, + gameMode, isPublic: roomPublic }; - try { - socketService.createRoom(roomData); - } catch (error) { - console.error('Error creating room:', error); - } + socketService.createRoom(roomData); }; return ( @@ -47,28 +41,28 @@ export const GameSetupPage = () => { {'<'} setRoomName(e.target.value)} + onChange={(e) => setTitle(e.target.value)} /> setMaxPlayers(newValue as number)} + value={maxPlayerCount} + onChange={(_, newValue) => setMaxPlayerCount(newValue as number)} > 게임 모드 선택 - } label="서바이벌" /> - } label="랭킹" /> + } label="서바이벌" /> + } label="랭킹" /> From eaece19a2e3cee0bd17b87192a7bb6a2c423448e Mon Sep 17 00:00:00 2001 From: ijun17 Date: Wed, 6 Nov 2024 01:43:40 +0900 Subject: [PATCH 18/18] =?UTF-8?q?refactor:=20=EC=83=98=ED=94=8C=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/api/socket.ts | 18 +++++++++---- FE/src/components/Chat.tsx | 52 +++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/FE/src/api/socket.ts b/FE/src/api/socket.ts index 2640a67..b2967e9 100644 --- a/FE/src/api/socket.ts +++ b/FE/src/api/socket.ts @@ -9,16 +9,16 @@ type ChatMessage = { }; type CreateRoomPayload = { - roomName: string; - maxPlayers: number; + title: string; + maxPlayerCount: number; gameMode: string; isPublic: boolean; }; // 이벤트의 데이터 타입을 정의 // type SocketDataMap = { -// chatMessage: ChatMessage; -// createRoom: CreateRoomPayload; +// [SocketEvents.CHAT_MESSAGE]: ChatMessage; +// [SocketEvents.CREATE_ROOM]: CreateRoomPayload; // // 다른 이벤트의 데이터 타입을 추가 // }; @@ -44,7 +44,7 @@ class SocketService { } // 이벤트 수신 메서드 - // on(event: T, callback: (data: SocketDataMap[T]) => void) { + // on(event: T, callback: (data: SocketDataMap[T]) => void) { // this.socket.on(event, (data: SocketDataMap[T]) => { // callback(data); // }); @@ -65,6 +65,14 @@ class SocketService { disconnect() { this.socket.disconnect(); } + + joinRoom(gameId: string, playerName: string) { + this.socket.send(SocketEvents.JOIN_ROOM, { gameId, playerName }); + } + + chatMessage(gameId: string, message: string) { + this.socket.send(SocketEvents.CHAT_MESSAGE, { gameId, message }); + } } export const socketService = new SocketService(''); diff --git a/FE/src/components/Chat.tsx b/FE/src/components/Chat.tsx index 7e753ee..80e46ec 100644 --- a/FE/src/components/Chat.tsx +++ b/FE/src/components/Chat.tsx @@ -1,46 +1,42 @@ +import { socketService } from '@/api/socket'; +import { useEffect, useState } from 'react'; + const sampleChat = Array(100) .fill(null) .map((_, i) => ({ name: 'user' + i, message: 'messagemessagemessagemessagemessagemessage' })); -// type ChatProps = { -// cket: Socket; - -/* -const [messages, setMessages] = useState>([]); +const Chat = () => { + const [messages, setMessages] = useState>([]); const [inputValue, setInputValue] = useState(''); useEffect(() => { + setMessages(sampleChat); //TODO 나중에 고쳐야 함 // 서버에서 메시지를 받을 때 - socket.on('chat message', (message) => { - setMessages((prevMessages) => [...prevMessages, message]); - }); - - return () => { - socket.off('chat message'); - }; - }, [socket]); + // socket.on('chat message', (message) => { + // setMessages((prevMessages) => [...prevMessages, message]); + // }); + // return () => { + // socket.off('chat message'); + // }; + }, []); const handleInputChange = (e: React.ChangeEvent) => { setInputValue(e.target.value); }; const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); // 폼 제출 기본 동작 방지 + e.preventDefault(); if (inputValue.trim()) { - const newMessage = { name: 'currentUser', message: inputValue }; // 현재 사용자 이름 설정 - socket.emit('chat message', newMessage); // 메시지를 서버로 전송 - setInputValue(''); // 입력값 초기화 + socketService.chatMessage('1234', inputValue); + setInputValue(''); } }; -*/ - -const Chat = () => { return (
메시지
- {sampleChat.map((e, i) => ( + {messages.map((e, i) => (
{e.name} {e.message} @@ -48,11 +44,15 @@ const Chat = () => { ))}
- +
+ +
);