diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..1588f65
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,24 @@
+---
+name: ๐ Bug Report
+about: ๋ฒ๊ทธ๋ฅผ ๋ณด๊ณ ํฉ๋๋ค
+title: "[Bug] "
+labels: bug
+assignees: ''
+---
+
+## ๐ ๋ฒ๊ทธ ์ค๋ช
+- ๋ฒ๊ทธ์ ๋ํด ๊ฐ๋จํ ์ค๋ช
ํด์ฃผ์ธ์.
+
+## โ
์ฌํ ๋ฐฉ๋ฒ
+- ๋ฒ๊ทธ๋ฅผ ์ฌํํ๋ ค๋ฉด ๋ค์ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ธ์:
+ 1. [์: ํ์ด์ง ์ด๋ ๊ฒฝ๋ก]
+ 2. [์: ํน์ ๋ฒํผ ํด๋ฆญ]
+ 3. [์: ๊ธฐ๋๋๋ ๊ฒฐ๊ณผ]
+
+## ๐ฅ ํ๊ฒฝ ์ ๋ณด
+- OS: [Windows, macOS, Linux ๋ฑ]
+- Browser: [Chrome, Firefox ๋ฑ]
+- Version: [์ฑ/๋ธ๋ผ์ฐ์ ๋ฒ์ ]
+
+## ๐ธ ์คํฌ๋ฆฐ์ท
+- ๋ฒ๊ทธ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์คํฌ๋ฆฐ์ท์ ์ถ๊ฐํด์ฃผ์ธ์ (ํ์ํ ๊ฒฝ์ฐ).
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..9880a67
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,16 @@
+---
+name: โจ Feature Request
+about: ์๋ก์ด ๊ธฐ๋ฅ์ ์์ฒญํฉ๋๋ค
+title: "[Feature] "
+labels: enhancement
+assignees: ''
+---
+
+## ๐ ๊ธฐ๋ฅ ์ค๋ช
+- ์ถ๊ฐํ๊ณ ์ถ์ ๊ธฐ๋ฅ์ด๋ ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ๋ตํ ์ค๋ช
ํด์ฃผ์ธ์.
+
+## ๐ค ์ด์
+- ์ด ๊ธฐ๋ฅ์ด ์ ํ์ํ์ง, ์ด๋ค ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋์ง ์ค๋ช
ํด์ฃผ์ธ์.
+
+## ๐ ์ถ๊ฐ ์ ๋ณด
+- ๊ธฐ๋ฅ๊ณผ ๊ด๋ จ๋ ์ฐธ๊ณ ์๋ฃ๋ ์ถ๊ฐ ์ ๋ณด๋ฅผ ํฌํจํด์ฃผ์ธ์.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..3d75a26
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,34 @@
+## ๐ ์์ฝ
+- ์ด Pull Request์ ๋ชฉ์ ๊ณผ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ๋ตํ ์ค๋ช
ํด์ฃผ์ธ์.
+
+## ๐ ๋ณ๊ฒฝ ์ฌํญ
+- ์ด๋ฒ Pull Request์์ ์์
ํ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
+ - [x] ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ ๋๋ ๋ฒ๊ทธ ์์
+ - [x] ์ฝ๋ ๋ฆฌํฉํ ๋ง ๋๋ ์ต์ ํ
+ - [x] ๋ฌธ์ ์
๋ฐ์ดํธ
+
+## ๐ ๊ด๋ จ ์ด์
+- ์ด PR๋ก ํด๊ฒฐ๋๋ ์ด์: #[์ด์ ๋ฒํธ]
+- ๊ด๋ จ๋ ์ด์: #[์ด์ ๋ฒํธ]
+
+## ๐ธ ์คํฌ๋ฆฐ์ท ๋๋ GIF (ํด๋น๋๋ ๊ฒฝ์ฐ)
+- UI ๋ณ๊ฒฝ์ด ์๋ ๊ฒฝ์ฐ ๋ณ๊ฒฝ ์ฌํญ์ ๋ณด์ฌ์ฃผ๋ ์คํฌ๋ฆฐ์ท์ด๋ GIF๋ฅผ ์ถ๊ฐํด์ฃผ์ธ์.
+
+## โ
์ฒดํฌ๋ฆฌ์คํธ
+- [ ] ์ฝ๋๊ฐ ์ ์์ ์ผ๋ก ๋์ํ๋์ง ํ
์คํธํ์ต๋๋ค.
+- [ ] ๊ด๋ จ ๋ฌธ์๋ฅผ ์์ฑํ๊ฑฐ๋ ์
๋ฐ์ดํธํ์ต๋๋ค. (ํด๋น๋๋ ๊ฒฝ์ฐ)
+- [ ] ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ํ ํ
์คํธ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
+- [ ] ๋ชจ๋ ํ
์คํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ํต๊ณผํ์ต๋๋ค.
+
+## ๐ก ํ
์คํธ ๋ฐฉ๋ฒ
+- ๋ณ๊ฒฝ ์ฌํญ์ ํ์ธํ๊ธฐ ์ํ ํ
์คํธ ๋ฐฉ๋ฒ์ ๋จ๊ณ๋ณ๋ก ์์ฑํด์ฃผ์ธ์:
+ 1. ๋ธ๋์น๋ฅผ ๋ก์ปฌ๋ก ๊ฐ์ ธ์ต๋๋ค.
+ 2. ์๋ ๋ช
๋ น์ด๋ฅผ ์ฌ์ฉํด ์ ํ๋ฆฌ์ผ์ด์
๋๋ ํ
์คํธ๋ฅผ ์คํํฉ๋๋ค:
+ ```
+ # ์์ ๋ช
๋ น์ด
+ ./run_tests.sh
+ ```
+ 3. ์์ ๋์์ด๋ ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
+
+## ๐ ์ถ๊ฐ ์ฐธ๊ณ ์ฌํญ
+- ์ด PR๊ณผ ๊ด๋ จํด ๋ฆฌ๋ทฐ์ด๊ฐ ์์์ผ ํ ์ถ๊ฐ ๋ด์ฉ์ด ์๋ค๋ฉด ์์ฑํด์ฃผ์ธ์.
diff --git a/package-lock.json b/package-lock.json
index 47d6e6b..e7809f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,18 @@
{
- "name": "vite-project",
+ "name": "refrigerator-front",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "vite-project",
+ "name": "refrigerator-front",
"version": "0.0.0",
"dependencies": {
"@reduxjs/toolkit": "^2.5.0",
"axios": "^1.7.9",
+ "bootstrap": "^5.3.3",
"react": "^18.3.1",
+ "react-bootstrap": "^2.10.7",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"react-router-dom": "^6.28.0"
@@ -266,6 +268,18 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
@@ -991,6 +1005,31 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz",
+ "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
"node_modules/@reduxjs/toolkit": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
@@ -1024,6 +1063,60 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@restart/hooks": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
+ "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@restart/ui": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.2.tgz",
+ "integrity": "sha512-MWWqJqSyqUWWPBOOiRQrX57CBc/9CoYONg7sE+uag72GCAuYrHGU5c49vU5s4BUSBgiKNY6rL7TULqGDrouUaA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@popperjs/core": "^2.11.8",
+ "@react-aria/ssr": "^3.5.0",
+ "@restart/hooks": "^0.5.0",
+ "@types/warning": "^3.0.3",
+ "dequal": "^2.0.3",
+ "dom-helpers": "^5.2.0",
+ "uncontrollable": "^8.0.4",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ }
+ },
+ "node_modules/@restart/ui/node_modules/@restart/hooks": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.0.tgz",
+ "integrity": "sha512-wS+h6IusJCPjTkmOOrRZxIPICD/mtFA3PRZviutoM23/b7akyDGfZF/WS+nIFk27u7JDhPE2+0GBdZxjSqHZkg==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@restart/ui/node_modules/uncontrollable": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
+ "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.14.0"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
@@ -1290,6 +1383,15 @@
"win32"
]
},
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1353,14 +1455,12 @@
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -1377,12 +1477,27 @@
"@types/react": "^18.0.0"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
+ "node_modules/@types/warning": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
+ "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
+ "license": "MIT"
+ },
"node_modules/@vitejs/plugin-react": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
@@ -1642,6 +1757,25 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/bootstrap": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
+ "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1784,6 +1918,12 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1849,7 +1989,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/data-view-buffer": {
@@ -1976,6 +2115,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -1989,6 +2137,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2887,6 +3045,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -3532,7 +3699,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -3785,7 +3951,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -3793,6 +3958,19 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/prop-types-extra": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
+ "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
+ "license": "MIT",
+ "dependencies": {
+ "react-is": "^16.3.2",
+ "warning": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=0.14.0"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -3821,6 +3999,37 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-bootstrap": {
+ "version": "2.10.7",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.7.tgz",
+ "integrity": "sha512-w6mWb3uytB5A18S2oTZpYghcOUK30neMBBvZ/bEfA+WIF2dF4OGqjzoFVMpVXBjtyf92gkmRToHlddiMAVhQqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "@restart/hooks": "^0.4.9",
+ "@restart/ui": "^1.9.2",
+ "@types/prop-types": "^15.7.12",
+ "@types/react-transition-group": "^4.4.6",
+ "classnames": "^2.3.2",
+ "dom-helpers": "^5.2.1",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.8.1",
+ "prop-types-extra": "^1.1.0",
+ "react-transition-group": "^4.4.5",
+ "uncontrollable": "^7.2.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.8",
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
@@ -3838,7 +4047,12 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-redux": {
@@ -3906,6 +4120,22 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
@@ -3944,6 +4174,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
@@ -4373,6 +4609,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4483,6 +4725,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=15.0.0"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
@@ -4605,6 +4862,15 @@
}
}
},
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 259d0a5..1612935 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,9 @@
"dependencies": {
"@reduxjs/toolkit": "^2.5.0",
"axios": "^1.7.9",
+ "bootstrap": "^5.3.3",
"react": "^18.3.1",
+ "react-bootstrap": "^2.10.7",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"react-router-dom": "^6.28.0"
diff --git a/src/App.jsx b/src/App.jsx
index 7e46023..a237256 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,10 +1,30 @@
+import { BrowserRouter, Routes, Route } from "react-router-dom"
+import Layout from "./pages/layouts/Layout"
+import Ingredient from "./pages/ingredient/Ingregdient"
+import Recipe from "./pages/recipe/Recipe"
+import RecipeDetail from "./pages/recipe/RecipeDetail"
+import {AddRecipe} from "./pages/recipe/AddRecipe"
+import Join from "./pages/user/Join"
+
function App() {
+ return (
+
+
+ }>
+
+ }/>
+ {/* } /> */}
+
+
+ } />
+ } />
+ }/>
+
+ } />
+
- return (
-
- test
-
- )
+
+
+ )
}
-
-export default App
+export default App
\ No newline at end of file
diff --git a/src/assets/css/Ingredient/ingredient/Ingredient.module.css b/src/assets/css/Ingredient/ingredient/Ingredient.module.css
new file mode 100644
index 0000000..547c8d9
--- /dev/null
+++ b/src/assets/css/Ingredient/ingredient/Ingredient.module.css
@@ -0,0 +1,5 @@
+/* Ingredient.module.css */
+.userIngredient {
+ border: 3px solid black;
+ background-color: lightgreen;
+}
diff --git a/src/assets/css/Ingredient/userIngredient/UsersIngredient.module.css b/src/assets/css/Ingredient/userIngredient/UsersIngredient.module.css
new file mode 100644
index 0000000..991e216
--- /dev/null
+++ b/src/assets/css/Ingredient/userIngredient/UsersIngredient.module.css
@@ -0,0 +1,86 @@
+.container {
+ padding: 1rem;
+ background-color: #f0fff0; /* ์ฐํ ๋
น์ ๋ฐฐ๊ฒฝ */
+ text-align: center;
+}
+
+.cardContainer {
+ display: flex;
+ flex-wrap: wrap; /* ์ค๋ฐ๊ฟ ํ์ฉ */
+ justify-content: flex-start; /* ์นด๋๋ค์ ์ผ์ชฝ ์ ๋ ฌ */
+ gap: 1rem; /* ์นด๋ ๊ฐ๊ฒฉ */
+}
+
+.header {
+ display: flex;
+ flex-direction: column; /* ์์ง์ผ๋ก ๋ฐฐ์น */
+ align-items: center; /* ์ํ ๊ฐ์ด๋ฐ ์ ๋ ฌ */
+ justify-content: center; /* ์์ง ๊ฐ์ด๋ฐ ์ ๋ ฌ */
+ margin-bottom: 1rem; /* ์ ๋ชฉ๊ณผ ์นด๋ ๊ฐ๊ฒฉ ์ถ๊ฐ */
+ padding: 0 1rem;
+}
+
+.header h1 {
+ font-size: 1.8rem;
+ color: #333;
+ margin: 0 0 0.5rem; /* ์๋ ๊ฐ๊ฒฉ ์ถ๊ฐ */
+}
+
+.filterDropdown {
+ padding: 0.5rem 1rem;
+ font-size: 1rem;
+ border: 1px solid #ddd; /* ๋๋กญ๋ค์ด ํ
๋๋ฆฌ */
+ border-radius: 5px; /* ๋ชจ์๋ฆฌ ๋ฅ๊ธ๊ฒ */
+ cursor: pointer;
+ background-color: #ffffff; /* ๋๋กญ๋ค์ด ๋ฐฐ๊ฒฝ */
+ color: #333; /* ํ
์คํธ ์์ */
+ transition: border-color 0.3s ease;
+}
+
+.filterDropdown:focus {
+ outline: none;
+ border-color: #28a745; /* ์ด๋ก์ ํ
๋๋ฆฌ */
+}
+
+.filterDropdown:hover {
+ border-color: #28a745; /* ํธ๋ฒ ์ ํ
๋๋ฆฌ ์์ */
+}
+
+.filters {
+ display: flex;
+ gap: 1rem; /* ํํฐ ๊ฐ ๊ฐ๊ฒฉ */
+ align-items: center;
+}
+
+.filterDropdown {
+ padding: 0.5rem 1rem;
+ font-size: 1rem;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ cursor: pointer;
+ background-color: #ffffff;
+ color: #333;
+ transition: border-color 0.3s ease;
+}
+
+.favoriteButton {
+ padding: 0.5rem 1rem;
+ font-size: 1rem;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ cursor: pointer;
+ background-color: #ffffff;
+ color: #333;
+ transition: background-color 0.3s ease, border-color 0.3s ease;
+}
+
+.favoriteButton.active {
+ background-color: #28a745; /* ํ์ฑํ ์ํ */
+ color: white; /* ํ
์คํธ ์์ */
+ border-color: #28a745;
+}
+
+.favoriteButton:hover {
+ background-color: #218838; /* Hover ์ ์ด๋์ด ์ด๋ก์ */
+ color: white;
+}
\ No newline at end of file
diff --git a/src/assets/css/Ingredient/userIngredient/UsersIngredientItem.module.css b/src/assets/css/Ingredient/userIngredient/UsersIngredientItem.module.css
new file mode 100644
index 0000000..5b02d62
--- /dev/null
+++ b/src/assets/css/Ingredient/userIngredient/UsersIngredientItem.module.css
@@ -0,0 +1,102 @@
+.card {
+ width: 260px; /* ๊ฐ๋ก ๊ธธ์ด */
+ height: auto; /* ์ธ๋ก ๊ธธ์ด ์๋ ์กฐ์ */
+ background-color: #ffffff; /* ์นด๋ ๋ฐฐ๊ฒฝ */
+ padding: 0.5rem; /* ๋ด๋ถ ์ฌ๋ฐฑ ์ต์ํ */
+ border: 1px solid #ddd;
+ border-radius: 10px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between; /* ์๋จ, ์ค๋จ, ํ๋จ ์์ญ ๋ถ๋ฆฌ */
+ text-align: left;
+ position: relative; /* ์ ์ฒด ์นด๋ ๋ด๋ถ ๋ฐฐ์น */
+ transition: all 0.3s ease;
+}
+
+.card img {
+ border-radius: 10px;
+ height: 120px; /* ์ด๋ฏธ์ง ๋์ด */
+ width: 100%;
+ object-fit: cover; /* ์ด๋ฏธ์ง ๋น์จ ์ ์ง */
+ margin-bottom: 0; /* ์ด๋ฏธ์ง์ ํ
์คํธ ๊ฐ๊ฒฉ ์ ๊ฑฐ */
+}
+
+/* ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ฒํผ๊ณผ ์๋ ์์ญ */
+.favoriteAndQuantity {
+ display: flex;
+ justify-content: space-between; /* ์ข์ฐ๋ก ๋ฐฐ์น */
+ align-items: center;
+ margin-top: -30px; /* ์ด๋ฏธ์ง์ ๊ฒน์น๋๋ก ์กฐ์ */
+ padding: 0; /* ์ข์ฐ ์ฌ๋ฐฑ ์ ๊ฑฐ */
+}
+
+.favoriteButton {
+ display: flex; /* Flexbox๋ก ๋ด๋ถ ์ ๋ ฌ */
+ align-items: center; /* ์ธ๋ก ๊ฐ์ด๋ฐ ์ ๋ ฌ */
+ justify-content: center; /* ๊ฐ๋ก ๊ฐ์ด๋ฐ ์ ๋ ฌ */
+ background-color: rgba(207, 205, 205, 0.7); /* ๋ฐํฌ๋ช
๋ฐฐ๊ฒฝ */
+ border: none;
+ font-size: 0.9rem; /* ๋ณ ํฌ๊ธฐ ์กฐ์ */
+ cursor: pointer;
+ border-radius: 50%; /* ๋ฒํผ์ ๋๊ทธ๋๊ฒ */
+ width: 1.5rem; /* ๋ฒํผ ๋๋น */
+ height: 1.5rem; /* ๋ฒํผ ๋์ด */
+ padding: 0; /* ๋ด๋ถ ์ฌ๋ฐฑ ์ ๊ฑฐ */
+ margin-top: 2px;
+}
+
+.favoriteButton:hover {
+ background-color: rgb(66, 55, 55)/* Hover ์ ์์ ๋ณ๊ฒฝ */
+}
+
+
+
+/* ์๋ ํ์ */
+.quantity {
+ background-color: rgba(207, 205, 205, 0.7); /* ๋ฐํฌ๋ช
๋ฐฐ๊ฒฝ */
+ color: black;
+ padding: 0.2rem 0.4rem; /* ์ฌ๋ฐฑ ์กฐ์ */
+ border-radius: 5px;
+ font-size: 0.85rem; /* ํ
์คํธ ํฌ๊ธฐ ์กฐ์ */
+}
+
+/* ์นด๋ ํ
์คํธ ์น์
*/
+.details {
+ font-size: 0.85rem;
+ color: #666;
+ margin-top: 0.1rem; /* ํ
์คํธ ๊ฐ๊ฒฉ ์ต์ํ */
+ line-height: 1.4;
+}
+
+.details strong {
+ color: #333;
+ font-weight: bold;
+}
+
+.title {
+ font-size: 1rem;
+ font-weight: bold;
+ margin: 0.2rem 0; /* ์ด๋ฏธ์ง์ ์ด๋ฆ ๊ฐ๊ฒฉ ์ต์ํ */
+ text-align: center;
+}
+
+/* ์ฌ์ฉํ๊ธฐ ๋ฒํผ */
+.useButton {
+ align-self: flex-end; /* ์ค๋ฅธ์ชฝ ์ ๋ ฌ */
+ background-color: rgba(207, 205, 205, 1);
+ color: black;
+ font-weight: 500;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 0.85rem;
+ position: absolute; /* ์ค๋ฅธ์ชฝ ํ๋จ ๊ณ ์ */
+ bottom: 10px;
+ right: 10px;
+ z-index: 5; /* ๋ค๋ฅธ ์์์ ๊ฒน์น์ง ์๋๋ก */
+}
+
+.useButton:hover {
+ background-color: rgb(179, 178, 178);
+}
diff --git a/src/assets/css/default.css b/src/assets/css/default.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/assets/css/main.module.css b/src/assets/css/main.module.css
new file mode 100644
index 0000000..21aa0a2
--- /dev/null
+++ b/src/assets/css/main.module.css
@@ -0,0 +1,25 @@
+@font-face {
+ font-family: 'Pretendard-Regular';
+ src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff');
+ font-weight: 400;
+ font-style: normal;
+}
+
+* {
+ font-family: Pretendard-Regular;
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+.mainContainer {
+ display: flex;
+ flex-direction: column; /* ์ํ ๋ ์ด์์ ์ ๋ ฌ */
+ min-height: 1200px; /* ํ๋ฉด ์ ์ฒด ๋์ด */
+ background-color: #f9f9f9;
+}
+
+main {
+ flex: 1; /* ๋ฉ์ธ ์ฝํ
์ธ ๊ฐ ๋จ์ ๊ณต๊ฐ์ ์ฐจ์ง */
+ padding: 1rem;
+}
\ No newline at end of file
diff --git a/src/assets/css/main/footer.module.css b/src/assets/css/main/footer.module.css
new file mode 100644
index 0000000..4da668b
--- /dev/null
+++ b/src/assets/css/main/footer.module.css
@@ -0,0 +1,75 @@
+.footer {
+ display: flex;
+ justify-content: space-between; /* ์ข์ฐ๋ก ์์ญ ๋๋ */
+ align-items: center;
+ background-color: #e0f7ff; /* ์ฐํ ํ๋์ ๋ฐฐ๊ฒฝ */
+ color: #333;
+ padding: 2rem;
+}
+
+.leftSection {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start; /* ์ผ์ชฝ ์ ๋ ฌ */
+ gap: 1rem;
+}
+
+.logo {
+ font-size: 2.2rem;
+ font-weight: bold;
+ margin: 0;
+}
+
+.logoReci {
+ color: black;
+}
+
+.logoPick {
+ color: #007bff; /* ํ๋์ */
+}
+
+.socialIcons {
+ display: flex;
+ gap: 1rem;
+}
+
+.icon {
+ font-size: 1.1rem;
+ text-decoration: none;
+ color: black;
+ transition: transform 0.3s ease, color 0.3s ease;
+}
+
+.icon:hover {
+ transform: scale(1.2); /* ์์ด์ฝ ํ๋ */
+ color: #007bff;
+}
+
+.rightSection {
+ display: flex;
+ gap: 3rem; /* ๊ฐ ๋งํฌ ์น์
๊ฐ ๊ฐ๊ฒฉ */
+ text-align: left;
+}
+
+.linksGroup {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.linksGroup h3 {
+ font-size: 1.2rem;
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+}
+
+.linksGroup a {
+ text-decoration: none;
+ color: #333;
+ font-size: 1rem;
+ transition: color 0.3s ease;
+}
+
+.linksGroup a:hover {
+ color: #007bff;
+}
diff --git a/src/assets/css/main/header.module.css b/src/assets/css/main/header.module.css
new file mode 100644
index 0000000..7115f26
--- /dev/null
+++ b/src/assets/css/main/header.module.css
@@ -0,0 +1,44 @@
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 2rem;
+ color: black;
+ box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
+ background-color: white;
+ margin-bottom: 15px;
+ position: sticky; /* ์คํฌ๋กค ์ ์๋จ ๊ณ ์ */
+ top: 0;
+ z-index: 1000; /* ๋ค๋ฅธ ์์ ์๋ก */
+ padding: 0.8rem 2rem;
+}
+
+.logo h1 {
+ font-size: 1.8rem;
+ font-weight: bolder;
+ margin: 0;
+}
+.logoReci {
+ color: black; /* Reci์ ๊ธฐ๋ณธ ์์ */
+}
+
+.logoPick {
+ color: #3FA2F6; /* Pick์ ์ด๋ก์ */
+ font-weight: 900; /* ๊ฐ์กฐ */
+}
+
+.nav {
+ display: flex;
+ gap: 1.5rem;
+}
+
+.nav a {
+ color: black;
+ text-decoration: none;
+ font-size: 1rem;
+ transition: color 0.3s ease;
+}
+
+.nav a:hover {
+ color: rgb(61, 60, 60);
+}
diff --git a/src/assets/css/recipe/recipe.css b/src/assets/css/recipe/recipe.css
new file mode 100644
index 0000000..8a3bd0a
--- /dev/null
+++ b/src/assets/css/recipe/recipe.css
@@ -0,0 +1,4 @@
+.recipe-wrapper {
+ display: flex;
+ gap: 20px;
+}
\ No newline at end of file
diff --git a/src/assets/css/user/Join.css b/src/assets/css/user/Join.css
new file mode 100644
index 0000000..be56060
--- /dev/null
+++ b/src/assets/css/user/Join.css
@@ -0,0 +1,71 @@
+.join-container {
+ width: 460px;
+ margin: 0 auto;
+}
+
+.join-container h2 {
+ font-size: 30px;
+ font-weight: bold;
+ text-align: center;
+ margin: 0 0 16px 0;
+}
+
+.join-container p {
+ text-align: center;
+ color: #666;
+ margin: 0 0 32px 0;
+ font-size: 16px;
+}
+
+.form-label {
+ display: block;
+ font-size: 15px;
+ margin: 0 0 8px 0;
+}
+
+.form-control {
+ width: 100%;
+ height: 48px;
+ padding: 0 16px;
+ border: 1px solid #DDE2E5;
+ border-radius: 5px;
+ background: #F8F9FA;
+ font-size: 14px;
+ box-sizing: border-box;
+ margin-bottom: 8px;
+}
+
+.form-control:focus {
+ border-color: #3FA2F6;
+ background: #fff;
+ outline: none;
+}
+
+.invalid-feedback {
+ display: block;
+ color: #FF3B3B;
+ font-size: 14px;
+ margin: 0 0 16px 0;
+}
+
+.btn-primary {
+ width: 100%;
+ height: 48px;
+ background: #3FA2F6;
+ border: none;
+ border-radius: 5px;
+ color: white;
+ font-size: 16px;
+ cursor: pointer;
+ box-sizing: border-box;
+ margin-top: 5px;
+}
+
+.btn-primary:hover {
+ background: #3691E0;
+}
+
+.btn-primary:disabled {
+ background: #A5D3FB;
+ cursor: not-allowed;
+}
\ No newline at end of file
diff --git a/src/assets/image/noimage.jpg b/src/assets/image/noimage.jpg
new file mode 100644
index 0000000..110d770
Binary files /dev/null and b/src/assets/image/noimage.jpg differ
diff --git a/src/assets/image/recipeimage.png b/src/assets/image/recipeimage.png
new file mode 100644
index 0000000..55c5d9d
Binary files /dev/null and b/src/assets/image/recipeimage.png differ
diff --git a/src/components/ingredient/userIngredient/UsersIngredient.jsx b/src/components/ingredient/userIngredient/UsersIngredient.jsx
new file mode 100644
index 0000000..8de30f9
--- /dev/null
+++ b/src/components/ingredient/userIngredient/UsersIngredient.jsx
@@ -0,0 +1,86 @@
+import { useEffect, useState } from "react";
+import { getUsersIngredient } from "../../../sources/api/IngredientAPI";
+import UsersIngredientItem from "./UsersIngredientItem";
+import style from '../../../assets/css/Ingredient/userIngredient/UsersIngredient.module.css';
+
+function UserIngredient() {
+ const [usersIngredientList, setUserIngredientList] = useState([]); // ์ ์ฒด ์ฌ๋ฃ ๋ชฉ๋ก
+ const [filteredIngredients, setFilteredIngredients] = useState([]); // ํํฐ๋ง๋ ์ฌ๋ฃ ๋ชฉ๋ก
+ const [category, setCategory] = useState("์ ์ฒด"); // ์ ํ๋ ์นดํ
๊ณ ๋ฆฌ
+ const [showFavoritesOnly, setShowFavoritesOnly] = useState(false); // ์ฆ๊ฒจ์ฐพ๊ธฐ ํํฐ ์ํ
+
+ const categories = [
+ "์ ์ฒด", "์ฑ์", "๊ณผ์ผ", "์ก๋ฅ", "ํด์ฐ๋ฌผ", "์ ์ ํ",
+ "๊ณ๋", "์ฅ๋ฅ", "์์ค", "์๋ฃ", "๊ณก๋ฌผ", "๊ฒฌ๊ณผ๋ฅ",
+ "๋ฐ์ฐฌ", "์์ ํ"
+ ];
+
+ useEffect(() => {
+ const fetchIngredients = async () => {
+ try {
+ const data = await getUsersIngredient(); // ์๋ฒ ๋ฐ์ดํฐ ํธ์ถ
+ setUserIngredientList(data); // ์ ์ฒด ๋ฐ์ดํฐ ์ค์
+ setFilteredIngredients(data); // ์ด๊ธฐ ํํฐ๋ง ๋ฐ์ดํฐ ์ค์
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ fetchIngredients(); // ๋น๋๊ธฐ ์์
ํธ์ถ
+ }, []);
+
+ useEffect(() => {
+ let filtered = usersIngredientList;
+
+ // ์นดํ
๊ณ ๋ฆฌ ํํฐ๋ง
+ if (category !== "์ ์ฒด") {
+ filtered = filtered.filter(
+ (ingredient) => ingredient.ingredientCategory === category
+ );
+ }
+
+ // ์ฆ๊ฒจ์ฐพ๊ธฐ ํํฐ๋ง
+ if (showFavoritesOnly) {
+ filtered = filtered.filter((ingredient) => ingredient.bookmarked === true);
+ }
+
+ setFilteredIngredients(filtered);
+ }, [category, showFavoritesOnly, usersIngredientList]);
+
+ return (
+
+
+
๋ด ๋์ฅ๊ณ
+
+
+
+
+
+
+ {filteredIngredients.map((ingredient) => (
+
+ ))}
+
+
+ );
+}
+
+export default UserIngredient;
diff --git a/src/components/ingredient/userIngredient/UsersIngredientItem.jsx b/src/components/ingredient/userIngredient/UsersIngredientItem.jsx
new file mode 100644
index 0000000..6a6e8bb
--- /dev/null
+++ b/src/components/ingredient/userIngredient/UsersIngredientItem.jsx
@@ -0,0 +1,62 @@
+import Button from 'react-bootstrap/Button';
+import Card from 'react-bootstrap/Card';
+import style from '../../../assets/css/Ingredient/userIngredient/UsersIngredientItem.module.css';
+import React, { useState } from 'react';
+import { updateIngredientBookmark } from '../../../sources/api/IngredientAPI';
+
+function UsersIngredientItem({ userIngredient }) {
+ const [isBookmarked, setIsBookmarked] = useState(userIngredient.bookmarked || false);
+
+ const toggleBookmark = () => {
+ const newBookmarkState = !isBookmarked;
+ setIsBookmarked(newBookmarkState); // ์ฆ๊ฒจ์ฐพ๊ธฐ ์ํ ์
๋ฐ์ดํธ
+
+
+ // ์๋ฒ๋ก ์ํ ์
๋ฐ์ดํธ (ํ์์)
+ updateIngredientBookmark(1, isBookmarked, userIngredient)
+ userIngredient.bookmarked = !userIngredient.bookmarked
+ };
+
+ console.log(userIngredient);
+ return (
+
+
+

+
+
+
+ ์๋: {userIngredient.ingredientAmount || 0}
+
+
+
+
+
+ {userIngredient.ingredientName || '์ฌ๋ฃ๋ช
'}
+
+
+ ์ ํต๊ธฐํ: {userIngredient.expirationDate || '์ ์ ์์'}
+ ๋จ์ ์ผ์: {userIngredient.remainExpirationDate || 0}
+ ๋ณด๊ด ๋ฐฉ๋ฒ: {userIngredient.ingredientStorage || '๋ณด๊ด ๋ฐฉ๋ฒ ๋ฏธ์ '}
+
+
+
+
+ );
+}
+
+export default UsersIngredientItem;
diff --git a/src/components/main/Footer.jsx b/src/components/main/Footer.jsx
new file mode 100644
index 0000000..a556024
--- /dev/null
+++ b/src/components/main/Footer.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+import style from '../../assets/css/main/Footer.module.css';
+
+function Footer() {
+ return (
+
+ );
+}
+
+export default Footer;
diff --git a/src/components/main/Header.jsx b/src/components/main/Header.jsx
new file mode 100644
index 0000000..3099bd4
--- /dev/null
+++ b/src/components/main/Header.jsx
@@ -0,0 +1,22 @@
+import React from "react";
+import style from '../../assets/css/main/header.module.css';
+
+function Header() {
+ return (
+
+ );
+}
+
+export default Header;
diff --git a/src/components/recipe/recipeItem.jsx b/src/components/recipe/recipeItem.jsx
new file mode 100644
index 0000000..fd68ffc
--- /dev/null
+++ b/src/components/recipe/recipeItem.jsx
@@ -0,0 +1,17 @@
+import defaultRecipeImg from "../../assets/image/recipeimage.png"
+
+const RecipeItem = ({ recipe }) => {
+ const imgSrc = recipe.recipeSource.length!==0?recipe.recipeSource[0].recipeSourceSave:defaultRecipeImg
+ return (
+
+

+
์ด๋ฆ:{recipe.recipeName}
+
๋ด์ฉ:{recipe.recipeContent}
+
์กฐ๋ฆฌ์๊ฐ:{recipe.recipeCookingTime}(๋จ์)
+
๋์ด๋:{recipe.recipeDifficulty}
+
์กฐํ์:{recipe.recipeViews}
+
+ );
+};
+
+export default RecipeItem
\ No newline at end of file
diff --git a/src/components/user/JoinForm.jsx b/src/components/user/JoinForm.jsx
new file mode 100644
index 0000000..6902263
--- /dev/null
+++ b/src/components/user/JoinForm.jsx
@@ -0,0 +1,209 @@
+import { useState } from 'react'
+import { Form, Button } from 'react-bootstrap'
+import { userApi } from '../../sources/api/UserAPI'
+import { useNavigate } from 'react-router-dom'
+import '../../assets/css/user/Join.css'
+
+const JoinForm = () => {
+ const navigate = useNavigate()
+ const [formData, setFormData] = useState({
+ userId: '',
+ userPw: '',
+ userPwConfirm: '',
+ userEmail: '',
+ userNickname: ''
+ })
+
+ const [errors, setErrors] = useState({})
+ const [isSubmitting, setIsSubmitting] = useState(false)
+
+ const handleChange = (e) => {
+ const { name, value } = e.target
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }))
+ // ์
๋ ฅ ์ ํด๋น ํ๋์ ์๋ฌ ๋ฉ์์ง ์ ๊ฑฐ
+ if (errors[name]) {
+ setErrors(prev => ({
+ ...prev,
+ [name]: ''
+ }))
+ }
+ }
+
+ const validateForm = () => {
+ const newErrors = {}
+
+ // ์์ด๋ ๊ฒ์ฆ
+ if (!formData.userId) {
+ newErrors.userId = '์์ด๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.'
+ } else if (formData.userId.length < 4) {
+ newErrors.userId = '์์ด๋๋ 4์ ์ด์์ด์ด์ผ ํฉ๋๋ค.'
+ }
+
+ // ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
+ if (!formData.userPw) {
+ newErrors.userPw = '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.'
+ } else if (formData.userPw.length < 8) {
+ newErrors.userPw = '๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค.'
+ }
+
+ // ๋น๋ฐ๋ฒํธ ํ์ธ
+ if (formData.userPw !== formData.userPwConfirm) {
+ newErrors.userPwConfirm = '๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.'
+ }
+
+ // ์ด๋ฉ์ผ ๊ฒ์ฆ
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ if (!formData.userEmail) {
+ newErrors.userEmail = '์ด๋ฉ์ผ์ ์
๋ ฅํด์ฃผ์ธ์.'
+ } else if (!emailRegex.test(formData.userEmail)) {
+ newErrors.userEmail = '์ฌ๋ฐ๋ฅธ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.'
+ }
+
+ // ๋๋ค์ ๊ฒ์ฆ
+ if (!formData.userNickname) {
+ newErrors.userNickname = '๋๋ค์์ ์
๋ ฅํด์ฃผ์ธ์.'
+ } else if (formData.userNickname.length < 2) {
+ newErrors.userNickname = '๋๋ค์์ 2์ ์ด์์ด์ด์ผ ํฉ๋๋ค.'
+ }
+
+ setErrors(newErrors)
+ return Object.keys(newErrors).length === 0 // ๋ชจ๋ ์
๋ ฅ์ด ์ ํจํ ๊ฒฝ์ฐ ํผ ์ ์ถ ์งํํ
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault()
+ setIsSubmitting(true)
+
+ try {
+ const isValid = validateForm()
+ if (!isValid) {
+ setIsSubmitting(false)
+ return
+ }
+
+ const { userPwConfirm, ...submitData } = formData
+ await userApi.signup(submitData)
+ alert('ํ์๊ฐ์
์ด ์๋ฃ๋์์ต๋๋ค!')
+ navigate('/login')
+ } catch (error) {
+ console.log('Error response:', error.response?.data)
+ if (error.response?.status === 409) {
+ // ์๋ฒ์์ ๋ฐ์ ๋ฌธ์์ด์ ๋ฐฐ์ด๋ก ๋ถ๋ฆฌ
+ const errorMessages = error.response.data.split(', ')
+
+ // ๊ฐ ์๋ฌ ๋ฉ์์ง๋ฅผ ํด๋นํ๋ ํ๋์ ๋งคํ
+ const fieldErrors = {}
+ errorMessages.forEach(message => {
+ if (message.includes('์์ด๋')) fieldErrors.userId = message
+ if (message.includes('์ด๋ฉ์ผ')) fieldErrors.userEmail = message
+ if (message.includes('๋๋ค์')) fieldErrors.userNickname = message
+ })
+
+ setErrors(prev => ({
+ ...prev,
+ ...fieldErrors
+ }))
+ } else {
+ alert('ํ์๊ฐ์
์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.')
+ }
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+
+ ์์ด๋
+
+
+ {errors.userId}
+
+
+
+
+ ๋น๋ฐ๋ฒํธ
+
+
+ {errors.userPw}
+
+
+
+
+ ๋น๋ฐ๋ฒํธ ํ์ธ
+
+
+ {errors.userPwConfirm}
+
+
+
+
+ ์ด๋ฉ์ผ
+
+
+ {errors.userEmail}
+
+
+
+
+ ๋๋ค์
+
+
+ {errors.userNickname}
+
+
+
+
+
+ )
+}
+
+export default JoinForm
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
index 19ad620..b67a5e1 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,8 +1,11 @@
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
+import './assets/css/main.module.css'
+import style from './assets/css/main.module.css'
+
createRoot(document.getElementById('root')).render(
- <>
+
)
diff --git a/src/pages/ingredient/Ingregdient.jsx b/src/pages/ingredient/Ingregdient.jsx
new file mode 100644
index 0000000..3d4e7a1
--- /dev/null
+++ b/src/pages/ingredient/Ingregdient.jsx
@@ -0,0 +1,17 @@
+import UsersIngredient from "../../components/ingredient/userIngredient/UsersIngredient"
+import style from '../../assets/css/ingredient/ingredient/Ingredient.module.css'
+
+
+function Ingredient() {
+ console.log(style);
+ return (
+ <>
+ ์ฌ๋ฃ ํ์ด์ง
+
+
+
+ >
+ )
+}
+
+export default Ingredient
\ No newline at end of file
diff --git a/src/pages/layouts/Layout.jsx b/src/pages/layouts/Layout.jsx
new file mode 100644
index 0000000..5f3618d
--- /dev/null
+++ b/src/pages/layouts/Layout.jsx
@@ -0,0 +1,15 @@
+import {Outlet} from 'react-router-dom'
+import Header from '../../components/main/Header'
+import Footer from '../../components/main/Footer'
+
+function Layout() {
+ return(
+
+
+
+
+
+ )
+}
+
+export default Layout
\ No newline at end of file
diff --git a/src/pages/recipe/Recipe.jsx b/src/pages/recipe/Recipe.jsx
new file mode 100644
index 0000000..be1ac53
--- /dev/null
+++ b/src/pages/recipe/Recipe.jsx
@@ -0,0 +1,45 @@
+import { useEffect, useState,useCallback } from "react"
+import { getRecipeList } from "../../sources/api/recipeAPI.jsx";
+import {Route, useNavigate} from "react-router-dom";
+import RecipeItem from "../../components/recipe/RecipeItem.jsx"
+import "../../assets/css/recipe/recipe.css"
+
+function recipe() {
+ const [recipeList, setRecipeList] = useState([])
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const fetchRecipes = async () => {
+ try {
+ const data = await getRecipeList();
+ console.log("recipeList",data); // ๊ฐ์ ธ์จ ๋ฐ์ดํฐ ํ์ธ
+ setRecipeList(data); // ์ํ ์
๋ฐ์ดํธ
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ fetchRecipes(); // ๋น๋๊ธฐ ์์
ํธ์ถ
+ }, []);
+
+ const handleAdd = useCallback(() => {
+ navigate("/recipe/create");
+ },[navigate])
+
+ return (
+ <>
+ ๋ ์ํผ ํ์ด์ง
+
+ {recipeList.map((recipe) => (
+
+ ))}
+
+
+ >
+ )
+}
+export default recipe
\ No newline at end of file
diff --git a/src/pages/recipe/RecipeDetail.jsx b/src/pages/recipe/RecipeDetail.jsx
new file mode 100644
index 0000000..56646b1
--- /dev/null
+++ b/src/pages/recipe/RecipeDetail.jsx
@@ -0,0 +1,59 @@
+import { useParams } from 'react-router-dom'
+import { useEffect, useState,useCallback } from "react"
+import { getRecipeDetail } from "../../sources/api/recipeAPI.jsx";
+import defaultRecipeImg from "../../assets/image/recipeimage.png"
+
+
+function RecipeDetail() {
+ const { recipePk } = useParams()
+ const [recipeDetail, setRecipeDetail] = useState({})
+ useEffect(() => {
+ const fetchRecipes = async () => {
+ try {
+ const data = await getRecipeDetail(recipePk);
+ console.log("recipeDetail",data);
+ setRecipeDetail(data);
+ } catch (err) {
+ console.error(err);
+ }
+ };
+ fetchRecipes();
+ }, [recipePk]);
+
+ const recipeImage =
+ recipeDetail.recipeSource && recipeDetail.recipeSource.length > 0
+ ? recipeDetail.recipeSource[0]
+ : defaultRecipeImg;
+
+ return (
+ <>
+ ๋ ์ํผ ์์ธ์กฐํ ํ์ด์ง
+
+

+
์ด๋ฆ:{recipeDetail.recipeName}
+
๋ด์ฉ:{recipeDetail.recipeContent}
+
์กฐ๋ฆฌ์๊ฐ:{recipeDetail.recipeCookingTime}(๋จ์)
+
๋์ด๋:{recipeDetail.recipeDifficulty}
+
์กฐํ์:{recipeDetail.recipeViews}
+
์ฌ๋ฃ
+ {recipeDetail.ingredients && recipeDetail.ingredients.map(ingredient => (
{ingredient.ingredientName}
))}
+
์๋ฆฌ๊ณผ์
+ {recipeDetail.recipeStep &&
+ recipeDetail.recipeStep.map((step, index) => (
+
+
Step {index + 1}: {step.recipeStepContent}
+ {step.recipeStepSource && (
+

+ )}
+
+ ))}
+
+
+ >
+ )
+}
+
+export default RecipeDetail
\ No newline at end of file
diff --git a/src/pages/recipe/addRecipe.jsx b/src/pages/recipe/addRecipe.jsx
new file mode 100644
index 0000000..d0988ba
--- /dev/null
+++ b/src/pages/recipe/addRecipe.jsx
@@ -0,0 +1,318 @@
+
+import { useState } from 'react';
+import { createRecipe } from "../../sources/api/recipeAPI.jsx";
+
+const initialState = {
+ recipeName: '',
+ recipeCookingTime: 0,
+ recipeDifficulty: 0,
+ recipeContent: '',
+ recipeSteps: [],
+ recipeCategoryPk: 0,
+ userPk: 0,
+};
+
+export const AddRecipe = () => {
+
+ const [request, setRequest] = useState({ ...initialState });
+
+ const [recipeSources, setRecipeSources] = useState([]);
+
+ const [recipeStepSources, setRecipeStepSources] = useState([]);
+
+ const [result, setResult] = useState(null);
+
+ const defaultUrl = 'https://picsum.photos/200/300';
+ const handleClickAdd = () => {
+
+ const formData = new FormData();
+
+ // request ๊ธฐ๋ณธ ๊ธ์ ๊ทธ๋ฅ ์ถ๊ฐ
+ formData.append("request",new Blob([JSON.stringify(request)],{type:"application/json"}));
+
+ // recipeSources ํ์ผ ๋ฐฐ์ด ์ถ๊ฐ
+ recipeSources.forEach(file => {
+ formData.append(`recipeSources`, file); // ๊ฐ์ ์ด๋ฆ์ผ๋ก ์๋ฒ์ ์ ๋ฌ
+ });
+
+ // recipeStepSources ์ถ๊ฐ
+ recipeStepSources.forEach((files, stepIndex) => {
+ if(files) {
+ files.forEach(file => {
+ formData.append(`recipeStepSources`, file);
+ })
+ }
+ });
+
+ createRecipe(formData)
+ .then(res => {
+ console.log(res);
+ setResult(res);
+
+ // ์ฌ์ฉ ํ ์ด๊ธฐํ
+ setRequest({ ...initialState });
+ setRecipeSources([]);
+ setRecipeStepSources([]);
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ };
+
+ // ํ์ผ ์
๋ก๋ ์ฒ๋ฆฌ
+ const handleRecipeSourcesChange = (e) => {
+ setRecipeSources(Array.from(e.target.files)); // ์ฌ๋ฌ ํ์ผ์ ๋ฐฐ์ด๋ก ์ถ๊ฐ
+ console.log(Array.from(e.target.files))
+ };
+
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setRequest(prevState => ({
+ ...prevState,
+ [name]: value,
+ }));
+ };
+
+ const addStep = (index) => {
+ setRequest((prevState) => {
+ const updatedSteps = [...prevState.recipeSteps];
+ const newStep = {
+ recipeStepOrder: index + 2,
+ recipeStepContent: '',
+ };
+
+ updatedSteps.splice(index + 1, 0, newStep); // ํด๋ฆญ๋ ์คํ
์๋ ์ฝ์
+
+ // ์ ์ฒด ์์ ์
๋ฐ์ดํธ
+ const updatedOrderedSteps = updatedSteps.map((step, idx) => ({
+ ...step,
+ recipeStepOrder: idx + 1, // ์์ ์ฌ์ ๋ ฌ
+ }));
+
+ return {
+ ...prevState,
+ recipeSteps: updatedOrderedSteps,
+ };
+ });
+
+ // recipeStepSources์๋ ๋น ๊ฐ ์ถ๊ฐ
+ setRecipeStepSources((prevSources) => {
+ const updatedSources = [...prevSources];
+ updatedSources.splice(index + 1, 0, null); // ํด๋ฆญ๋ ์คํ
์๋์ ๋น ๊ฐ ์ฝ์
+ return updatedSources;
+ });
+
+ };
+
+ const removeStep = (index) => {
+ setRequest((prevState) => {
+ const updatedSteps = [...prevState.recipeSteps];
+ updatedSteps.splice(index, 1); // ํด๋น ์ธ๋ฑ์ค ์คํ
์ญ์
+
+ // ์์ ์ฌ์ ๋ ฌ
+ const updatedOrderedSteps = updatedSteps.map((step, idx) => ({
+ ...step,
+ recipeStepOrder: idx + 1,
+ }));
+
+ return {
+ ...prevState,
+ recipeSteps: updatedOrderedSteps,
+ };
+ });
+
+ // recipeStepSources์์๋ ํด๋น ์ธ๋ฑ์ค์ ์ด๋ฏธ์ง ์ญ์
+ setRecipeStepSources((prevSources) => {
+ const updatedSources = [...prevSources];
+ updatedSources.splice(index, 1); // ํด๋น ์ธ๋ฑ์ค ์ญ์
+ return updatedSources;
+ });
+ };
+
+ const handleStepChange = (e, index) => {
+ const { name, value } = e.target;
+ setRequest(prevState => {
+ const updatedSteps = [...prevState.recipeSteps];
+ updatedSteps[index] = { ...updatedSteps[index], [name]: value };
+ return {
+ ...prevState,
+ recipeSteps: updatedSteps,
+ };
+ });
+ };
+
+ const handleRecipeStepSourcesChange = (e, index) => {
+ const files = Array.from(e.target.files||[]); // ์
๋ก๋๋ ํ์ผ ๋ฆฌ์คํธ
+
+ setRecipeStepSources((prevSources) => {
+ const updatedSources = [...prevSources];
+ updatedSources[index] = files.length > 0 ? files : null; // ํ์ผ์ด ์์ผ๋ฉด ์
๋ฐ์ดํธ, ์์ผ๋ฉด null
+ return updatedSources;
+ });
+ };
+
+ const printRecipeStepSources = () => {
+ console.log(recipeStepSources);
+ }
+ return (
+
+
+
๋ ์ํผ ์ฌ์ง :
+
+
+
+
+
+
+
๋ ์ํผ ์ด๋ฆ :
+
+
+
+
+
+
+
+
+
๋ ์ํผ ๊ฐ๋จํ ๋ด์ฉ :
+
+
+
+
+
+
+
+ {/* ๋จ๊ณ๋ณ ์
๋ ฅ */}
+
+
+ {request.recipeSteps.map((step, index) => (
+
+
์คํ
{index + 1} :
+ handleStepChange(e, index)}
+ placeholder={`์คํ
${index + 1}`}
+ />
+
+ {/* ์จ๊ฒจ์ง ํ์ผ ์ ํ ์
๋ ฅ */}
+
handleRecipeStepSourcesChange(e, index)}
+ style={{display: 'none'}} // ์จ๊ธฐ๊ธฐ
+ />
+
+
+
+
+ {/* ๋ผ๋ฒจ์ ํด๋ฆญํ๋ฉด ํ์ผ ์ฐฝ ์ด๊ธฐ */}
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/pages/user/Join.jsx b/src/pages/user/Join.jsx
new file mode 100644
index 0000000..813d53b
--- /dev/null
+++ b/src/pages/user/Join.jsx
@@ -0,0 +1,15 @@
+import JoinForm from '../../components/user/JoinForm'
+import { Container } from "react-bootstrap"
+import '../../assets/css/user/Join.css'
+
+const Join = () => {
+ return (
+
+ ํ์๊ฐ์
+ ReciPick์ ํ์์ด ๋์ด ๋๋ง์ ์ํ ๋ง์ถค ๋ ์ํผ๋ฅผ ๋ง๋๋ณด์ธ์.
+
+
+ )
+}
+
+export default Join
\ No newline at end of file
diff --git a/src/sources/api/IngredientAPI.js b/src/sources/api/IngredientAPI.js
new file mode 100644
index 0000000..4fb5435
--- /dev/null
+++ b/src/sources/api/IngredientAPI.js
@@ -0,0 +1,55 @@
+import axios from "axios";
+
+const BASE_URL = 'http://localhost:8080/ingredient'
+
+export async function getUsersIngredient() {
+ try {
+ const response = await axios({
+ url: `${BASE_URL}`,
+ method: 'get',
+ params: {
+ userPk: 1,
+ },
+ });
+ return response.data; // ๋ฐ์ดํฐ๋ฅผ ๋ฐํ
+ } catch (err) {
+ console.error(err);
+ throw err; // ํธ์ถ์์๊ฒ ์๋ฌ ์ ๋ฌ
+ }
+}
+
+export function updateIngredientBookmark(userPk, isBookmarked, ingredient) {
+ if (!isBookmarked) {
+ axios({
+ url: `${BASE_URL}/bookmark/regist`,
+ method: 'post',
+ data: {
+ userPk: userPk,
+ ingredientMyRefrigeratorPk: ingredient.ingredientMyRefrigeratorPk
+ }
+ })
+ .then((res) => {
+ console.log(res);
+ console.log("์ถ๊ฐ๋จ");
+ })
+ .catch((err) => {
+ console.log(err);
+ })
+ } else {
+ axios({
+ url: `${BASE_URL}/bookmark/delete`,
+ method: 'delete',
+ data: {
+ userPk: userPk,
+ ingredientBookmarkPk: ingredient.ingredientBookmarkPk
+ }
+ })
+ .then((res) => {
+ console.log(res);
+ console.log("์ญ์ ๋จ");
+ })
+ .catch((err) => {
+ console.log(err);
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/sources/api/UserAPI.js b/src/sources/api/UserAPI.js
new file mode 100644
index 0000000..9cdf908
--- /dev/null
+++ b/src/sources/api/UserAPI.js
@@ -0,0 +1,15 @@
+import axios from 'axios'
+
+const BASE_URL = 'http://localhost:8080'
+
+export const userApi = {
+ // ํ์๊ฐ์
+ signup: async (userData) => {
+ try {
+ const response = await axios.post(`${BASE_URL}/auth/join`, userData)
+ return response.data
+ } catch (error) {
+ throw error
+ }
+ },
+}
\ No newline at end of file
diff --git a/src/sources/api/recipeAPI.jsx b/src/sources/api/recipeAPI.jsx
new file mode 100644
index 0000000..2d68640
--- /dev/null
+++ b/src/sources/api/recipeAPI.jsx
@@ -0,0 +1,24 @@
+import axios from "axios";
+
+export const API_URL_HOST = "http://localhost:8080";
+
+const prefix = `${API_URL_HOST}/recipe`;
+
+export const getRecipeDetail = async (recipePk) => {
+ const url = `${prefix}/${recipePk}`
+ const res = await axios.get(url)
+ return res.data
+}
+
+export const getRecipeList = async () => {
+ const url = `${prefix}`;
+ const res = await axios.get(url);
+ return res.data;
+}
+
+export const createRecipe = async (recipe) => {
+ console.log(recipe);
+ const url = `${prefix}`;
+ const res = await axios.post(url, recipe);
+ return res.data;
+}
\ No newline at end of file