diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..453722c2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,46 @@ +name: npm-build-and-deploy + +on: + pull_request: + branches: [ develop ] + +jobs: + npm-build: + runs-on: ubuntu-22.04 + + # build ์ค‘ warning ๋ฌด์‹œ + env: + CI: false + + steps: + - uses: actions/checkout@v3.6.0 + + - name: Set up Node 20 + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Inject .env file from GitHub Secrets + env: + ENV_FILE: ${{ secrets.ENV_FILE }} + TARGET_DIR: ./ + run: echo $ENV_FILE | base64 -d > $TARGET_DIR/.env + + - name: Try build + run: npm run build + + deploy: + needs: npm-build + runs-on: ubuntu-22.04 + + steps: + - name: Deploy Trigger + run: | + curl -X POST http://ec2-3-37-14-140.ap-northeast-2.compute.amazonaws.com:20001/job/animory-fe-deploy/build \ + --user admin:${{ secrets.JENKINS_DEPLOY_KEY }} + + diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..5d0502b0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,15 @@ +name: deploy + +on: + push: + branches: [ develop ] + +jobs: + deploy: + runs-on: ubuntu-22.04 + + steps: + - name: Deploy Trigger + run: | + curl -X POST http://ec2-3-37-14-140.ap-northeast-2.compute.amazonaws.com:20001/job/animory-fe-deploy/build \ + --user admin:${{ secrets.JENKINS_DEPLOY_KEY }} \ No newline at end of file diff --git a/README.md b/README.md index 48723273..cdf368e2 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - [๐Ÿ’ป ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ](#-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%86%8C%EA%B0%9C) - [๐Ÿ“š ๊ธฐ์ˆ  ์Šคํƒ](#-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D) - [๐Ÿ—ƒ๏ธ ๋””์ž์ธ ํŒจํ„ด & ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์†Œ๊ฐœ](#%EF%B8%8F-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4--%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EC%86%8C%EA%B0%9C) +- [โŒจ๏ธ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜](#%EF%B8%8F-๋„ค์ด๋ฐ-์ปจ๋ฒค์…˜) - [๐Ÿ“‘ ํŽ˜์ด์ง€๋ณ„ ๊ธฐ๋Šฅ ์†Œ๊ฐœ](#-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%B3%84-%EA%B8%B0%EB%8A%A5-%EC%86%8C%EA%B0%9C) - [๐Ÿ“ ์š”๊ตฌ์‚ฌํ•ญ ๋ช…์„ธ์„œ](https://github.com/Step3-kakao-tech-campus/Team16_BE/wiki/%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%AA%85%EC%84%B8) - [๐Ÿ“’ ์นด์นด์˜ค ํ…Œํฌ ์บ ํผ์Šค 3๋‹จ๊ณ„ ์ง„ํ–‰ ์ปค๋ฆฌํ˜๋Ÿผ ์•ˆ๋‚ด ์‚ฌํ•ญ](#-%EC%B9%B4%EC%B9%B4%EC%98%A4-%ED%85%8C%ED%81%AC-%EC%BA%A0%ED%8D%BC%EC%8A%A4-3%EB%8B%A8%EA%B3%84-%EC%A7%84%ED%96%89-%EB%B3%B4%EB%93%9C) @@ -43,15 +44,27 @@
### ๐Ÿ—ƒ๏ธ ๋””์ž์ธ ํŒจํ„ด & ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์†Œ๊ฐœ -๋””์ž์ธ ํŒจํ„ด ์„ค๋ช… +### โ–ถ๏ธ ๋””์ž์ธ ํŒจํ„ด ์„ค๋ช… [๋ณ€๊ฒฝ ์ „] - `VAC Pattern`๊ณผ `Atomic Design Pattern`์„ ๋ณ‘ํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - - VAC ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ UI๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - - ์ปดํฌ๋„ŒํŠธ ๋ฐ ํŽ˜์ด์ง€๋Š” Atomin ๋””์ž์ธ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ์—ผ๋‘์— ๋‘๊ณ  ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + - VAC ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ UI๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + - ์ปดํฌ๋„ŒํŠธ ๋ฐ ํŽ˜์ด์ง€๋Š” Atomin ๋””์ž์ธ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ์—ผ๋‘์— ๋‘๊ณ  ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +
+ +### โ–ถ๏ธ ๋””์ž์ธ ํŒจํ„ด ์„ค๋ช… [๋ณ€๊ฒฝ ํ›„] +- ๊ธฐ์กด ๋ฐฉ์‹์˜ ๋ฌธ์ œ์  + 1. `Component์˜ Level`์„ ์ •ํ•˜๋Š” ๊ธฐ์ค€์ด ๋ชจํ˜ธํ•˜๋‹ค๋Š” ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + 2. ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•œ์ •์ ์ด์–ด์„œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์— ์–ด๋ ค์›€์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. +- ๋ณ€๊ฒฝ๋œ ๋ฐฉ์‹ + 1. `VAC Pattern`์„ ์œ ์ง€ํ•˜์—ฌ ๋กœ์ง๊ณผ UI๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ , ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค. + 2. `commons` ํด๋”๋ฅผ ๋งŒ๋“ค์–ด์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. +
๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ - ์•„๋ž˜์˜ ๊ตฌ์กฐ๋Š” ๋””์ž์ธ ํŒจํ„ด์„ ๊ณ ๋ คํ•˜์—ฌ ์ž‘์„ฑํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์ž…๋‹ˆ๋‹ค. -- ํด๋”๋ช…์€ ๋ฐ”๋€Œ์ง€ ์•Š์ง€๋งŒ, ๋‚ด๋ถ€ ํŒŒ์ผ์˜ ์ด๋ฆ„์€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- `Atomic Design Pattern`์˜ ๋ฌธ์ œ๋กœ ์ธํ•ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. +- ํด๋”๋ช…์€ ๋ฐ”๋€Œ์ง€ ์•Š์ง€๋งŒ, ๋‚ด๋ถ€ ํŒŒ์ผ์˜ ์ด๋ฆ„์€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ``` โ”œโ”€โ”€ /public @@ -64,32 +77,30 @@ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ logo1 โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ โ””โ”€โ”€ logo2 โ”œโ”€โ”€ /src -โ”‚ โ”œโ”€โ”€ /apis -โ”‚ โ”œโ”€โ”€ /components -โ”‚ โ”‚ โ”œโ”€โ”€ /atoms -โ”‚ โ”‚ โ”œโ”€โ”€ /molecules -โ”‚ โ”‚ โ”œโ”€โ”€ /organisms -โ”‚ โ”‚ โ”œโ”€โ”€ /templates -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ SomeTemplate.tsx -โ”‚ โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ VSomeTemplate.tsx +โ”‚ โ”œโ”€โ”€ /commons โ”‚ โ”œโ”€โ”€ /pages -โ”‚ โ”‚ โ”œโ”€โ”€ HomePage.tsx -โ”‚ โ”‚ โ””โ”€โ”€ DetailedPetPage.tsx +โ”‚ โ”‚ โ”œโ”€โ”€ home +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Home.tsx +โ”‚ โ”‚ โ”œโ”€โ”€ login +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ VLoginPage.tsx +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ LoginPage.tsx +โ”‚ โ”‚ โ”œโ”€โ”€ map +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Map.tsx +โ”‚ โ”œโ”€โ”€ โ””โ”€โ”€ โ””โ”€โ”€ MapPage.tsx โ”‚ โ”œโ”€โ”€ /layouts -โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ GNB.tsx +โ”‚ โ”‚ โ”œโ”€โ”€ VGNB.tsx +โ”‚ โ”‚ โ””โ”€โ”€ GNB.tsx โ”‚ โ”œโ”€โ”€ /recoil -โ”‚ โ”‚ โ”œโ”€โ”€ PetShelterState.tsx -โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ PetInfoState.tsx -โ”‚ โ”œโ”€โ”€ /commons -โ”‚ โ”‚ โ”œโ”€โ”€ someFunction.ts -โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ regex.ts +โ”‚ โ”‚ โ”œโ”€โ”€ registerState.tsx +โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ regionState.tsx โ”‚ โ”œโ”€โ”€ /hooks โ”‚ โ””โ”€โ”€ โ””โ”€โ”€ useFetch.ts โ”‚ โ”œโ”€โ”€ App.css โ”‚ โ”œโ”€โ”€ App.tsx โ”‚ โ”œโ”€โ”€ App.test.tsx โ”‚ โ”œโ”€โ”€ index.css -โ””โ”€โ”€ โ””โ”€โ”€ index.tsx +โ”œโ”€โ”€ โ””โ”€โ”€ index.tsx +โ”œโ”€โ”€ .env โ”œโ”€โ”€ .eslintrc โ”œโ”€โ”€ .gitignore โ”œโ”€โ”€ .prettierrc @@ -102,13 +113,36 @@
+### โŒจ๏ธ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜ +- ๋ชจ๋“  ์ด๋ฆ„์€ ๊ธฐ๋Šฅ์„ ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ๋„๋ก ์˜๋ฏธ๋ฅผ ๋‚ดํฌํ•˜๊ณ  ์žˆ๋„๋ก ์ž‘์„ฑํ•จ์„ ์›์น™์œผ๋กœ ํ•œ๋‹ค. + #### 1. ํŒŒ์ผ + - ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ : `PascalCase` ์ ์šฉ + - ์ปดํฌ๋„ŒํŠธ ์ด์™ธ์˜ ํŒŒ์ผ : `camelCase` ์ ์šฉ + - ํ…Œ์ŠคํŠธ ํŒŒ์ผ : `App.test.tx`์™€ ๊ฐ™์ด `.test`๋ฅผ ๋ช…์‹œ + #### 2. ํด๋” + - ๋ชจ๋“  ํด๋”๋ฅผ `camelCase`๋กœ ์ ์šฉ + #### 3. ๋ณ€์ˆ˜๋ช… + - ์ผ๋ฐ˜์ ์ธ ๋ณ€์ˆ˜ : `camelCase`๋กœ ์˜์–ด ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉ + - ์ƒ์ˆ˜ : ๋Œ€๋ฌธ์ž๋กœ๋งŒ ์ž‘์„ฑํ•˜๊ณ , ์—ฌ๋Ÿฌ ๋‹จ์–ด์˜ ๊ตฌ๋ถ„์€ _(underscore) ์‚ฌ์šฉ + #### 4. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ + - handle + EventName์œผ๋กœ ์‚ฌ์šฉ + #### 5. ํ•จ์ˆ˜ + - ํ•จ์ˆ˜์˜ ๊ธฐ๋Šฅ์„ ๊ธฐ์ค€์œผ๋กœ ์ž‘๋ช… + - `camelCase` ์ ์šฉ + #### 6. Type, Interface + - `PascalCase` ์ ์šฉ + #### 7. Route(๊ฒฝ๋กœ) + - `kebab-case` ์ ์šฉ + +
+ ### ๐Ÿ“‘ ํŽ˜์ด์ง€๋ณ„ ๊ธฐ๋Šฅ ์†Œ๊ฐœ - ๊ธฐํš ๋‹จ๊ณ„์—์„œ `Figma`์„ ํ†ตํ•ด UI์— ๋Œ€ํ•œ ์™€์ด์–ดํ”„๋ ˆ์ž„์„ ์ œ์ž‘ํ•˜์˜€์Šต๋‹ˆ๋‹ค. - ์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ํŽ˜์ด์ง€๋ณ„ UI ์„ค๊ณ„ ์ด๋ฏธ์ง€์™€ ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋ฐ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค. - ์ž์„ธํ•œ ์‚ฌํ•ญ์€ [TEAM-16-Wireframe](https://www.figma.com/file/A0w3m1DU5JJm2zzvo9lnGE/16%EC%A1%B0-%EC%83%88%EA%B8%B0%ED%9A%8D?type=design&node-id=684%3A1302&mode=design&t=OXljL8TPJH6AsihE-1)์„ ํ†ตํ•ด ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
-1. ๋ฉ”์ธ ํŽ˜์ด์ง€ +### 1. ๋ฉ”์ธ ํŽ˜์ด์ง€ main-page @@ -118,7 +152,7 @@
-2. ์ƒ์„ธ ์ •๋ณด ํŽ˜์ด์ง€ +### 2. ์ƒ์„ธ ์ •๋ณด ํŽ˜์ด์ง€ image @@ -128,7 +162,7 @@
-3. ๊ทผ์ฒ˜ ๋™๋ฌผ ๋ณดํ˜ธ์†Œ ์ฐพ๊ธฐ ํŽ˜์ด์ง€ +### 3. ๊ทผ์ฒ˜ ๋™๋ฌผ ๋ณดํ˜ธ์†Œ ์ฐพ๊ธฐ ํŽ˜์ด์ง€ image @@ -138,7 +172,7 @@
-4. ๋ณดํ˜ธ์†Œ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ +### 4. ๋ณดํ˜ธ์†Œ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ image @@ -147,7 +181,7 @@
-5. ํ”„๋กœํ•„ ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€ +### 5. ํ”„๋กœํ•„ ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€ image image @@ -159,7 +193,7 @@
-6. ๋“ฑ๋กํ•˜๊ธฐ ํŽ˜์ด์ง€ +### 6. ๋“ฑ๋กํ•˜๊ธฐ ํŽ˜์ด์ง€ image image @@ -170,7 +204,7 @@
-7. ์ˆ˜์ •ํ•˜๊ธฐ ํŽ˜์ด์ง€ +### 7. ์ˆ˜์ •ํ•˜๊ธฐ ํŽ˜์ด์ง€ image @@ -179,7 +213,7 @@
-8. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ +### 8. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ image @@ -191,7 +225,7 @@
-9. ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ +### 9. ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ image @@ -202,9 +236,9 @@
-10. GNB(Global Navigation Bar) +### 10. GNB(Global Navigation Bar) - image + image - ์นดํ…Œ๊ณ ๋ฆฌ, ํ”„๋กœํ•„ ๋ชฉ๋ก, ์ฃผ๋ณ€ ๋ณดํ˜ธ์†Œ ์ฐพ๊ธฐ, ๋“ฑ๋กํ•˜๊ธฐ, ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. - ๊ฐ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด, ํ•ด๋‹น ์—ญํ• ์„ ํ•˜๋Š” ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. @@ -212,10 +246,10 @@
-11. ์นดํ…Œ๊ณ ๋ฆฌ Modal +### 11. ์นดํ…Œ๊ณ ๋ฆฌ Modal - image - image + image + image - GNB์— ์žˆ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ ํƒญ์„ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด, ์ง€์—ญ๊ณผ ๋™๋ฌผ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋‹ฌ์ฐฝ์ด ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. - ์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋”ฐ๋ผ ํ™ˆ ํ™”๋ฉด์—์„œ ๋ณด์—ฌ์ง€๋Š” ๋™๋ฌผ ์ˆํผ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค. diff --git a/package-lock.json b/package-lock.json index 53a2243f..7ce9dfac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", "react": "^18.2.0", + "react-cookie": "^6.1.1", "react-daum-postcode": "^3.1.3", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", @@ -4046,6 +4047,11 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", + "integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA==" + }, "node_modules/@types/eslint": { "version": "8.44.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", @@ -4099,6 +4105,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.3.tgz", + "integrity": "sha512-Wny3a2UXn5FEA1l7gc6BbpoV5mD1XijZqgkp4TRgDCDL5r3B5ieOFGUX5h3n78Tr1MEG7BfvoM8qeztdvNU0fw==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -9076,6 +9091,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -14771,6 +14799,19 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-cookie": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-6.1.1.tgz", + "integrity": "sha512-fuFRpf8LH6SfmVMowDUIRywJF5jAUDUWrm0EI5VdXfTl5bPcJ7B0zWbuYpT0Tvikx7Gs18MlvAT+P+744dUz2g==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^6.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-daum-postcode": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/react-daum-postcode/-/react-daum-postcode-3.1.3.tgz", @@ -17062,6 +17103,15 @@ "node": ">=8" } }, + "node_modules/universal-cookie": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-6.1.1.tgz", + "integrity": "sha512-33S9x3CpdUnnjwTNs2Fgc41WGve2tdLtvaK2kPSbZRc5pGpz2vQFbRWMxlATsxNNe/Cy8SzmnmbuBM85jpZPtA==", + "dependencies": { + "@types/cookie": "^0.5.1", + "cookie": "^0.5.0" + } + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/package.json b/package.json index f3c2be78..6444a173 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", "react": "^18.2.0", + "react-cookie": "^6.1.1", "react-daum-postcode": "^3.1.3", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", diff --git a/src/App.tsx b/src/App.tsx index 6c0a4e04..e1d6e7b9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,8 +12,13 @@ import RegisterPage from 'pages/register/RegisterPage'; import ShelterInfoPage from 'pages/shelterInfo/ShelterInfoPage'; import SignupPage from 'pages/signUp/SignupPage'; import UrgentListPage from 'pages/profileList/urgentList/UrgentListPage'; +import UpdatePage from 'pages/update/UpdatePage'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { refetchOnWindowFocus: false, refetchOnMount: false }, + }, +}); function App() { return ( @@ -31,6 +36,7 @@ function App() { } /> } /> } /> + } /> diff --git a/src/commons/ErrorBoundary.tsx b/src/commons/ErrorBoundary.tsx new file mode 100644 index 00000000..a06622fa --- /dev/null +++ b/src/commons/ErrorBoundary.tsx @@ -0,0 +1,39 @@ +/* eslint-disable class-methods-use-this */ +import React, { Component, ErrorInfo, ReactNode } from 'react'; + +interface Props { + children?: ReactNode; + fallback?: ReactNode; // Define a fallback prop +} + +interface State { + hasError: boolean; +} + +class ErrorBoundary extends Component { + public state: State = { + hasError: false, + }; + + public static getDerivedStateFromError(_: Error): State { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Uncaught error:', error, errorInfo); + } + + public render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; // Use the provided fallback prop if an error occurs + } + return

There was an error

; // Default fallback if no prop is provided + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/commons/MonthDays.tsx b/src/commons/MonthDays.tsx index 463367e1..d7da5b0e 100644 --- a/src/commons/MonthDays.tsx +++ b/src/commons/MonthDays.tsx @@ -33,12 +33,16 @@ export const CurrentMonthDays = ({ + console.log(data); + if (isError || data?.success === false) { + return ( +
+
+
+ +
+ + {modalString}์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค + +
{data?.error?.message}
+
+ + +
- {isSuccess && ( - <> - - ์ถ”๊ฐ€ ๋“ฑ๋ก ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? - -
- - -
- - )} - {!isSuccess && isLoading ? ( - <> - ๋“ฑ๋ก์ค‘์ž…๋‹ˆ๋‹ค... - hourglass - - ) : ( - <> - - ๋“ฑ๋ก ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? - -
- - -
- - )}
- - ); + ); + } + if (isLoading) { + return ( +
+
+
+ +
+ + {modalString}์ค‘์ž…๋‹ˆ๋‹ค... + + hourglass +
+
+ ); + } + if (isSuccess) { + return ( +
+
+
+ +
+ + ์ถ”๊ฐ€ {modalString} ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? + +
+ + +
+
+
+ ); + } + if (!isSuccess && !isLoading && !isError) { + return ( +
+
+
+ +
+ + {modalString} ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? + +
+ + +
+
+
+ ); + } + return
๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค
; }; export default RegisterModal; diff --git a/src/layouts/VGNB.tsx b/src/layouts/VGNB.tsx index f0e721d9..cf61aa6b 100644 --- a/src/layouts/VGNB.tsx +++ b/src/layouts/VGNB.tsx @@ -1,4 +1,5 @@ import LogoButton from 'commons/LogoButton'; +import { getLoginState, removeUser } from 'commons/cookie/getUser'; import { Link } from 'react-router-dom'; export interface VGNBProps { @@ -36,8 +37,11 @@ const VGNB = (props: VGNBProps) => {
- diff --git a/src/layouts/VLargeGNB.tsx b/src/layouts/VLargeGNB.tsx index 0a078263..4d046287 100644 --- a/src/layouts/VLargeGNB.tsx +++ b/src/layouts/VLargeGNB.tsx @@ -1,5 +1,6 @@ import { Link } from 'react-router-dom'; import LogoButton from 'commons/LogoButton'; +import { getLoginState, removeUser } from 'commons/cookie/getUser'; export interface VLargeGNBProps { handleCategoryButtonClick: () => void; @@ -61,8 +62,11 @@ const VLargeGNB = (props: VLargeGNBProps) => {
- diff --git a/src/pages/detailPet/VDetailPetData.tsx b/src/pages/detailPet/VDetailPetData.tsx index c809718e..cc290e4e 100644 --- a/src/pages/detailPet/VDetailPetData.tsx +++ b/src/pages/detailPet/VDetailPetData.tsx @@ -54,7 +54,7 @@ const VDetailPetData = ({ return (
z )} -
+
diff --git a/src/pages/detailPet/VDetailPetInfo.tsx b/src/pages/detailPet/VDetailPetInfo.tsx index b8a5ff58..1c39d5e2 100644 --- a/src/pages/detailPet/VDetailPetInfo.tsx +++ b/src/pages/detailPet/VDetailPetInfo.tsx @@ -3,13 +3,13 @@ import { DetailPetInfoProps } from 'pages/detailPet/VDetailPetData'; const VDetailPetInfo = (data: DetailPetInfoProps) => { return ( -
-

{data.name}

-
+
+

{data.name}

+
๋‚˜์ด - {data.age} + {data.age}
์„ฑ๋ณ„ @@ -17,12 +17,12 @@ const VDetailPetInfo = (data: DetailPetInfoProps) => {
-
+
๋ชธ๋ฌด๊ฒŒ - {data.weight}kg + {data.weight}kg
-
+
๋ถ„์–‘์ง€์—ญ {data.shelterInfo.name} @@ -42,7 +42,7 @@ const VDetailPetInfo = (data: DetailPetInfoProps) => {
๋ฐฑ์‹ ์ ‘์ข… - {data.vaccinationStatus} + {data.vaccinationStatus}
์ค‘์„ฑํ™” @@ -53,7 +53,7 @@ const VDetailPetInfo = (data: DetailPetInfoProps) => {
์ž…์–‘ ์—ฌ๋ถ€ - {data.adoptionStatus} + {data.adoptionStatus}
๋ณดํ˜ธ์ข…๋ฃŒ์ผ @@ -61,7 +61,7 @@ const VDetailPetInfo = (data: DetailPetInfoProps) => {
-
+
์—ฐ๋ฝ์ฒ˜ {data.shelterInfo.contact} diff --git a/src/pages/login/LoginInputForm.tsx b/src/pages/login/LoginInputForm.tsx index 888a53f9..390c69e5 100644 --- a/src/pages/login/LoginInputForm.tsx +++ b/src/pages/login/LoginInputForm.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import { useRecoilState } from 'recoil'; +import { useNavigate } from 'react-router-dom'; import { shelterLoginState } from 'recoil/shelterState'; import VLoginInputForm from './VLoginInputForm'; +import { setCookie } from '../../commons/cookie/cookie'; const LoginInputForm = () => { const [userInfo, setUserInfo] = useRecoilState(shelterLoginState); @@ -9,7 +11,7 @@ const LoginInputForm = () => { const [isPasswordEmpty, setIsPasswordEmpty] = useState(false); const [errorText, setErrorText] = useState('์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'); - // const navigate = useNavigate(); + const navigate = useNavigate(); const emailValidate = (text: string) => { const emailReg = /^[\w.-]+@[\w.-]+\.\w+$/g; // emailํ˜•์‹ @@ -42,19 +44,30 @@ const LoginInputForm = () => { email: userInfo.email, password: userInfo.password, }), - }).then((res) => { - console.log(res); - // if (res.status === 200) { - // navigate('/'); - // } - }); + }) + .then((res) => { + const jwtToken = res.headers.get('Authorization'); + if (jwtToken) { + const slicedToken = jwtToken.split(' ')[1]; + setCookie('loginToken', slicedToken); + } else { + console.log('Token์ด Null'); + } + + return res.json(); + }) + .then((data) => { + if (data.success) { + navigate('/'); + } + }); }; const handleChange = (event: React.ChangeEvent) => { const target = event.target as HTMLInputElement; if (target.id === 'id') { setUserInfo((prev) => ({ ...prev, email: target.value })); - emailValidate(userInfo.email); + emailValidate(target.value); setIsEmailEmpty(true); } else if (target.id === 'password') { setUserInfo((prev) => ({ ...prev, password: target.value })); diff --git a/src/pages/login/VLoginInputForm.tsx b/src/pages/login/VLoginInputForm.tsx index 6ca37790..01d116c4 100644 --- a/src/pages/login/VLoginInputForm.tsx +++ b/src/pages/login/VLoginInputForm.tsx @@ -28,7 +28,7 @@ const VLoginInputForm = ({ onChange={(e) => { handleChange(e); }} - autocomplete="email" + autocomplete="off" /> {isEmailEmpty &&
{errorText}
} { handleChange(e); }} - autocomplete="current-password" + autocomplete="off" /> {isPasswordEmpty && (
๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.
diff --git a/src/pages/map/Map.tsx b/src/pages/map/Map.tsx index b4267ae8..e9d06d28 100644 --- a/src/pages/map/Map.tsx +++ b/src/pages/map/Map.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable func-names */ @@ -5,7 +6,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable no-new */ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; declare global { interface Window { @@ -21,24 +23,75 @@ interface SearchedPlaceType { road_address_name: string; x: string; y: string; + isRegistered?: boolean; } const Map: React.FC = () => { - const searchedPlace = useRef([]); + const [searchedPlace, setSearchedPlace] = useState([]); + const [isLoading, setIsLoading] = React.useState(true); + const [isLoading2, setIsLoading2] = React.useState(true); + const [notMutated, setNotMutated] = React.useState(false); const [currentPosition, setCurrentPosition] = React.useState({ - lat: 0, - lon: 0, + lat: 35.1759293, + lon: 126.9149701, }); + const { + data, + isLoading: isMutateLoading, + mutate, + isSuccess, + } = useMutation( + ['shelter'], + () => { + console.log(searchedPlace); + return fetch(`${process.env.REACT_APP_URI}/shelter/filter`, { + method: 'POST', + body: JSON.stringify(searchedPlace.map((place) => place.id)), + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json()); + }, + { + onSuccess: (data) => { + console.log(data); + if (data.response.length > 0) { + for (let i = 0; i < data.response.length; i += 1) { + for (let j = 0; j < searchedPlace.length; j += 1) { + console.log( + data.response[i].kakaoLocationId, + searchedPlace[j].id, + ); + if ( + data.response[i].kakaoLocationId.toString() === + searchedPlace[j].id + ) { + setSearchedPlace((prev) => { + const temp = [...prev]; + temp[j].isRegistered = true; + return temp; + }); + } else { + setSearchedPlace((prev) => { + const temp = [...prev]; + temp[j].isRegistered = false; + return temp; + }); + } + } + } + } + }, + }, + ); + console.log(searchedPlace); + if (notMutated) { + mutate(); + setNotMutated(false); + console.log('mutate'); + } useEffect(() => { - // ํ˜„์žฌ ์œ„์น˜๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค - const getCurrentPosition = navigator.geolocation.getCurrentPosition( - function (position) { - const lat = position.coords.latitude; // ์œ„๋„ - const lon = position.coords.longitude; // ๊ฒฝ๋„ - setCurrentPosition({ lat, lon }); - }, - ); const mapScript = document.createElement('script'); mapScript.async = true; @@ -50,6 +103,7 @@ const Map: React.FC = () => { if (!currentPosition.lat || !currentPosition.lon) { return; } + console.log(currentPosition.lat, currentPosition.lon); window.kakao.maps.load(() => { const mapContainer = document.getElementById('map'); const mapOption = { @@ -78,11 +132,7 @@ const Map: React.FC = () => { }); // ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ์™„๋ฃŒ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค - function placesSearchCB( - data: string | any[], - status: any, - pagination: any, - ) { + function placesSearchCB(data: string | any[], status: any) { if (status === kakao.maps.services.Status.OK) { // ๊ฒ€์ƒ‰๋œ ์žฅ์†Œ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ง€๋„ ๋ฒ”์œ„๋ฅผ ์žฌ์„ค์ •ํ•˜๊ธฐ์œ„ํ•ด // LatLngBounds ๊ฐ์ฒด์— ์ขŒํ‘œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค @@ -90,8 +140,9 @@ const Map: React.FC = () => { for (let i = 0; i < data.length; i += 1) { if ( - data[i].place_name === '๊ด‘์ฃผ๊ด‘์—ญ์‹œ๋™๋ฌผ๋ณดํ˜ธ์†Œ' || - data[i].place_name === '๊ด‘์ฃผ์ฒญ์†Œ๋…„์ผ์‹œ๋ณดํ˜ธ์†Œ' + searchedPlace.some((place) => { + return place.id === data[i].id && place.isRegistered; + }) ) { displayMarker2(data[i]); } else displayMarker(data[i]); @@ -116,11 +167,14 @@ const Map: React.FC = () => { x, y, }; - searchedPlace.current.push(placeInfo); + if (data.length > searchedPlace.length) { + setSearchedPlace((prev) => [...prev, placeInfo]); + } bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x)); } // ๊ฒ€์ƒ‰๋œ ์žฅ์†Œ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ง€๋„ ๋ฒ”์œ„๋ฅผ ์žฌ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค + setNotMutated(true); map.setBounds(bounds); } } @@ -161,6 +215,29 @@ const Map: React.FC = () => { image: markerImage2, }); + // ๋งˆ์ปค์— ํด๋ฆญ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค + kakao.maps.event.addListener(marker, 'click', function () { + // ๋งˆ์ปค๋ฅผ ํด๋ฆญํ•˜๋ฉด ์žฅ์†Œ๋ช…์ด ์ธํฌ์œˆ๋„์šฐ์— ํ‘œ์ถœ๋ฉ๋‹ˆ๋‹ค + infowindow.setContent( + `
${place.place_name}
`, + ); + infowindow.open(map, marker); + // ์ค‘๊ฐ„ ์ง€์ ์œผ๋กœ ์ด๋™ + map.setCenter(marker.getPosition()); + }); + } + // searchedPlace.current ์ˆœํ™˜ํ•˜๋ฉฐ isRegistered์— ๋Œ€ํ•ด์„œ ๋งˆ์ปค ์Šคํƒ€์ผ ๋ณ€๊ฒฝํ•˜๊ธฐ + const imageSrc3 = '/assets/images/all.png'; + const imageSize3 = new kakao.maps.Size(64, 69); + const markerImage3 = new kakao.maps.MarkerImage(imageSrc3, imageSize3); + function displayMarker3(place: any) { + // ๋งˆ์ปค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ง€๋„์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค + const marker = new kakao.maps.Marker({ + map, + position: new kakao.maps.LatLng(place.y, place.x), + image: markerImage3, + }); + // ๋งˆ์ปค์— ํด๋ฆญ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค kakao.maps.event.addListener(marker, 'click', function () { // ๋งˆ์ปค๋ฅผ ํด๋ฆญํ•˜๋ฉด ์žฅ์†Œ๋ช…์ด ์ธํฌ์œˆ๋„์šฐ์— ํ‘œ์ถœ๋ฉ๋‹ˆ๋‹ค @@ -173,16 +250,21 @@ const Map: React.FC = () => { }); } }); + setTimeout(() => { + console.log('settimeout'); + setIsLoading(false); + }, 500); + setTimeout(() => { + setIsLoading2(false); + }, 1000); }; mapScript.addEventListener('load', onLoadKakaoMap); - }, [currentPosition.lat, currentPosition.lon]); + }, [isLoading, isLoading2]); return (
- +
); }; diff --git a/src/pages/profileList/PageButton.tsx b/src/pages/profileList/PageButton.tsx index 7a4b5b79..129bf9d8 100644 --- a/src/pages/profileList/PageButton.tsx +++ b/src/pages/profileList/PageButton.tsx @@ -11,14 +11,14 @@ export default function PageButton({ }: Props) { if (disabled) { return ( - + {children} ); } if (active) { return ( - + {children} ); @@ -26,7 +26,7 @@ export default function PageButton({ return ( diff --git a/src/pages/profileList/VProfileCard.tsx b/src/pages/profileList/VProfileCard.tsx index 014c169e..3f400220 100644 --- a/src/pages/profileList/VProfileCard.tsx +++ b/src/pages/profileList/VProfileCard.tsx @@ -14,7 +14,10 @@ const VProfileCard = ({ shelter, state, }: VProfileInfoProps) => ( - +
diff --git a/src/pages/profileList/VProfileListHome.tsx b/src/pages/profileList/VProfileListHome.tsx index 0d4ff281..603b1547 100644 --- a/src/pages/profileList/VProfileListHome.tsx +++ b/src/pages/profileList/VProfileListHome.tsx @@ -11,32 +11,32 @@ export interface ProfileListProps { const VProfileListHome = (profileListProps: ProfileListProps) => { return ( -
-

+
+

๊ธด๊ธ‰ ๋„์›€์ด ํ•„์š”ํ•ด์š”! ๋”๋ณด๊ธฐ

-
+
-

+

์‹ ๊ทœ ์• ๋‹ˆ๋ชจ๋ฆฌ ์นœ๊ตฌ๋“ค ๋”๋ณด๊ธฐ

-
+
diff --git a/src/pages/profileList/newList/VNewList.tsx b/src/pages/profileList/newList/VNewList.tsx index ba9b4f77..fbd138f0 100644 --- a/src/pages/profileList/newList/VNewList.tsx +++ b/src/pages/profileList/newList/VNewList.tsx @@ -22,11 +22,11 @@ export interface Props { } const VNewList = (props: Props) => ( -
-

+
+

์‹ ๊ทœ ์• ๋‹ˆ๋ชจ๋ฆฌ ์นœ๊ตฌ๋“ค

-
+
@@ -36,7 +36,7 @@ const VNewList = (props: Props) => (
-
+
{ return ( -
-

+
+

๊ธด๊ธ‰ ๋„์›€์ด ํ•„์š”ํ•ด์š”!

-
+
@@ -37,7 +37,7 @@ const VUrgentList = (props: Props) => {
-
+
void; } -const Calendar = ({ handleClick }: Props) => { +const Calendar = ({ handleClick }: CalendarProps) => { const dayOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const months = [ 'January', @@ -255,16 +255,16 @@ const Calendar = ({ handleClick }: Props) => {
- + {dayOfWeek.map((row: string, index: number) => ( - + ))} {calendarRows.map((row: JSX.Element[], index: number) => ( - + {row} ))} diff --git a/src/pages/register/DetailRadio.tsx b/src/pages/register/DetailRadio.tsx index 75ad6373..af7fa5be 100644 --- a/src/pages/register/DetailRadio.tsx +++ b/src/pages/register/DetailRadio.tsx @@ -3,7 +3,8 @@ interface RadioProps { label: string; value: string; selected?: boolean; - onChange: (e: any) => void; + onChange: (e: React.ChangeEvent) => void; + onClick: (e: React.MouseEvent) => void; } const DetailRadio = ({ @@ -12,6 +13,7 @@ const DetailRadio = ({ value, selected, onChange, + onClick, }: RadioProps) => { return (
@@ -23,6 +25,7 @@ const DetailRadio = ({ value={value} defaultChecked={selected} onChange={onChange} + onClick={onClick} /> {label} diff --git a/src/pages/register/MRegisterForm.tsx b/src/pages/register/MRegisterForm.tsx index 1241bcf5..44c49f15 100644 --- a/src/pages/register/MRegisterForm.tsx +++ b/src/pages/register/MRegisterForm.tsx @@ -4,7 +4,6 @@ import VMRegisterForm from './VMRegisterForm'; const MRegisterForm = () => { const [petInfo, setPetInfo] = useRecoilState(registerState); - const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -57,21 +56,45 @@ const MRegisterForm = () => { // useState์˜ setํ•จ์ˆ˜๋กœ petInfo๋ฅผ ์—…๋ฐ์ดํŠธํ•ด๋„, handleChange๊ฐ€ ์‹คํ–‰๋  ๋•Œ์˜ petInfo๋Š” ์—…๋ฐ์ดํŠธ ์ „์˜ petInfo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. // ๋”ฐ๋ผ์„œ tempPetInfo๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ตœ์‹ ์˜ petInfo๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. const tempPetInfo = { ...petInfo, [fieldName]: newValue }; - const allFieldsFilled = Object.values(tempPetInfo).every((value, index) => { - // isComplete๋Š” petInfo์˜ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์ฑ„์›Œ์ ธ ์žˆ์„ ๋•Œ true - if (index === Object.values(petInfo).length - 1) { - return true; - } - return !!value; + const list = [ + tempPetInfo.age, + tempPetInfo.name, + tempPetInfo.adoptionStatus, + tempPetInfo.type, + tempPetInfo.weight, + tempPetInfo.description, + tempPetInfo.sex, + tempPetInfo.size, + tempPetInfo.vaccinationStatus, + tempPetInfo.neutralizationStatus, + ]; + console.log(list); + const lili = list.filter((item) => { + return item !== ''; }); - if (allFieldsFilled) { + if (lili.length === 10) { setPetInfo((prev) => ({ ...prev, isComplete: true })); - } + } else setPetInfo((prev) => ({ ...prev, isComplete: false })); }; + // ์•ˆ์“ฐ๋Š” ์ด์œ  ๊ฐ’์ด ์—†์–ด์„œ ์•ˆ๋งŒ๋“ค์–ด์ง€๊ณ  ์—…๋ฐ์ดํŠธ๊ฐ€ ์ž˜์•ˆ๋จ. + // const allFieldsFilled = Object.values(tempPetInfo).every((value, index) => { + // console.log(value); + // // isComplete๋Š” petInfo์˜ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์ฑ„์›Œ์ ธ ์žˆ์„ ๋•Œ true + // if (index === Object.values(tempPetInfo).length - 2) { + // console.log(index, Object.values(tempPetInfo).length - 2); + // return true; + // } + // console.log(index); + // return !!value; + // }); + // if (allFieldsFilled) { + // setPetInfo((prev) => ({ ...prev, isComplete: true })); + // } + // }; + const MRegisterProps = { handleChange, - handleSubmit, }; return ; diff --git a/src/pages/register/RadioGroup.tsx b/src/pages/register/RadioGroup.tsx index fe345012..9ab2a90e 100644 --- a/src/pages/register/RadioGroup.tsx +++ b/src/pages/register/RadioGroup.tsx @@ -1,95 +1,61 @@ -import registerState from 'recoil/registerState'; +import registerState, { RegisterType } from 'recoil/registerState'; import { useRecoilState } from 'recoil'; -import DetailRadio from './DetailRadio'; +import { useEffect } from 'react'; +import VRadioGroup from './VRadioGroup'; const RadioGroup = () => { - const [registerData, setRegisterData] = useRecoilState(registerState); + const [petInfo, setPetInfo] = useRecoilState(registerState); - const handleSexChange = (value: string) => { - setRegisterData({ ...registerData, sex: value }); + const handleChange = (event: React.ChangeEvent) => { + const target = event.target as HTMLInputElement; + const fieldName = target.name; + const newValue = target.value; + setPetInfo((prev) => ({ ...prev, [fieldName]: newValue })); + + // useState์˜ setํ•จ์ˆ˜๋กœ petInfo๋ฅผ ์—…๋ฐ์ดํŠธํ•ด๋„, handleChange๊ฐ€ ์‹คํ–‰๋  ๋•Œ์˜ petInfo๋Š” ์—…๋ฐ์ดํŠธ ์ „์˜ petInfo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. + // ๋”ฐ๋ผ์„œ tempPetInfo๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ตœ์‹ ์˜ petInfo๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. + const tempPetInfo = { ...petInfo, [fieldName]: newValue }; + const list = [ + tempPetInfo.age, + tempPetInfo.name, + tempPetInfo.adoptionStatus, + tempPetInfo.type, + tempPetInfo.weight, + tempPetInfo.description, + tempPetInfo.sex, + tempPetInfo.size, + tempPetInfo.vaccinationStatus, + tempPetInfo.neutralizationStatus, + ]; + const lili = list.filter((item) => { + return item !== ''; + }); + if (lili.length === 10) { + setPetInfo((prev) => ({ ...prev, isComplete: true })); + } else setPetInfo((prev) => ({ ...prev, isComplete: false })); }; + const handleSexChange = (value: string) => { + setPetInfo((prev) => ({ ...prev, sex: value })); + }; const handleAdoptionStatusChange = (value: string) => { - setRegisterData({ ...registerData, adoptionStatus: value }); + setPetInfo((prev) => ({ ...prev, adoptionStatus: value })); }; - const handleNeutralizationStatusChange = (value: string) => { - setRegisterData({ ...registerData, neutralizationStatus: value }); + setPetInfo((prev) => ({ ...prev, neutralizationStatus: value })); }; + useEffect(() => { + // petInfo ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์›ํ•˜๋Š” ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + }, [petInfo.sex, petInfo.neutralizationStatus, petInfo.adoptionStatus]); - return ( -
-
-

์„ฑ๋ณ„

-
- handleSexChange('MALE')} - /> - handleSexChange('FEMALE')} - /> -
-
+ const RadioGroupProps = { + handleChange, + handleSexChange, + handleAdoptionStatusChange, + handleNeutralizationStatusChange, + }; -
-

- ์ž…์–‘ ์ƒํƒœ -

-
- handleAdoptionStatusChange('YES')} - /> - handleAdoptionStatusChange('NO')} - /> -
-
-
-

- ์ค‘์„ฑํ™” ์ƒํƒœ -

-
- handleNeutralizationStatusChange('YES')} - /> - handleNeutralizationStatusChange('NO')} - /> - handleNeutralizationStatusChange('UNKNOWN')} - /> -
-
-
- ); + return ; }; export default RadioGroup; diff --git a/src/pages/register/RegisterHeader.tsx b/src/pages/register/RegisterHeader.tsx index 0f99dbb1..287cb567 100644 --- a/src/pages/register/RegisterHeader.tsx +++ b/src/pages/register/RegisterHeader.tsx @@ -1,4 +1,5 @@ import { useMutation } from '@tanstack/react-query'; +import { getCookie } from 'commons/cookie/cookie'; import ModalPortal from 'commons/modals/ModalPortal'; import RegisterModal, { RegisterModalProps, @@ -9,48 +10,6 @@ import { useRecoilValue } from 'recoil'; import registerState from 'recoil/registerState'; import ImageVideoInput from './ImageVideoInput'; -interface PetPostProps { - name: string; - age: string; - type: string; - weight: number; - size: string; - sex: string; - vaccinationStatus: string; - adoptionStatus: string; - neutralizationStatus: string; - description: string; - petPolygonProfileDto: { - intelligence: number; - affinity: number; - athletic: number; - adaptability: number; - activeness: number; - }; - protectionExpirationDate: string; -} - -const mockPetData: PetPostProps = { - name: '๋ฝ€์‚', - age: '1๋…„1๊ฐœ์›”', - type: 'DOG', - weight: 1, - size: '์ œ๊ฐ€ ๋ณด๋‚ธ ์š”์ฒญ', - sex: 'MALE', - vaccinationStatus: 'YES', - adoptionStatus: 'YES', - neutralizationStatus: 'YES', - description: '์ž˜ ๋ฐ›์•„์กŒ์„๊นŒ์š”', - petPolygonProfileDto: { - intelligence: 1, - affinity: 1, - athletic: 1, - adaptability: 1, - activeness: 1, - }, - protectionExpirationDate: '2021-10-25', -}; - const RegisterHeader = () => { const [selectedImageFile, setSelectedImageFile] = useState(null); const [selectedVideoFile, setSelectedVideoFile] = useState(null); @@ -72,33 +31,28 @@ const RegisterHeader = () => { // ๋“ฑ๋กํ•˜๊ธฐ ๊ด€๋ จ const postPet = async (formData: FormData) => { + const loginToken = getCookie('loginToken'); const res = await fetch(`${process.env.REACT_APP_URI}/pet`, { method: 'POST', body: formData, + headers: { + Authorization: `Bearer ${loginToken}`, + }, }); return res.json(); }; - const { data, mutate, isError, isLoading, isSuccess } = useMutation(postPet, { - onError: (err: unknown) => { - console.log(err); - }, - onSuccess: (res) => { - console.log(res); - }, - }); + const { data, mutate, isError, isLoading, isSuccess } = useMutation(postPet); const handleRegisterButtonClick = async () => { - if (!selectedImageFile || !selectedVideoFile || registerPetData.isComplete) + console.log(registerPetData); + if (!selectedImageFile || !selectedVideoFile || !registerPetData.isComplete) return; - // destructuring์„ ์ด์šฉํ•ด์„œ isComplete๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ rest์— ๋‹ด์Œ - // api์— ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ๋Š” rest + image + video - // const { isComplete, ...rest } = registerPetData; - console.log(mockPetData); const formData = new FormData(); formData.append('profileVideo', selectedVideoFile); formData.append('profileImage', selectedImageFile); + const { isComplete, ...restRegisterPetData } = registerPetData; formData.append( 'petInfo', - new Blob([JSON.stringify(mockPetData)], { + new Blob([JSON.stringify(restRegisterPetData)], { type: 'application/json', }), ); @@ -130,9 +84,7 @@ const RegisterHeader = () => { handleModalOutsideClick, handleRegisterButtonClick, handleRegisterMoreButtonClick: () => { - setIsModalOpen(false); - setSelectedImageFile(null); - setSelectedVideoFile(null); + window.location.reload(); }, handleRegisterFinishButtonClick: () => { navigate('/'); @@ -140,6 +92,8 @@ const RegisterHeader = () => { isLoading, isSuccess, isError, + data, + modalString: '๋“ฑ๋ก', }; return ( <> diff --git a/src/pages/register/StatusScore.tsx b/src/pages/register/StatusScore.tsx index f613a4f7..cc17093e 100644 --- a/src/pages/register/StatusScore.tsx +++ b/src/pages/register/StatusScore.tsx @@ -1,8 +1,8 @@ interface StatusProps { status: string; score: number; - selectedOption: string; - handleChange: (status: string, option: string) => void; + selectedOption: number; + handleChange: (status: string, option: number) => void; } const Dash = () => { @@ -21,7 +21,7 @@ const Dash = () => { const StatusScore = ({ status, selectedOption, handleChange }: StatusProps) => { const getButtonColor = (option: string) => { return `appearance-none ${ - selectedOption !== option + String(selectedOption) !== option ? 'border-[#C4C4C4] bg-[#C4C4C4]' : 'border-brand-color bg-brand-color' } border-8 rounded-full w-[50px] h-[50px] cursor-pointer`; @@ -36,35 +36,35 @@ const StatusScore = ({ status, selectedOption, handleChange }: StatusProps) => { type="radio" value="1" className={getButtonColor('1')} - onChange={() => handleChange(status, '1')} + onClick={() => handleChange(status, 1)} /> handleChange(status, '2')} + onClick={() => handleChange(status, 2)} /> handleChange(status, '3')} + onClick={() => handleChange(status, 3)} /> handleChange(status, '4')} + onClick={() => handleChange(status, 4)} /> handleChange(status, '5')} + onClick={() => handleChange(status, 5)} /> High
diff --git a/src/pages/register/StatusSelectGroup.tsx b/src/pages/register/StatusSelectGroup.tsx index c9622053..ae962503 100644 --- a/src/pages/register/StatusSelectGroup.tsx +++ b/src/pages/register/StatusSelectGroup.tsx @@ -5,34 +5,33 @@ import StatusScore from './StatusScore'; const StatusSelectGroup = () => { const [profileStatus, setProfileStatus] = useRecoilState(registerState); - const { polygonProfile } = profileStatus; + const { petPolygonProfileDto } = profileStatus; - const [intelligenceOption, setIntelligenceOption] = useState('3'); - const [affinityOption, setAffinityOption] = useState('3'); - const [athleticOption, setAthleticOption] = useState('3'); - const [adaptabilityOption, setAdaptabilityOption] = useState('3'); - const [activenessOption, setActivenessOption] = useState('3'); + const [intelligenceOption, setIntelligenceOption] = useState(3); + const [affinityOption, setAffinityOption] = useState(3); + const [athleticOption, setAthleticOption] = useState(3); + const [adaptabilityOption, setAdaptabilityOption] = useState(3); + const [activenessOption, setActivenessOption] = useState(3); - const handleOptionChange = (status: string, option: string) => { + const handleOptionChange = (status: string, option: number) => { switch (status) { case 'intelligence': setIntelligenceOption(option); setProfileStatus((prev) => ({ ...prev, - polygonProfile: { - ...polygonProfile, - intelligence: Number(option), + petPolygonProfileDto: { + ...prev.petPolygonProfileDto, + intelligence: option, }, })); - break; case 'affinity': setAffinityOption(option); setProfileStatus((prev) => ({ ...prev, - polygonProfile: { - ...polygonProfile, - affinity: Number(option), + petPolygonProfileDto: { + ...prev.petPolygonProfileDto, + affinity: option, }, })); break; @@ -40,9 +39,9 @@ const StatusSelectGroup = () => { setAthleticOption(option); setProfileStatus((prev) => ({ ...prev, - polygonProfile: { - ...polygonProfile, - athletic: Number(option), + petPolygonProfileDto: { + ...prev.petPolygonProfileDto, + athletic: option, }, })); break; @@ -50,9 +49,9 @@ const StatusSelectGroup = () => { setAdaptabilityOption(option); setProfileStatus((prev) => ({ ...prev, - polygonProfile: { - ...polygonProfile, - adaptability: Number(option), + petPolygonProfileDto: { + ...prev.petPolygonProfileDto, + adaptability: option, }, })); break; @@ -60,9 +59,9 @@ const StatusSelectGroup = () => { setActivenessOption(option); setProfileStatus((prev) => ({ ...prev, - polygonProfile: { - ...polygonProfile, - activeness: Number(option), + petPolygonProfileDto: { + ...prev.petPolygonProfileDto, + activeness: option, }, })); break; @@ -75,31 +74,31 @@ const StatusSelectGroup = () => {
diff --git a/src/pages/register/VDayModalInput.tsx b/src/pages/register/VDayModalInput.tsx index fd7b2ed8..00e6cca0 100644 --- a/src/pages/register/VDayModalInput.tsx +++ b/src/pages/register/VDayModalInput.tsx @@ -2,17 +2,17 @@ import Calendar from 'pages/register/Calendar'; import { useRecoilValue } from 'recoil'; import registerState from 'recoil/registerState'; -interface Props { +interface DayModalProps { open: boolean; handleClick: () => void; } -const VDayModalInput = ({ open, handleClick }: Props) => { +const VDayModalInput = ({ open, handleClick }: DayModalProps) => { const protectionDate = useRecoilValue(registerState); const { protectionExpirationDate } = protectionDate; return ( -
+
@@ -23,10 +23,11 @@ const VDayModalInput = ({ open, handleClick }: Props) => { type="text" placeholder="๋‚ ์งœ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”." onClick={handleClick} - defaultValue={protectionExpirationDate} - autoComplete="date" + value={protectionExpirationDate} + autoComplete="off" + readOnly /> - +
diff --git a/src/pages/register/VMRegisterForm.tsx b/src/pages/register/VMRegisterForm.tsx index c568da4d..b5010be3 100644 --- a/src/pages/register/VMRegisterForm.tsx +++ b/src/pages/register/VMRegisterForm.tsx @@ -6,12 +6,12 @@ type RegisterProps = { handleChange: (event: React.ChangeEvent) => void; }; -const MRegisterForm = ({ handleChange }: RegisterProps) => { +const VMRegisterForm = ({ handleChange }: RegisterProps) => { return ( -
-
-
-
+
+
+
+
{ autocomplete="on" />
-
+
{ }} autocomplete="on" /> +
-
-
+
-
+
{ />
-
+
{ />
-
+
{ ); }; -export default MRegisterForm; +export default VMRegisterForm; diff --git a/src/pages/register/VRadioGroup.tsx b/src/pages/register/VRadioGroup.tsx new file mode 100644 index 00000000..a3102fb8 --- /dev/null +++ b/src/pages/register/VRadioGroup.tsx @@ -0,0 +1,151 @@ +import registerState, { RegisterType } from 'recoil/registerState'; +import { useRecoilState } from 'recoil'; +import DetailRadio from './DetailRadio'; + +export function checkIfAllFilled(data: RegisterType) { + const allFieldsFilled = Object.values(data).every((value) => !!value); + return allFieldsFilled; +} + +type RadioGroupProps = { + handleChange: (event: React.ChangeEvent) => void; + handleSexChange: (arg0: string) => void; + handleAdoptionStatusChange: (arg0: string) => void; + handleNeutralizationStatusChange: (arg0: string) => void; +}; + +const VRadioGroup = ({ + handleChange, + handleSexChange, + handleAdoptionStatusChange, + handleNeutralizationStatusChange, +}: RadioGroupProps) => { + const [petInfo, setPetInfo] = useRecoilState(registerState); + + return ( +
+
+

์„ฑ๋ณ„

+
+ { + handleSexChange('MALE'); + console.log(petInfo.sex); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> + { + handleSexChange('FEMALE'); + console.log(petInfo.sex); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> +
+
+ +
+

+ ์ž…์–‘ ์ƒํƒœ +

+
+ { + handleAdoptionStatusChange('YES'); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> + { + handleAdoptionStatusChange('NO'); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> +
+
+
+

+ ์ค‘์„ฑํ™” ์ƒํƒœ +

+
+ { + handleNeutralizationStatusChange('YES'); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> + { + handleNeutralizationStatusChange('NO'); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> + { + handleNeutralizationStatusChange('UNKNOWN'); + setPetInfo((prev) => ({ + ...prev, + isComplete: checkIfAllFilled(prev), + })); + }} + onChange={(e) => handleChange(e)} + /> +
+
+
+ ); +}; + +export default VRadioGroup; diff --git a/src/pages/signUp/SignupInputForm.tsx b/src/pages/signUp/SignupInputForm.tsx index 057a2fd8..b0846711 100644 --- a/src/pages/signUp/SignupInputForm.tsx +++ b/src/pages/signUp/SignupInputForm.tsx @@ -1,11 +1,45 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useRecoilState } from 'recoil'; import { shelterSignupState } from 'recoil/shelterState'; import VSignupInputForm from './VSignupInputForm'; +export interface EmailConfirmProps { + isValid: boolean; + checked: boolean; +} + +interface EmailValidProps { + validText: string; + inValidText: string; + emailConfirmObj: EmailConfirmProps; +} + const SignupInputForm = () => { const [shelterInfo, setShelterInfo] = useRecoilState(shelterSignupState); - const [confirm, setConfirm] = useState(false); + // confirm state์˜ ๊ฒฝ์šฐ, ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๋•Œ false + // isValid: ์ค‘๋ณต๊ฒ€์‚ฌ ํ†ต๊ณผํ–ˆ๋Š”๊ฐ€? + // checked: ์ด๋ฉ”์ผ ์ค‘๋ณต ๊ฒ€์‚ฌ๋ฅผ ํ–ˆ๋Š”๊ฐ€? + const [emailConfirm, setEmailConfirm] = useState({ + isValid: true, + checked: false, + }); + const [passwordConfirm, setPasswordConfirm] = useState(true); + const { isValid, checked } = emailConfirm; + const [emailValidText, setEmailValidText] = useState(''); + const [emailInValidText, setEmailInValidText] = useState(''); + + const navigate = useNavigate(); + + const getEmailValidText = ({ + validText, + inValidText, + emailConfirmObj, + }: EmailValidProps) => { + setEmailValidText(validText); + setEmailInValidText(inValidText); + setEmailConfirm(emailConfirmObj); + }; const duplicateCheck = () => { // shelterInfo.email @@ -19,9 +53,39 @@ const SignupInputForm = () => { }), }) .then((res) => { - res.json(); + return res.json(); }) - .then((data) => console.log('Data', data)); + .then((data) => { + if (!data.success) { + getEmailValidText({ + validText: '', + inValidText: data.error.message, + emailConfirmObj: { + isValid: false, + checked: false, + }, + }); + } else if (!shelterInfo.email) { + getEmailValidText({ + validText: '', + inValidText: '', + // ์•ˆ ๋„ฃ์œผ๋ฉด ๋นˆ์นธ์œผ๋กœ ๊ณต๊ฐ„ ์ฐจ์ง€ํ•ด์„œ ์ด๋ ‡๊ฒŒ ์กฐ๊ฑด ๋„ฃ์–ด์คŒ + emailConfirmObj: { + isValid: false, + checked: true, + }, + }); + } else { + getEmailValidText({ + validText: '์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.', + inValidText: '', + emailConfirmObj: { + isValid: true, + checked: true, + }, + }); + } + }); }; const handleChange = (event: React.ChangeEvent) => { @@ -42,9 +106,9 @@ const SignupInputForm = () => { // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ๋ถ€๋ถ„ ๊ตฌํ˜„ case 'password-confirm': if (target.value !== shelterInfo.password) { - setConfirm(true); + setPasswordConfirm(false); } else { - setConfirm(false); + setPasswordConfirm(true); } break; default: @@ -54,29 +118,38 @@ const SignupInputForm = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - // ํšŒ์›๊ฐ€์ž… ์ •๋ณด ๋ณด๋‚ด๋Š” API ์ ์šฉํ•ด์•ผ ๋จ - fetch(`${process.env.REACT_APP_URI}/account/shelter`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - ...shelterInfo, - email: shelterInfo.email, - password: shelterInfo.password, - name: shelterInfo.name, - contact: shelterInfo.contact, - zonecode: shelterInfo.zonecode, - address: shelterInfo.address, - }), - }) - .then((res) => { - // const token = res.headers.get('Authorization'); - return res.json(); + // ์ค‘๋ณต ํ™•์ธ์ด ๋˜์ง€ ์•Š์•˜์„ ๋•Œ + if (!emailConfirm.checked) { + alert('์ด๋ฉ”์ผ ์ค‘๋ณต์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”'); + } + // ์ œ๋Œ€๋กœ ํ™•์ธ๋˜์—ˆ์„ ๋•Œ + if (emailConfirm.isValid && passwordConfirm) { + fetch(`${process.env.REACT_APP_URI}/account/shelter`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...shelterInfo, + email: shelterInfo.email, + password: shelterInfo.password, + name: shelterInfo.name, + contact: shelterInfo.contact, + zonecode: shelterInfo.zonecode, + address: shelterInfo.address, + }), }) - .then((data) => { - console.log(data); - }); + .then((res) => { + return res.json(); + }) + .then((data) => { + if (!data.success) { + alert(data.error.message); // ์ด ๋ถ€๋ถ„์€ ์ฃผ์†Œ ๋ฐ›๋Š” ๊ฑฐ ๋•Œ๋ฌธ์— ๊ทธ๋ƒฅ ํ…์ŠคํŠธ๋งŒ ๋„ฃ๊ธฐ ์• ๋งคํ•จ + return; + } + navigate('/login'); + }); + } }; const SignupInputFormProps = { @@ -84,7 +157,11 @@ const SignupInputForm = () => { handleChange, handleSubmit, duplicateCheck, - confirm, + passwordConfirm, + isValid, + checked, + emailValidText, + emailInValidText, }; return ; diff --git a/src/pages/signUp/VSignupInputForm.tsx b/src/pages/signUp/VSignupInputForm.tsx index 6ac51ad3..14eb83fa 100644 --- a/src/pages/signUp/VSignupInputForm.tsx +++ b/src/pages/signUp/VSignupInputForm.tsx @@ -2,19 +2,27 @@ import AddressInputGroup from 'pages/signUp/AddressInputGroup'; import InputGroup from 'commons/InputGroup'; import React from 'react'; -interface Props { +interface VSignupInputProps { handleChange: (event: React.ChangeEvent) => void; handleSubmit: (e: React.FormEvent) => void; duplicateCheck: () => void; - confirm: boolean; + isValid: boolean; + checked: boolean; + passwordConfirm: boolean; + emailValidText: string; + emailInValidText: string; } const VSignupInputForm = ({ handleChange, handleSubmit, duplicateCheck, - confirm, -}: Props) => { + isValid, + checked, + passwordConfirm, + emailValidText, + emailInValidText, +}: VSignupInputProps) => { return (
+ {checked && isValid && ( +
{emailValidText}
+ )} + {!checked && !isValid && ( +
{emailInValidText}
+ )} - {confirm && ( + {!passwordConfirm && (
๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
)} +
+ +
+ {isModalOpen && ( + + + + )} + + ); +}; + +export default UpdateHeader; diff --git a/src/pages/update/UpdateInput.tsx b/src/pages/update/UpdateInput.tsx new file mode 100644 index 00000000..2cc9292e --- /dev/null +++ b/src/pages/update/UpdateInput.tsx @@ -0,0 +1,17 @@ +// eslint-disable-next-line import/no-cycle +import { InputGroupProps } from './UpdateInputGroup'; + +const Input = ({ id, name, type, placeholder, onChange }: InputGroupProps) => { + return ( + + ); +}; + +export default Input; diff --git a/src/pages/update/UpdateInputGroup.tsx b/src/pages/update/UpdateInputGroup.tsx new file mode 100644 index 00000000..52d1ef21 --- /dev/null +++ b/src/pages/update/UpdateInputGroup.tsx @@ -0,0 +1,40 @@ +import Container from 'commons/Container'; +import { RegisterType } from 'recoil/registerState'; +// eslint-disable-next-line import/no-cycle +import Input from './UpdateInput'; + +export interface InputGroupProps { + id: string; + name: string; + type: string; + placeholder: string; + onChange: (e: React.ChangeEvent) => void; + value: any; +} + +const InputGroup = ({ + id, + name, + type, + placeholder, + onChange, + value, +}: InputGroupProps) => { + return ( + + + + + ); +}; + +export default InputGroup; diff --git a/src/pages/update/UpdatePage.tsx b/src/pages/update/UpdatePage.tsx new file mode 100644 index 00000000..6262f0c6 --- /dev/null +++ b/src/pages/update/UpdatePage.tsx @@ -0,0 +1,19 @@ +import { Suspense } from 'react'; +import GNB from 'layouts/GNB'; +import ErrorBoundary from 'commons/ErrorBoundary'; +import UpdateTemplate from './UpdateTemplate'; + +const UpdatePage = () => { + return ( + <> + + ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.
}> + }> + + + + + ); +}; + +export default UpdatePage; diff --git a/src/pages/update/UpdateRegisterForm.tsx b/src/pages/update/UpdateRegisterForm.tsx new file mode 100644 index 00000000..7e047328 --- /dev/null +++ b/src/pages/update/UpdateRegisterForm.tsx @@ -0,0 +1,50 @@ +import { useState } from 'react'; +import { useRecoilState } from 'recoil'; +import registerState, { RegisterType } from 'recoil/registerState'; +import VMRegisterForm from './VUpdateRegisterForm'; + +interface PetProps { + petInfo: RegisterType; +} + +const UpdateRegisterForm = ({ petInfo }: PetProps) => { + const [petInfoState, setPetInfo] = useRecoilState(registerState); + + const handleChange = (event: React.SyntheticEvent) => { + const target = event.target as HTMLInputElement; + const fieldName = target.id; + const newValue = target.value; + + const updatedPetInfo = { + ...petInfoState, + [fieldName]: newValue, + }; + + // ์ƒํƒœ ์—…๋ฐ์ดํŠธ + setPetInfo(updatedPetInfo); + + // useState์˜ setํ•จ์ˆ˜๋กœ petInfo๋ฅผ ์—…๋ฐ์ดํŠธํ•ด๋„, handleChange๊ฐ€ ์‹คํ–‰๋  ๋•Œ์˜ petInfo๋Š” ์—…๋ฐ์ดํŠธ ์ „์˜ petInfo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. + // ๋”ฐ๋ผ์„œ tempPetInfo๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ตœ์‹ ์˜ petInfo๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. + const tempPetInfo = { ...petInfo, [fieldName]: newValue }; + const allFieldsFilled = Object.values(tempPetInfo).every((value, index) => { + // isComplete๋Š” petInfo์˜ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์ฑ„์›Œ์ ธ ์žˆ์„ ๋•Œ true + if (index === Object.values(petInfo).length - 1) { + return true; + } + return !!value; + }); + if (allFieldsFilled) { + console.log('๊ฒ€์‚ฌ', petInfo); + setPetInfo((prev) => ({ ...prev, isComplete: true })); + } else setPetInfo((prev) => ({ ...prev, isComplete: false })); + }; + + const MRegisterProps = { + handleChange, + petInfo, + }; + + return ; +}; + +export default UpdateRegisterForm; diff --git a/src/pages/update/UpdateTemplate.tsx b/src/pages/update/UpdateTemplate.tsx new file mode 100644 index 00000000..3baf83fc --- /dev/null +++ b/src/pages/update/UpdateTemplate.tsx @@ -0,0 +1,80 @@ +import { useQuery } from '@tanstack/react-query'; +import { getCookie } from 'commons/cookie/cookie'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { useRecoilState } from 'recoil'; +import registerState from 'recoil/registerState'; +import DayModalInput from 'pages/register/DayModalInput'; +import UpdateHeader from './UpdateHeader'; +import PatchStatusSelectGroup from './PatchStatusSelectGroup'; +import UpdateRegisterForm from './UpdateRegisterForm'; + +const UpdateTemplate = () => { + const params = useParams(); + const petId = params.id; + const cookie = getCookie('loginToken'); + const [updateState, setUpdateState] = useRecoilState(registerState); + const [error, setError] = useState({ + isError: false, + errorMessage: '', + }); + + // petInfo return + const getPetInfo = async () => { + const res = await fetch( + `${process.env.REACT_APP_URI}/pet/register-info/${petId}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${cookie}`, + }, + }, + ) + .then((response) => { + return response.json(); + }) + .then((apiData) => { + if (apiData.success === false) { + throw new Error(apiData.message); + } + return { ...apiData.response }; + }); + return res; + }; + + const { data, isLoading, isError } = useQuery({ + queryKey: ['pet-update'], + queryFn: () => getPetInfo(), + onSuccess: (fetchedData) => { + const { profileImageUrl, profileShortFormUrl, ...rest } = fetchedData; + setUpdateState({ ...rest, isComplete: true }); + }, + suspense: true, + }); + + // ๋ฐ์ดํ„ฐ ๋“ค์–ด์˜ค๋Š” ๊ฒƒ ํ™•์ธ ์šฉ๋„ + useEffect(() => { + if (!isLoading && data) { + console.log('status: ', updateState); + } + }, [updateState]); + + if (isError) { + return
{error.errorMessage}
; + } + + return ( + <> + + + + + + ); +}; + +export default UpdateTemplate; diff --git a/src/pages/update/VUpdateRegisterForm.tsx b/src/pages/update/VUpdateRegisterForm.tsx new file mode 100644 index 00000000..ed4e41f6 --- /dev/null +++ b/src/pages/update/VUpdateRegisterForm.tsx @@ -0,0 +1,108 @@ +import RadioGroup from 'pages/register/RadioGroup'; +import SelectBox from 'commons/SelectBox'; +import { RegisterType } from 'recoil/registerState'; +import InputGroup from './UpdateInputGroup'; + +type RegisterProps = { + handleChange: (event: React.ChangeEvent) => void; + petInfo: RegisterType; +}; + +const VMRegisterForm = ({ handleChange, petInfo }: RegisterProps) => { + console.log('dk', petInfo); + console.log(petInfo.name); + return ( +
+
+
+
+ { + handleChange(e); + }} + value={petInfo.name} + /> +
+
+ { + handleChange(e); + }} + value={petInfo.age} + /> + +
+
+
+
+
+ +
+
+
+
+ { + handleChange(e); + }} + value={petInfo.size} + /> +
+
+
+
+ { + handleChange(e); + }} + value={petInfo.weight} + /> +
+
+ { + handleChange(e); + }} + value={petInfo.vaccinationStatus} + /> +
+
+
+
+ { + handleChange(e); + }} + value={petInfo.description} + /> +
+
+
+ ); +}; + +export default VMRegisterForm; diff --git a/src/recoil/registerState.ts b/src/recoil/registerState.ts index c7eb8aae..a7b26adb 100644 --- a/src/recoil/registerState.ts +++ b/src/recoil/registerState.ts @@ -12,7 +12,7 @@ export interface RegisterType { neutralizationStatus: string; // YES | NO | UNKNOWN protectionExpirationDate: string; // ๋ณดํ˜ธ๋งŒ๋ฃŒ์ผ null ๊ฐ€๋Šฅ 2023-10-25 description: string; // String - polygonProfile: { + petPolygonProfileDto: { // 1 ~ 5 ์ •์ˆ˜ intelligence: number; // "์˜๋ฆฌํ•จ ์ ์ˆ˜" affinity: number; // "์นœํ™”๋ ฅ ์ ์ˆ˜", @@ -37,12 +37,12 @@ const registerState = atom({ neutralizationStatus: '', protectionExpirationDate: '', description: '', - polygonProfile: { - intelligence: 1, - affinity: 1, - athletic: 1, - adaptability: 1, - activeness: 1, + petPolygonProfileDto: { + intelligence: 3, + affinity: 3, + athletic: 3, + adaptability: 3, + activeness: 3, }, isComplete: false, },
{row}