diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml
new file mode 100644
index 0000000..0c91180
--- /dev/null
+++ b/.github/workflows/frontend-ci.yml
@@ -0,0 +1,38 @@
+name: Frontend CI (Vite Build)
+
+on:
+ push:
+ branches: [ "main" ] # 귣ġ ̸ master ʿ
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ build-check:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [20.x] # Vite 6.x ֽ Node (18, 20+)
+
+ steps:
+ # 1. ڵ
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ # 2. Node.js
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ # 3. ġ
+ # package-lock.json ִٸ npm ci մϴ.
+ # lock ٸ npm install ؼ ϼ.
+ - name: Install dependencies
+ run: npm ci
+
+ # 4. Ȯ (ٽ ܰ)
+ # Vite 尡 ϸ ⼭ , PR ɴϴ.
+ - name: Build Project
+ run: npm run build
\ No newline at end of file
diff --git a/.screen-graph.json b/.screen-graph.json
new file mode 100644
index 0000000..24122fa
--- /dev/null
+++ b/.screen-graph.json
@@ -0,0 +1,1267 @@
+{
+ "nodes": [
+ {
+ "id": "src/screens/Desktop/Desktop.jsx",
+ "label": "Desktop",
+ "routes": [
+ "/*",
+ "/desktop"
+ ],
+ "dataModelId": "45:2",
+ "isRoot": true
+ },
+ {
+ "id": "src/screens/CommunityPage/CommunityPage.jsx",
+ "label": "CommunityPage",
+ "routes": [
+ "/communitypage"
+ ],
+ "dataModelId": "1:6"
+ },
+ {
+ "id": "src/screens/MyPage/MyPage.jsx",
+ "label": "MyPage",
+ "routes": [
+ "/mypage"
+ ]
+ },
+ {
+ "id": "src/screens/RecipePage/RecipePage.jsx",
+ "label": "RecipePage",
+ "routes": [
+ "/recipepage/:id"
+ ]
+ },
+ {
+ "id": "src/screens/CommunityContent/CommunityContent.jsx",
+ "label": "CommunityContent",
+ "routes": [
+ "/communitycontentpage/:id"
+ ],
+ "dataModelId": "107:399"
+ },
+ {
+ "id": "src/screens/AddIngredientPage/AddIngredientPage.jsx",
+ "label": "AddIngredientPage",
+ "routes": [
+ "/addingredient"
+ ]
+ },
+ {
+ "id": "src/screens/CreatePostPage/CreatePostPage.jsx",
+ "label": "CreatePostPage",
+ "routes": [
+ "/createpost"
+ ]
+ },
+ {
+ "id": "src/screens/MenuRecommendationPage/MenuRecommendationPage.jsx",
+ "label": "MenuRecommendationPage",
+ "routes": [
+ "/menurecommendation"
+ ]
+ }
+ ],
+ "edges": [
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\Desktop\\sections\\IngredientInventory\\IngredientInventory.jsx:134:8-to-src\\screens\\AddIngredientPage\\AddIngredientPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx",
+ "data": {
+ "viaRoute": "/addingredient",
+ "trigger": {
+ "element": "\r\n \r\n
\r\n\r\n
\r\n

\r\n
\r\n
\r\n ",
+ "line": 134,
+ "endLine": 148,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\screens\\Desktop\\sections\\IngredientInventory\\IngredientInventory.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\Desktop\\sections\\MenuInventory\\MenuInventory.jsx:57:8-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": "\r\n \r\n ",
+ "line": 57,
+ "endLine": 59,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\screens\\Desktop\\sections\\MenuInventory\\MenuInventory.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\Desktop\\sections\\MenuInventory\\MenuInventory.jsx:60:8-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": "\r\n 추천받기 →\r\n ",
+ "line": 60,
+ "endLine": 62,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\screens\\Desktop\\sections\\MenuInventory\\MenuInventory.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\Desktop\\sections\\Community\\Community.jsx:18:6-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": "\r\n 재료 나눔 게시판
\r\n ",
+ "line": 18,
+ "endLine": 20,
+ "column": 6,
+ "endColumn": 13,
+ "sourceFile": "src\\screens\\Desktop\\sections\\Community\\Community.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\CommunityPage\\sections\\Communitypage\\Communitypage.jsx:54:4-to-src\\screens\\CreatePostPage\\CreatePostPage.jsx",
+ "source": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "target": "src\\screens\\CreatePostPage\\CreatePostPage.jsx",
+ "data": {
+ "viaRoute": "/createpost",
+ "trigger": {
+ "element": "navigate(\"/createpost\")",
+ "line": 54,
+ "endLine": 54,
+ "column": 4,
+ "endColumn": 27,
+ "sourceFile": "src\\screens\\CommunityPage\\sections\\Communitypage\\Communitypage.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx:170:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 170,
+ "endLine": 170,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx:271:12-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n 취소\r\n ",
+ "line": 271,
+ "endLine": 273,
+ "column": 12,
+ "endColumn": 19,
+ "sourceFile": "src\\screens\\AddIngredientPage\\AddIngredientPage.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\CreatePostPage\\CreatePostPage.jsx:16:6-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\CreatePostPage\\CreatePostPage.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": "navigate(\"/communitypage\")",
+ "line": 16,
+ "endLine": 16,
+ "column": 6,
+ "endColumn": 32,
+ "sourceFile": "src\\screens\\CreatePostPage\\CreatePostPage.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\CreatePostPage\\CreatePostPage.jsx:49:4-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\CreatePostPage\\CreatePostPage.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": "navigate(\"/communitypage\")",
+ "line": 49,
+ "endLine": 49,
+ "column": 4,
+ "endColumn": 30,
+ "sourceFile": "src\\screens\\CreatePostPage\\CreatePostPage.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\screens\\CreatePostPage\\CreatePostPage.jsx:88:12-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\CreatePostPage\\CreatePostPage.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": "\r\n 취소\r\n ",
+ "line": 88,
+ "endLine": 90,
+ "column": 12,
+ "endColumn": 19,
+ "sourceFile": "src\\screens\\CreatePostPage\\CreatePostPage.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:74:14-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 홈\r\n ",
+ "line": 74,
+ "endLine": 80,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:81:14-to-src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MenuRecommendationPage\\MenuRecommendationPage.jsx",
+ "data": {
+ "viaRoute": "/menurecommendation",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 메뉴 추천\r\n ",
+ "line": 81,
+ "endLine": 87,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:88:14-to-src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\CommunityPage\\CommunityPage.jsx",
+ "data": {
+ "viaRoute": "/communitypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 재료 나눔 게시판\r\n ",
+ "line": 88,
+ "endLine": 94,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:95:14-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": " setShowMenu(false)}\r\n >\r\n 마이페이지\r\n ",
+ "line": 95,
+ "endLine": 101,
+ "column": 14,
+ "endColumn": 21,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:104:10-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n
\r\n ",
+ "line": 104,
+ "endLine": 110,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:113:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "\r\n \r\n 알고리즘 \r\n 셰프\r\n
\r\n ",
+ "line": 113,
+ "endLine": 118,
+ "column": 8,
+ "endColumn": 15,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx:121:10-to-src\\screens\\MyPage\\MyPage.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\MyPage\\MyPage.jsx",
+ "data": {
+ "viaRoute": "/mypage",
+ "trigger": {
+ "element": "\r\n 마이페이지
\r\n ",
+ "line": 121,
+ "endLine": 123,
+ "column": 10,
+ "endColumn": 17,
+ "sourceFile": "src\\components\\UnifiedHeader\\UnifiedHeader.jsx"
+ }
+ }
+ },
+ {
+ "id": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx:87:8-to-src\\screens\\Desktop\\Desktop.jsx",
+ "source": "src\\screens\\Desktop\\Desktop.jsx",
+ "target": "src\\screens\\Desktop\\Desktop.jsx",
+ "data": {
+ "viaRoute": "/desktop",
+ "trigger": {
+ "element": "navigate(\"/desktop\")",
+ "line": 87,
+ "endLine": 87,
+ "column": 8,
+ "endColumn": 28,
+ "sourceFile": "src\\components\\PreferencesPopup\\PreferencesPopup.jsx"
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index f665eb6..6486779 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,124 @@
-AlgorithmChef_Frontend
+# 알고리즘 셰프 - Algorithm Chef
+
+환영합니다! 이 프로젝트는 Anima에 의해 자동으로 생성된 React 애플리케이션입니다.
+"알고리즘 셰프"는 사용자의 냉장고 속 식재료와 개인 성향을 기반으로 맞춤형 레시피를 추천하고, 식재료 나눔 커뮤니티를 제공하는 서비스입니다.
+
+## 시작하기
+
+> **전제 조건:**
+> 다음 단계를 수행하려면 시스템에 [NodeJS](https://nodejs.org/en/)가 설치되어 있어야 합니다.
+
+프로젝트를 시작하려면 먼저 다음 명령어로 의존성을 설치해야 합니다:
+
+```bash
+npm install
+```
+
+그 다음, 다음 명령어로 개발 버전을 실행할 수 있습니다:
+
+```bash
+npm run dev
+```
+
+몇 초 후, 프로젝트는 [http://localhost:5173/](http://localhost:5173/) 주소에서 접근할 수 있습니다.
+
+결과에 만족하면, 다음 명령어로 릴리스용 프로젝트를 빌드할 수 있습니다:
+
+```bash
+npm run build
+```
+
+## 주요 기능
+
+* **반응형 디자인**: 모든 페이지는 다양한 화면 크기(데스크톱, 태블릿, 모바일)에 최적화되어 있습니다.
+* **통합 헤더**: 모든 페이지에 일관된 탐색 및 인증 기능을 제공하는 단일 헤더 컴포넌트가 적용되었습니다.
+* **메뉴 내비게이션**: 헤더 좌측의 햄버거 메뉴를 통해 주요 페이지(홈, 메뉴 추천, 재료 나눔 게시판, 마이페이지)로 쉽게 이동할 수 있습니다.
+* **사용자 인증**:
+ * 로그인 및 회원가입 팝업 시스템.
+ * 성별, 생년월일 입력 및 성향(건강 목표, 알레르기, 선호/비선호 재료, 선호 요리, 매운맛 선호도, 알림 설정) 선택 기능.
+ * 로그인 시 사용자 이름 표시 및 로그아웃 버튼으로 전환.
+ * 아이디/비밀번호 유효성 검사 및 중복 아이디 확인 (현재 `localStorage` 기반 목업).
+* **나의 냉장고**:
+ * 식재료 목록을 카테고리별로 필터링하고 검색할 수 있습니다.
+ * 새로운 식재료를 추가하거나 영수증을 등록할 수 있는 기능 (팝업 및 전용 페이지).
+* **메뉴 추천**:
+ * Gemini API와 연동될 검색창 (현재 목업).
+ * 식재료 기반 및 성향 기반 자동 메뉴 추천 기능.
+ * 레시피 카드를 최대 12개까지 표시하며, 좌우 스크롤 버튼으로 페이지 이동 가능.
+ * 각 레시피 카드를 클릭하면 상세 레시피 페이지로 이동합니다.
+* **재료 나눔 게시판**:
+ * 게시글 목록을 페이지네이션(최대 5페이지)으로 탐색할 수 있습니다.
+ * 로그인한 사용자만 새 글을 작성할 수 있습니다.
+ * 각 게시글은 고유한 댓글 기능을 가지며, 댓글은 `localStorage`에 저장됩니다.
+ * 게시글 상세 페이지에서 현재 게시글의 앞뒤 2개씩, 총 4개의 관련 게시글을 표시합니다.
+* **마이페이지**:
+ * 로그인 상태에 따라 사용자 성향 정보를 표시하거나 로그인 요청 메시지를 출력합니다.
+ * 사용자의 건강 목표, 알레르기, 선호/비선호 재료, 선호 요리, 매운맛 선호도, 알림 설정을 확인할 수 있습니다.
+
+## 백엔드 연동 가이드
+
+이 프로젝트는 프론트엔드 기능 구현에 중점을 두었으며, 모든 데이터는 현재 `localStorage`를 사용하여 목업(mockup)으로 처리됩니다. 실제 백엔드와 연동하려면 다음 지침을 따르세요:
+
+1. **API 클라이언트 설치**: `axios`와 같은 HTTP 클라이언트 라이브러리를 설치합니다.
+ ```bash
+ npm install axios
+ ```
+
+2. **API 엔드포인트 정의**: 백엔드에서 제공하는 API 엔드포인트를 정의합니다. 예:
+ * `GET /api/users/{username}/ingredients` (사용자 냉장고 재료)
+ * `GET /api/ingredients/all` (모든 식재료 DB)
+ * `POST /api/ingredients` (식재료 추가)
+ * `POST /api/login` (로그인)
+ * `POST /api/signup` (회원가입)
+ * `POST /api/users/{username}/preferences` (사용자 성향 저장)
+ * `GET /api/users/{username}/preferences` (사용자 성향 조회)
+ * `GET /api/posts` (모든 게시글)
+ * `POST /api/posts` (새 게시글 작성)
+ * `GET /api/posts/{id}/comments` (게시글 댓글 조회)
+ * `POST /api/posts/{id}/comments` (댓글 작성)
+ * `GET /api/recommend/ingredients` (식재료 기반 레시피 추천)
+ * `GET /api/recommend/tendencies` (성향 기반 레시피 추천)
+ * `POST /api/gemini-search` (Gemini API 검색)
+
+3. **목업 데이터 교체**: 프로젝트 코드 내의 `// TODO: Backend Integration: Replace with API call` 주석을 찾아 `localStorage`를 사용하는 목업 로직을 실제 `axios` 호출로 교체합니다.
+
+ **예시 (로그인):**
+ ```javascript
+ // src/components/LoginPopup/LoginPopup.jsx
+ const handleLogin = async (e) => {
+ e.preventDefault();
+ try {
+ const response = await axios.post('/api/login', { username, password });
+ login(response.data.user); // Assuming backend returns user data
+ onClose();
+ } catch (error) {
+ alert("아이디 또는 비밀번호가 일치하지 않습니다.");
+ console.error("Login error:", error);
+ }
+ };
+ ```
+
+ **예시 (게시글 목록):**
+ ```javascript
+ // src/screens/CommunityPage/sections/Communitypage/Communitypage.jsx
+ useEffect(() => {
+ const fetchPosts = async () => {
+ try {
+ const response = await axios.get('/api/posts');
+ setAllPosts(response.data);
+ } catch (error) {
+ console.error("Failed to fetch posts:", error);
+ setAllPosts(DEFAULT_POSTS); // Fallback to default posts
+ }
+ };
+ fetchPosts();
+ }, []);
+ ```
+
+4. **환경 변수 설정**: API의 기본 URL 등은 `.env` 파일을 사용하여 관리하는 것이 좋습니다.
+ ```
+ VITE_API_BASE_URL=http://localhost:8080/api
+ ```
+ 그리고 코드에서는 `import.meta.env.VITE_API_BASE_URL`과 같이 접근합니다.
+
+이 가이드를 통해 "알고리즘 셰프" 프론트엔드를 실제 백엔드 서비스와 성공적으로 연동할 수 있을 것입니다.
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index cee1e2c..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import { defineConfig, globalIgnores } from 'eslint/config'
-
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{js,jsx}'],
- extends: [
- js.configs.recommended,
- reactHooks.configs['recommended-latest'],
- reactRefresh.configs.vite,
- ],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- parserOptions: {
- ecmaVersion: 'latest',
- ecmaFeatures: { jsx: true },
- sourceType: 'module',
- },
- },
- rules: {
- 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
- },
- },
-])
diff --git a/index.html b/index.html
index d7c6abc..ad21cc5 100644
--- a/index.html
+++ b/index.html
@@ -1,13 +1,36 @@
-
+
-
-
-
- algorithmcheffront
+
+ 알고리즘 셰프 - Algorithm Chef
+
+
+
-
-
+
+
diff --git a/package-lock.json b/package-lock.json
index 8a0e906..fbb37b3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,27 +1,530 @@
{
- "name": "algorithmcheffront",
- "version": "0.0.0",
+ "name": "anima-project",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "algorithmcheffront",
- "version": "0.0.0",
+ "name": "anima-project",
+ "version": "1.0.0",
"dependencies": {
- "react": "^19.1.1",
- "react-dom": "^19.1.1"
+ "axios": "^1.13.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^6.8.1"
},
"devDependencies": {
- "@eslint/js": "^9.36.0",
- "@types/react": "^19.1.16",
- "@types/react-dom": "^19.1.9",
- "@vitejs/plugin-react": "^5.0.4",
- "babel-plugin-react-compiler": "^19.1.0-rc.3",
- "eslint": "^9.36.0",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.22",
- "globals": "^16.4.0",
- "vite": "^7.1.7"
+ "@animaapp/vite-plugin-screen-graph": "^0.1.5",
+ "@vitejs/plugin-react": "4.3.4",
+ "esbuild": "0.24.0",
+ "globals": "15.12.0",
+ "vite": "6.0.4"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/@animaapp/vite-plugin-screen-graph/-/vite-plugin-screen-graph-0.1.11.tgz",
+ "integrity": "sha512-fTq3NEQaJMqVxY87iSbB18FDmotkXmfP+80Ch9v8MuwI4SB58KvGqrpLt1TuyM3kTLpTvZWSkkDMTOved8m0cw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.24.1",
+ "@babel/traverse": "^7.24.1",
+ "@babel/types": "^7.27.0",
+ "estree-walker": "^3.0.3",
+ "fast-glob": "^3.2.12",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/@animaapp/vite-plugin-screen-graph/node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
}
},
"node_modules/@babel/code-frame": {
@@ -308,9 +811,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
+ "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
"cpu": [
"ppc64"
],
@@ -325,9 +828,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
+ "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
"cpu": [
"arm"
],
@@ -342,9 +845,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
+ "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
"cpu": [
"arm64"
],
@@ -359,9 +862,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
+ "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
"cpu": [
"x64"
],
@@ -376,9 +879,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
+ "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
"cpu": [
"arm64"
],
@@ -393,9 +896,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
+ "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
"cpu": [
"x64"
],
@@ -410,9 +913,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
+ "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
"cpu": [
"arm64"
],
@@ -427,9 +930,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
+ "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
"cpu": [
"x64"
],
@@ -444,9 +947,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
+ "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
"cpu": [
"arm"
],
@@ -461,9 +964,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
+ "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
"cpu": [
"arm64"
],
@@ -478,9 +981,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
+ "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
"cpu": [
"ia32"
],
@@ -495,9 +998,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
+ "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
"cpu": [
"loong64"
],
@@ -512,9 +1015,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
+ "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
"cpu": [
"mips64el"
],
@@ -529,9 +1032,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
+ "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
"cpu": [
"ppc64"
],
@@ -546,9 +1049,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
+ "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
"cpu": [
"riscv64"
],
@@ -563,9 +1066,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
+ "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
"cpu": [
"s390x"
],
@@ -580,9 +1083,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
+ "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
"cpu": [
"x64"
],
@@ -596,27 +1099,10 @@
"node": ">=18"
}
},
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
+ "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
"cpu": [
"x64"
],
@@ -631,9 +1117,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
+ "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
"cpu": [
"arm64"
],
@@ -648,9 +1134,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
+ "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
"cpu": [
"x64"
],
@@ -664,27 +1150,10 @@
"node": ">=18"
}
},
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
+ "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
"cpu": [
"x64"
],
@@ -699,9 +1168,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
- "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
+ "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
"cpu": [
"arm64"
],
@@ -716,9 +1185,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
- "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
+ "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
"cpu": [
"ia32"
],
@@ -733,9 +1202,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
- "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
+ "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
"cpu": [
"x64"
],
@@ -749,215 +1218,6 @@
"node": ">=18"
}
},
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
- "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.4.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -1008,17 +1268,57 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.43",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz",
- "integrity": "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==",
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz",
+ "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz",
- "integrity": "sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
"cpu": [
"arm"
],
@@ -1030,9 +1330,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz",
- "integrity": "sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
"cpu": [
"arm64"
],
@@ -1044,9 +1344,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz",
- "integrity": "sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
"cpu": [
"arm64"
],
@@ -1058,9 +1358,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz",
- "integrity": "sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
"cpu": [
"x64"
],
@@ -1072,9 +1372,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz",
- "integrity": "sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
"cpu": [
"arm64"
],
@@ -1086,9 +1386,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz",
- "integrity": "sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
"cpu": [
"x64"
],
@@ -1100,9 +1400,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz",
- "integrity": "sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
"cpu": [
"arm"
],
@@ -1114,9 +1414,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz",
- "integrity": "sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
"cpu": [
"arm"
],
@@ -1128,9 +1428,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz",
- "integrity": "sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
"cpu": [
"arm64"
],
@@ -1142,9 +1442,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz",
- "integrity": "sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
"cpu": [
"arm64"
],
@@ -1156,9 +1456,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz",
- "integrity": "sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
"cpu": [
"loong64"
],
@@ -1170,9 +1470,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz",
- "integrity": "sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
"cpu": [
"ppc64"
],
@@ -1184,9 +1484,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz",
- "integrity": "sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
"cpu": [
"riscv64"
],
@@ -1198,9 +1498,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz",
- "integrity": "sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
"cpu": [
"riscv64"
],
@@ -1212,9 +1512,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz",
- "integrity": "sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
"cpu": [
"s390x"
],
@@ -1226,9 +1526,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz",
- "integrity": "sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
"cpu": [
"x64"
],
@@ -1240,9 +1540,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz",
- "integrity": "sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
"cpu": [
"x64"
],
@@ -1254,9 +1554,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz",
- "integrity": "sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
"cpu": [
"arm64"
],
@@ -1268,9 +1568,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz",
- "integrity": "sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
"cpu": [
"arm64"
],
@@ -1282,9 +1582,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz",
- "integrity": "sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
"cpu": [
"ia32"
],
@@ -1296,9 +1596,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz",
- "integrity": "sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
"cpu": [
"x64"
],
@@ -1310,9 +1610,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz",
- "integrity": "sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
"cpu": [
"x64"
],
@@ -1340,196 +1640,105 @@
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "19.2.2",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
- "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "csstype": "^3.0.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.2",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
- "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz",
- "integrity": "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.4",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.43",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.18.0"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
- "peer": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
+ "dependencies": {
+ "@babel/types": "^7.0.0"
}
},
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dev": true,
"license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
}
},
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "@babel/types": "^7.28.2"
}
},
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "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",
+ "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
"dev": true,
"license": "MIT",
"dependencies": {
- "color-convert": "^2.0.1"
+ "@babel/core": "^7.26.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
},
"engines": {
- "node": ">=8"
+ "node": "^14.18.0 || >=16.0.0"
},
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
}
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
},
- "node_modules/babel-plugin-react-compiler": {
- "version": "19.1.0-rc.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz",
- "integrity": "sha512-mjRn69WuTz4adL0bXGx8Rsyk1086zFJeKmes6aK0xPuK3aaXmDJdLHqwKKMrpm6KAI1MCoUK72d2VeqQbu8YIA==",
- "dev": true,
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.0"
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/baseline-browser-mapping": {
- "version": "2.8.25",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz",
- "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==",
+ "version": "2.8.30",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz",
+ "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
- "node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/browserslist": {
- "version": "4.27.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
- "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==",
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
"dev": true,
"funding": [
{
@@ -1548,10 +1757,10 @@
"license": "MIT",
"peer": true,
"dependencies": {
- "baseline-browser-mapping": "^2.8.19",
- "caniuse-lite": "^1.0.30001751",
- "electron-to-chromium": "^1.5.238",
- "node-releases": "^2.0.26",
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
"update-browserslist-db": "^1.1.4"
},
"bin": {
@@ -1561,20 +1770,23 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
"engines": {
- "node": ">=6"
+ "node": ">= 0.4"
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001754",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
- "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
+ "version": "1.0.30001756",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
+ "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==",
"dev": true,
"funding": [
{
@@ -1592,50 +1804,18 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
- "color-name": "~1.1.4"
+ "delayed-stream": "~1.0.0"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">= 0.8"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -1643,28 +1823,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1683,353 +1841,217 @@
}
}
},
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.249",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.249.tgz",
- "integrity": "sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==",
- "dev": true,
- "license": "ISC"
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
},
- "node_modules/esbuild": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
- "dev": true,
- "hasInstallScript": true,
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
},
"engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.12",
- "@esbuild/android-arm": "0.25.12",
- "@esbuild/android-arm64": "0.25.12",
- "@esbuild/android-x64": "0.25.12",
- "@esbuild/darwin-arm64": "0.25.12",
- "@esbuild/darwin-x64": "0.25.12",
- "@esbuild/freebsd-arm64": "0.25.12",
- "@esbuild/freebsd-x64": "0.25.12",
- "@esbuild/linux-arm": "0.25.12",
- "@esbuild/linux-arm64": "0.25.12",
- "@esbuild/linux-ia32": "0.25.12",
- "@esbuild/linux-loong64": "0.25.12",
- "@esbuild/linux-mips64el": "0.25.12",
- "@esbuild/linux-ppc64": "0.25.12",
- "@esbuild/linux-riscv64": "0.25.12",
- "@esbuild/linux-s390x": "0.25.12",
- "@esbuild/linux-x64": "0.25.12",
- "@esbuild/netbsd-arm64": "0.25.12",
- "@esbuild/netbsd-x64": "0.25.12",
- "@esbuild/openbsd-arm64": "0.25.12",
- "@esbuild/openbsd-x64": "0.25.12",
- "@esbuild/openharmony-arm64": "0.25.12",
- "@esbuild/sunos-x64": "0.25.12",
- "@esbuild/win32-arm64": "0.25.12",
- "@esbuild/win32-ia32": "0.25.12",
- "@esbuild/win32-x64": "0.25.12"
+ "node": ">= 0.4"
}
},
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.259",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz",
+ "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==",
"dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">= 0.4"
}
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 0.4"
}
},
- "node_modules/eslint": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
- "dev": true,
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
- "peer": true,
"dependencies": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.1",
- "@eslint/plugin-kit": "^0.4.1",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
+ "es-errors": "^1.3.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
+ "node": ">= 0.4"
}
},
- "node_modules/eslint-plugin-react-hooks": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
- "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
- "dev": true,
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
- "engines": {
- "node": ">=10"
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
},
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
- "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
+ "node_modules/esbuild": {
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
+ "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
"dev": true,
+ "hasInstallScript": true,
"license": "MIT",
- "peerDependencies": {
- "eslint": ">=8.40"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
+ "bin": {
+ "esbuild": "bin/esbuild"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
},
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.24.0",
+ "@esbuild/android-arm": "0.24.0",
+ "@esbuild/android-arm64": "0.24.0",
+ "@esbuild/android-x64": "0.24.0",
+ "@esbuild/darwin-arm64": "0.24.0",
+ "@esbuild/darwin-x64": "0.24.0",
+ "@esbuild/freebsd-arm64": "0.24.0",
+ "@esbuild/freebsd-x64": "0.24.0",
+ "@esbuild/linux-arm": "0.24.0",
+ "@esbuild/linux-arm64": "0.24.0",
+ "@esbuild/linux-ia32": "0.24.0",
+ "@esbuild/linux-loong64": "0.24.0",
+ "@esbuild/linux-mips64el": "0.24.0",
+ "@esbuild/linux-ppc64": "0.24.0",
+ "@esbuild/linux-riscv64": "0.24.0",
+ "@esbuild/linux-s390x": "0.24.0",
+ "@esbuild/linux-x64": "0.24.0",
+ "@esbuild/netbsd-x64": "0.24.0",
+ "@esbuild/openbsd-arm64": "0.24.0",
+ "@esbuild/openbsd-x64": "0.24.0",
+ "@esbuild/sunos-x64": "0.24.0",
+ "@esbuild/win32-arm64": "0.24.0",
+ "@esbuild/win32-ia32": "0.24.0",
+ "@esbuild/win32-x64": "0.24.0"
}
},
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">=6"
}
},
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "@types/estree": "^1.0.0"
}
},
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
"dependencies": {
- "estraverse": "^5.1.0"
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
},
"engines": {
- "node": ">=0.10"
+ "node": ">=8.6.0"
}
},
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "ISC",
"dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
+ "reusify": "^1.0.4"
}
},
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flat-cache": "^4.0.0"
+ "to-regex-range": "^5.0.1"
},
"engines": {
- "node": ">=16.0.0"
+ "node": ">=8"
}
},
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
"license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=4.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
}
},
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
},
"engines": {
- "node": ">=16"
+ "node": ">= 6"
}
},
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2045,6 +2067,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2055,23 +2086,60 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
- "is-glob": "^4.0.3"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">=10.13.0"
+ "node": ">= 6"
}
},
"node_modules/globals": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
- "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "version": "15.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
+ "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2081,51 +2149,55 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
- "node": ">= 4"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
+ "has-symbols": "^1.0.3"
},
"engines": {
- "node": ">=6"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
"engines": {
- "node": ">=0.8.19"
+ "node": ">= 0.4"
}
},
"node_modules/is-extglob": {
@@ -2151,33 +2223,22 @@
"node": ">=0.10.0"
}
},
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
- "license": "ISC"
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -2191,27 +2252,6 @@
"node": ">=6"
}
},
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -2225,74 +2265,80 @@
"node": ">=6"
}
},
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
- "json-buffer": "3.0.1"
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
}
},
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
+ "yallist": "^3.0.2"
}
},
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 0.4"
}
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
},
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "yallist": "^3.0.2"
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
}
},
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "mime-db": "1.52.0"
},
"engines": {
- "node": "*"
+ "node": ">= 0.6"
}
},
"node_modules/ms": {
@@ -2321,13 +2367,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -2335,89 +2374,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2426,14 +2382,13 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
- "node": ">=12"
+ "node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
@@ -2468,72 +2423,126 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.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",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
},
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
},
"node_modules/react": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
- "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
- "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
- "scheduler": "^0.27.0"
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
},
"peerDependencies": {
- "react": "^19.2.0"
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
}
},
"node_modules/react-refresh": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
- "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "node_modules/react-router": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz",
+ "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz",
+ "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.1",
+ "react-router": "6.30.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=4"
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
}
},
"node_modules/rollup": {
- "version": "4.53.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.1.tgz",
- "integrity": "sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==",
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2547,36 +2556,63 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.53.1",
- "@rollup/rollup-android-arm64": "4.53.1",
- "@rollup/rollup-darwin-arm64": "4.53.1",
- "@rollup/rollup-darwin-x64": "4.53.1",
- "@rollup/rollup-freebsd-arm64": "4.53.1",
- "@rollup/rollup-freebsd-x64": "4.53.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.53.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.53.1",
- "@rollup/rollup-linux-arm64-gnu": "4.53.1",
- "@rollup/rollup-linux-arm64-musl": "4.53.1",
- "@rollup/rollup-linux-loong64-gnu": "4.53.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.53.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.53.1",
- "@rollup/rollup-linux-riscv64-musl": "4.53.1",
- "@rollup/rollup-linux-s390x-gnu": "4.53.1",
- "@rollup/rollup-linux-x64-gnu": "4.53.1",
- "@rollup/rollup-linux-x64-musl": "4.53.1",
- "@rollup/rollup-openharmony-arm64": "4.53.1",
- "@rollup/rollup-win32-arm64-msvc": "4.53.1",
- "@rollup/rollup-win32-ia32-msvc": "4.53.1",
- "@rollup/rollup-win32-x64-gnu": "4.53.1",
- "@rollup/rollup-win32-x64-msvc": "4.53.1",
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
"fsevents": "~2.3.2"
}
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
},
"node_modules/semver": {
"version": "6.3.1",
@@ -2588,29 +2624,6 @@
"semver": "bin/semver.js"
}
},
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -2621,60 +2634,17 @@
"node": ">=0.10.0"
}
},
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "prelude-ls": "^1.2.1"
+ "is-number": "^7.0.0"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">=8.0"
}
},
"node_modules/update-browserslist-db": {
@@ -2708,36 +2678,23 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
"node_modules/vite": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
- "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.4.tgz",
+ "integrity": "sha512-zwlH6ar+6o6b4Wp+ydhtIKLrGM/LoqZzcdVmkGAFun0KHTzIzjh+h0kungEx7KJg/PYnC80I4TII9WkjciSR6Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "esbuild": "^0.25.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
+ "esbuild": "^0.24.0",
+ "postcss": "^8.4.49",
+ "rollup": "^4.23.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -2746,14 +2703,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"jiti": ">=1.21.0",
- "less": "^4.0.0",
+ "less": "*",
"lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -2794,51 +2751,12 @@
}
}
},
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"license": "ISC"
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
}
}
}
diff --git a/package.json b/package.json
index 8c3bc9d..1b8178f 100644
--- a/package.json
+++ b/package.json
@@ -1,28 +1,25 @@
{
- "name": "algorithmcheffront",
- "private": true,
- "version": "0.0.0",
+ "name": "anima-project",
+ "version": "1.0.0",
+ "description": "A React project automatically generated by Anima, the design-to-code platform",
+ "source": "./index.html",
"type": "module",
"scripts": {
"dev": "vite",
- "build": "vite build",
- "lint": "eslint .",
- "preview": "vite preview"
+ "build": "vite build"
},
"dependencies": {
- "react": "^19.1.1",
- "react-dom": "^19.1.1"
+ "axios": "^1.13.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^6.8.1"
},
"devDependencies": {
- "@eslint/js": "^9.36.0",
- "@types/react": "^19.1.16",
- "@types/react-dom": "^19.1.9",
- "@vitejs/plugin-react": "^5.0.4",
- "babel-plugin-react-compiler": "^19.1.0-rc.3",
- "eslint": "^9.36.0",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.22",
- "globals": "^16.4.0",
- "vite": "^7.1.7"
+ "@animaapp/vite-plugin-screen-graph": "^0.1.5",
+ "@vitejs/plugin-react": "4.3.4",
+ "esbuild": "0.24.0",
+ "globals": "15.12.0",
+ "vite": "6.0.4"
}
}
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/src/App.jsx b/src/App.jsx
index f67355a..018704f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,35 +1,69 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import React from "react";
+import { RouterProvider, createBrowserRouter } from "react-router-dom";
+import { AuthProvider } from "./contexts/AuthContext";
+import { ScrollToTop } from "./components/ScrollToTop";
-function App() {
- const [count, setCount] = useState(0)
+// ▼▼▼ [수정 1] 껍데기(CommunityContent) 지우고, 알맹이(Communitycontentpage)를 import 하세요!
+// 경로가 './screens/Communitycontentpage/Communitycontentpage' 인지
+// './screens/sections/Communitycontentpage' 인지 본인 폴더 구조에 맞춰 정확히 적어주세요.
+import { Communitycontentpage } from "./screens/CommunityContent/sections/Communitycontentpage";
+// ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
- return (
- <>
-
- Vite + React
-
-
-
- Edit src/App.jsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
- >
- )
-}
+import { CommunityPage } from "./screens/CommunityPage";
+import { Desktop } from "./screens/Desktop";
+import { MyPage } from "./screens/MyPage";
+import { RecipePage } from "./screens/RecipePage";
+import { AddIngredientPage } from "./screens/AddIngredientPage";
+import { CreatePostPage } from "./screens/CreatePostPage";
+import { MenuRecommendationPage } from "./screens/MenuRecommendationPage";
+
+const router = createBrowserRouter([
+ {
+ path: "/*",
+ element: ,
+ },
+ {
+ path: "/desktop",
+ element: ,
+ },
+ {
+ path: "/communitypage",
+ element: ,
+ },
+ {
+ path: "/mypage",
+ element: ,
+ },
+ {
+ path: "/recipepage/:id",
+ element: ,
+ },
+ {
+ // ▼▼▼ [수정 2] 여기서 CommunityContent 대신 Communitycontentpage를 바로 연결!
+ path: "/communitycontentpage/:id",
+ element: ,
+ // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
+ },
+ {
+ path: "/addingredient",
+ element: ,
+ },
+ {
+ path: "/createpost",
+ element: ,
+ },
+ {
+ path: "/menurecommendation",
+ element: ,
+ },
+]);
-export default App
+export const App = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/api/Community/community.js b/src/api/Community/community.js
new file mode 100644
index 0000000..8c17770
--- /dev/null
+++ b/src/api/Community/community.js
@@ -0,0 +1,150 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/board";
+
+export const getPostList = async (page, size, filter) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/posts`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ page: page,
+ size,
+ filter: filter || null,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const createPost = async (post) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(`${BACKEND_DOMAIN}/post`, post, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const getPostDetail = async (postId, page, size) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/post/${postId}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ page,
+ size,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const createComment = async (postId, comment) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(
+ `${BACKEND_DOMAIN}/post/${postId}/comment`,
+ comment,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const getReplies = async (commentId, page) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(
+ `${BACKEND_DOMAIN}/comments/${commentId}/replies`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ page,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const updatePost = async (postId, updatedPost) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.put(
+ `${BACKEND_DOMAIN}/post/${postId}`,
+ updatedPost,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const deletePost = async (postId) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.delete(`${BACKEND_DOMAIN}/post/${postId}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/Community/recipeReview.js b/src/api/Community/recipeReview.js
new file mode 100644
index 0000000..fcb8ac9
--- /dev/null
+++ b/src/api/Community/recipeReview.js
@@ -0,0 +1,20 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/recipe/review";
+
+export const makeReview = async (review) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(`${BACKEND_DOMAIN}`, review, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/Recipe/geminiRecipeApi.js b/src/api/Recipe/geminiRecipeApi.js
new file mode 100644
index 0000000..2cdc152
--- /dev/null
+++ b/src/api/Recipe/geminiRecipeApi.js
@@ -0,0 +1,60 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/recipes/recommend";
+
+export const recommendPrefer = async (prefer) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(`${BACKEND_DOMAIN}/prefer`, prefer, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const recommendCondition = async (condition) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(
+ `${BACKEND_DOMAIN}/condition`,
+ condition,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const recommendExpir = async (expire) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(`${BACKEND_DOMAIN}/expir`, expire, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/Recipe/recipeApi.js b/src/api/Recipe/recipeApi.js
new file mode 100644
index 0000000..b3f20ff
--- /dev/null
+++ b/src/api/Recipe/recipeApi.js
@@ -0,0 +1,45 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/api/recipes";
+
+export const getRandomRecipe = async (ingredientName) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/search`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ ingredient: ingredientName,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const getRandomRecipeMultiple = async (ingredients) => {
+ const token = localStorage.getItem("accessToken");
+ console.log("헤더 값 확인:", `Bearer ${token}`);
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/search-multi`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ ingredients: ingredients.join(","),
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log("에러 상태 코드:", error.response.status);
+ console.log("에러 데이터:", error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/Stt/sttApi.js b/src/api/Stt/sttApi.js
new file mode 100644
index 0000000..5646568
--- /dev/null
+++ b/src/api/Stt/sttApi.js
@@ -0,0 +1,45 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/api";
+
+/**
+ * * @param {Blob} audioBlob - Voice.js에서 녹음된 오디오 Blob 객체
+ * @returns {Promise} - 추천된 레시피 리스트 (List)
+ */
+export const recommendByVoice = async (audioBlob) => {
+ const token = localStorage.getItem("accessToken");
+ const userId = localStorage.getItem("userId");
+
+ if (!userId) {
+ console.error("User ID not found. Please login.");
+ throw new Error("로그인이 필요한 서비스입니다.");
+ }
+
+ const formData = new FormData();
+
+ formData.append("audio", audioBlob, "voice.webm");
+
+ formData.append("userId", userId);
+
+ try {
+ const response = await axios.post(`${BACKEND_DOMAIN}/stt`, formData, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "multipart/form-data",
+ },
+ });
+
+ console.log("음성 검색 성공, 레시피 개수:", response.data.length);
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.error(
+ "음성 검색 요청 실패:",
+ error.response.status,
+ error.response.data
+ );
+ } else {
+ console.error("서버 연결 실패:", error.message);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/authApi.js b/src/api/authApi.js
new file mode 100644
index 0000000..0140a7d
--- /dev/null
+++ b/src/api/authApi.js
@@ -0,0 +1,46 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/auth";
+
+export const loginAPi = async (userId, password) => {
+ const response = await axios.post(`${BACKEND_DOMAIN}/login`, {
+ userId,
+ password,
+ });
+ return response.data;
+};
+
+export const signUpApi = async (newUser) => {
+ const response = await axios.post(`${BACKEND_DOMAIN}/signUp`, newUser);
+ return response.data;
+};
+
+export const findPasswordApi = async (userId, email) => {
+ const response = await axios.patch(`${BACKEND_DOMAIN}/findPassword`, {
+ userId,
+ email,
+ });
+ return response.data;
+};
+
+export const findUserIdApi = async (email, birthDate) => {
+ const response = await axios.post(`${BACKEND_DOMAIN}/findUserId`, {
+ email,
+ birthDate,
+ });
+ return response.data;
+};
+
+export const updateTempPasswordApi = async (password) => {
+ const token = localStorage.getItem("accessToken");
+
+ const response = await axios.patch(
+ `${BACKEND_DOMAIN}/update-tempPassword`,
+ { password },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+};
diff --git a/src/api/fridgeApi.js b/src/api/fridgeApi.js
new file mode 100644
index 0000000..8b18349
--- /dev/null
+++ b/src/api/fridgeApi.js
@@ -0,0 +1,124 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/fridge";
+export const registerIngredientManual = async (ingredients) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(
+ `${BACKEND_DOMAIN}/ingredient/register/manual`,
+ { ingredients },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const updateIngredientInfo = async (ingredient) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.patch(
+ `${BACKEND_DOMAIN}/ingredient/update`,
+ ingredient,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const deleteIngredientInfo = async (id) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ await axios.delete(`${BACKEND_DOMAIN}/ingredient/${id}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return true;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const updateIngredients = async (ingredients) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.patch(
+ `${BACKEND_DOMAIN}/ingredients/register/update`,
+ ingredients,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const getFridgeIngredients = async () => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/ingredients`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const filterFridgeIngredientsByCategory = async (category) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/ingredients`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ category: category,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/ingredientsApi.js b/src/api/ingredientsApi.js
new file mode 100644
index 0000000..988b4af
--- /dev/null
+++ b/src/api/ingredientsApi.js
@@ -0,0 +1,70 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/ingredients";
+
+export const getIngredientsInfo = async (page, size) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(BACKEND_DOMAIN, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ page: page,
+ size: size,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const filterIngredientsByCategory = async (page, size, category) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(BACKEND_DOMAIN, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ page: page,
+ size: size,
+ category: category,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const filterIngredientsByName = async (page, size, ingredientName) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(BACKEND_DOMAIN, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ params: {
+ page: page,
+ size: size,
+ name: ingredientName,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/preferenceApi.js b/src/api/preferenceApi.js
new file mode 100644
index 0000000..feae0cf
--- /dev/null
+++ b/src/api/preferenceApi.js
@@ -0,0 +1,38 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080";
+
+export const getAllergyInfo = async () => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/allergies`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const getHealthGoalInfo = async () => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/healthGoals`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/api/userInfo.js b/src/api/userInfo.js
new file mode 100644
index 0000000..2c6dc25
--- /dev/null
+++ b/src/api/userInfo.js
@@ -0,0 +1,56 @@
+import axios from "axios";
+const BACKEND_DOMAIN = "http://localhost:8080/user";
+
+export const makeSurveyApi = async (survey) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.post(`${BACKEND_DOMAIN}/survey`, survey, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const getUserInfo = async () => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.get(`${BACKEND_DOMAIN}/mypage`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
+
+export const updateUserTendency = async (newInfo) => {
+ const token = localStorage.getItem("accessToken");
+ try {
+ const response = await axios.patch(`${BACKEND_DOMAIN}/survey`, newInfo, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ if (error.response) {
+ console.log(error.response.status);
+ console.log(error.response.data);
+ }
+ throw error;
+ }
+};
diff --git a/src/assets/react.svg b/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/components/AddIngredientPopup/AddIngredientPopup.jsx b/src/components/AddIngredientPopup/AddIngredientPopup.jsx
new file mode 100644
index 0000000..6fb24e2
--- /dev/null
+++ b/src/components/AddIngredientPopup/AddIngredientPopup.jsx
@@ -0,0 +1,133 @@
+import React, { useState, useEffect } from "react";
+import "./style.css";
+
+const DEFAULT_IMAGE_URL =
+ "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png";
+
+export const AddIngredientPopup = ({ onClose, onAdd, items }) => {
+ const [editingItems, setEditingItems] = useState([]);
+
+ useEffect(() => {
+ const today = new Date().toISOString().split("T")[0];
+
+ if (items && items.length > 0) {
+ const initializedItems = items.map((item) => ({
+ ...item,
+ purchaseDate: item.purchaseDate
+ ? item.purchaseDate.split("T")[0]
+ : today,
+ expiredDate: item.expiredDate ? item.expiredDate.split("T")[0] : "",
+ image: item.image || DEFAULT_IMAGE_URL,
+ }));
+ setEditingItems(initializedItems);
+ }
+ }, [items]);
+
+ const handleItemChange = (id, field, value) => {
+ setEditingItems((prev) =>
+ prev.map((item) => (item.id === id ? { ...item, [field]: value } : item))
+ );
+ };
+
+ const handleDeleteItem = (id) => {
+ setEditingItems((prev) => prev.filter((item) => item.id !== id));
+ };
+
+ const handleFinalSubmit = () => {
+ const isValid = editingItems.every(
+ (item) => item.name && item.purchaseDate && item.expiredDate
+ );
+
+ if (!isValid) {
+ alert("모든 재료의 이름, 구매일자, 유통기한을 입력해주세요.");
+ return;
+ }
+
+ if (editingItems.length === 0) {
+ alert("등록할 재료가 없습니다.");
+ return;
+ }
+
+ onAdd(editingItems);
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
재료 확인 및 수정
+
+ 자동 계산된 유통기한을 확인하고 필요하면 수정해주세요.
+
+
+
+
+
+ 재료명
+ 구매일자
+ 유통기한
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AddIngredientPopup;
diff --git a/src/components/AddIngredientPopup/ChangeIngrdientPopup.jsx b/src/components/AddIngredientPopup/ChangeIngrdientPopup.jsx
new file mode 100644
index 0000000..555a207
--- /dev/null
+++ b/src/components/AddIngredientPopup/ChangeIngrdientPopup.jsx
@@ -0,0 +1,130 @@
+import React, { useState } from "react";
+import "./style.css";
+import {
+ updateIngredientInfo,
+ deleteIngredientInfo,
+} from "../../api/fridgeApi";
+
+function ChangeIngredientPopup({ ingredient, onClose, onUpdate }) {
+ if (!ingredient) return null;
+
+ const { id, name, purchaseDate, expiredDate } = ingredient;
+
+ const formattedPurchaseDate = purchaseDate ? purchaseDate.split("T")[0] : "";
+ const formattedExpiredDate = expiredDate ? expiredDate.split("T")[0] : "";
+
+ const [newPurchaseDate, setNewPurchaseDate] = useState(formattedPurchaseDate);
+ const [newExpiredDate, setNewExpiredDate] = useState(formattedExpiredDate);
+ const handleUpdate = async () => {
+ if (!newPurchaseDate || !newExpiredDate) {
+ alert("구매일자와 유통기한을 모두 입력해주세요.");
+ return;
+ }
+
+ try {
+ const updateData = {
+ ingredientId: id,
+ purchasedDate: `${newPurchaseDate}T00:00:00`,
+ expiredDate: `${newExpiredDate}T00:00:00`,
+ };
+
+ const response = await updateIngredientInfo(updateData);
+
+ if (response) {
+ console.log("수정 응답:", response);
+
+ const msg = response.message || "수정되었습니다";
+ alert(`${name}의 정보가 ${msg}.`);
+
+ if (onUpdate) onUpdate();
+ onClose();
+ }
+ } catch (error) {
+ console.error("수정 실패:", error);
+ const errorMsg = error.response?.data || "정보 수정에 실패했습니다.";
+ alert(errorMsg);
+ }
+ };
+
+ const handleDelete = async () => {
+ if (!window.confirm(`정말로 ${name}을(를) 삭제하시겠습니까?`)) {
+ return;
+ }
+
+ try {
+ const result = await deleteIngredientInfo(id);
+
+ if (result) {
+ console.log("삭제 요청 ID:", id);
+ alert(`${name}이(가) 삭제되었습니다.`);
+
+ if (onUpdate) onUpdate();
+ onClose();
+ }
+ } catch (error) {
+ console.error("삭제 실패:", error);
+ alert("삭제에 실패했습니다. 잠시 후 다시 시도해주세요.");
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ChangeIngredientPopup;
diff --git a/src/components/AddIngredientPopup/ReceiptPopup.jsx b/src/components/AddIngredientPopup/ReceiptPopup.jsx
new file mode 100644
index 0000000..249ca7a
--- /dev/null
+++ b/src/components/AddIngredientPopup/ReceiptPopup.jsx
@@ -0,0 +1,50 @@
+import { useState } from "react";
+import "./style.css";
+import AddIngredientPopup from "./AddIngredientPopup";
+function ReceiptPopup({ onClose }) {
+ const [showAddPopup, setShowAddPopup] = useState(false);
+ if (showAddPopup) {
+ return (
+ setShowAddPopup(false)}
+ onAdd={() => console.log("추가할 예정")}
+ items={() => console.log("ocr api 돌려서 나온 결과")}
+ />
+ );
+ }
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
영수증 등록
+
+
+
+
+ );
+}
+
+export default ReceiptPopup;
diff --git a/src/components/AddIngredientPopup/index.js b/src/components/AddIngredientPopup/index.js
new file mode 100644
index 0000000..f656586
--- /dev/null
+++ b/src/components/AddIngredientPopup/index.js
@@ -0,0 +1 @@
+export { AddIngredientPopup } from "./AddIngredientPopup";
diff --git a/src/components/AddIngredientPopup/style.css b/src/components/AddIngredientPopup/style.css
new file mode 100644
index 0000000..00bbed4
--- /dev/null
+++ b/src/components/AddIngredientPopup/style.css
@@ -0,0 +1,334 @@
+.add-ingredient-popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+/* 재료 추가 팝업 (넓은 버전) */
+.add-ingredient-popup-container {
+ background-color: #ffffff;
+ border-radius: 20px;
+ padding: 40px;
+ width: 700px; /* 넉넉한 너비 */
+ max-width: 95%;
+ position: relative;
+ box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease-out;
+ max-height: 90vh;
+ overflow-y: auto;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+.add-ingredient-popup-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ background: none;
+ border: none;
+ font-size: 28px;
+ color: #666;
+ cursor: pointer;
+ z-index: 10;
+}
+
+.add-ingredient-popup-header {
+ margin-bottom: 25px;
+ text-align: center;
+}
+
+.add-ingredient-popup-title {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 24px;
+ font-weight: 700;
+ color: #000;
+ margin: 0;
+}
+
+.add-ingredient-popup-form {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.add-ingredient-popup-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+}
+
+.add-ingredient-popup-label {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ color: #000;
+}
+
+.add-ingredient-popup-input {
+ width: 100%;
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 16px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+.add-ingredient-popup-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+input[type="file"]::file-selector-button {
+ margin-right: 15px;
+ border: none;
+ background: #f0f0f0;
+ padding: 8px 12px;
+ border-radius: 8px;
+ color: #555;
+ cursor: pointer;
+ font-family: "Inter", sans-serif;
+ font-weight: 600;
+ transition: background-color 0.2s;
+}
+
+input[type="file"]::file-selector-button:hover {
+ background: #e0e0e0;
+}
+
+.helper-text {
+ font-size: 13px;
+ color: #888;
+ margin-top: 2px;
+}
+
+.add-ingredient-popup-submit-btn {
+ padding: 14px;
+ background-color: #f6910b;
+ color: #fff;
+ border: none;
+ border-radius: 12px;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ width: 100%;
+ margin-top: 10px;
+ transition: background-color 0.2s;
+}
+
+.add-ingredient-popup-submit-btn:hover {
+ background-color: #e58209;
+}
+
+.processed-list-container {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ width: 100%;
+}
+
+.processed-list-header {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 10px;
+ margin-bottom: 5px;
+ font-size: 13px;
+ font-weight: 600;
+ color: #666;
+ gap: 10px;
+}
+
+.processed-list-body {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ max-height: 400px;
+ overflow-y: auto;
+ padding-right: 5px;
+}
+
+.processed-item-row {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.processed-item-row .item-input {
+ padding: 10px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ font-size: 14px;
+ font-family: "Inter", Helvetica, sans-serif;
+ box-sizing: border-box;
+}
+
+.item-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.name-input {
+ flex: 1.5;
+ min-width: 120px;
+}
+
+.date-input {
+ flex: 1.2;
+ min-width: 135px;
+ text-align: center;
+ font-size: 13px;
+}
+
+.item-delete-btn {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ border: none;
+ background-color: #ffeded;
+ color: #ff4d4d;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+ transition: background-color 0.2s;
+}
+
+.item-delete-btn:hover {
+ background-color: #ffcccc;
+}
+
+.add-row-btn {
+ background: none;
+ border: 1px dashed #ccc;
+ border-radius: 8px;
+ padding: 12px;
+ color: #666;
+ font-size: 14px;
+ cursor: pointer;
+ margin-top: 5px;
+ width: 100%;
+ transition: all 0.2s;
+}
+
+.add-row-btn:hover {
+ border-color: #f6910b;
+ color: #f6910b;
+ background-color: #fff8f0;
+}
+
+.change-popup-container {
+ width: 450px;
+ padding: 40px;
+ border-radius: 20px;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+}
+
+.popup-subtitle {
+ margin-top: 5px;
+ font-size: 16px;
+ color: #666;
+ font-weight: 500;
+}
+
+.change-popup-body {
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ margin-bottom: 35px;
+ margin-top: 10px;
+}
+
+.input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.input-label {
+ font-size: 15px;
+ font-weight: 600;
+ color: #333;
+ margin-left: 4px;
+}
+
+.disabled-input {
+ background-color: #f9f9f9;
+ color: #666;
+ cursor: not-allowed;
+ border-color: #eee;
+}
+
+.change-popup-footer {
+ display: flex;
+ gap: 15px;
+}
+
+.btn-update,
+.btn-delete {
+ flex: 1;
+ padding: 16px;
+ border-radius: 12px;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ border: none;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.btn-update {
+ background-color: #f6910b;
+ color: white;
+ box-shadow: 0 4px 12px rgba(246, 145, 11, 0.2);
+}
+
+.btn-update:hover {
+ background-color: #e58209;
+ transform: translateY(-2px);
+}
+
+.btn-delete {
+ background-color: #fff0f0;
+ color: #ff4d4d;
+ border: 1px solid #ffcccc;
+}
+
+.btn-delete:hover {
+ background-color: #ffe5e5;
+ border-color: #ffb3b3;
+}
diff --git a/src/components/CommunitypageWrapper/CommunitypageWrapper.jsx b/src/components/CommunitypageWrapper/CommunitypageWrapper.jsx
new file mode 100644
index 0000000..28ac451
--- /dev/null
+++ b/src/components/CommunitypageWrapper/CommunitypageWrapper.jsx
@@ -0,0 +1,45 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import { Link } from "react-router-dom";
+import "./style.css";
+
+export const CommunitypageWrapper = ({
+ className,
+ postId,
+ title,
+ author,
+ date,
+ category,
+ content,
+}) => {
+ return (
+
+
+
+
+
+
작성자 :
+
+
{author}
+
+
작성일 :
+
+
{date}
+
+
+ );
+};
diff --git a/src/components/CommunitypageWrapper/index.js b/src/components/CommunitypageWrapper/index.js
new file mode 100644
index 0000000..47ea4ad
--- /dev/null
+++ b/src/components/CommunitypageWrapper/index.js
@@ -0,0 +1 @@
+export { CommunitypageWrapper } from "./CommunitypageWrapper";
diff --git a/src/components/CommunitypageWrapper/style.css b/src/components/CommunitypageWrapper/style.css
new file mode 100644
index 0000000..2b8c5c0
--- /dev/null
+++ b/src/components/CommunitypageWrapper/style.css
@@ -0,0 +1,124 @@
+.communitypage-wrapper {
+ align-items: center;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ display: flex;
+ gap: 10px;
+ padding: 2px 10px;
+ position: relative;
+ width: 100%;
+ text-decoration: none;
+}
+
+.communitypage-wrapper .communitypage-6 {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ padding: 0px 5px;
+ position: relative;
+ width: 60px;
+}
+
+.communitypage-wrapper .communitypage-7 {
+ align-items: center;
+ align-self: stretch;
+ color: #000000bf;
+ display: flex;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.40px;
+ line-height: 24.0px;
+ margin-top: -1.00px;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+}
+
+.communitypage-wrapper .communitypage-8 {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ padding: 1px 0px;
+ position: relative;
+}
+
+.communitypage-wrapper .communitypage-9 {
+ align-items: center;
+ color: #00000066;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.40px;
+ line-height: 24.0px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitypage-wrapper .communitypage-10 {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.32px;
+ line-height: 19.2px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitypage-wrapper .communitypage-11 {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 3px 0px;
+ position: relative;
+}
+
+.communitypage-wrapper .communitypage-12 {
+ align-items: center;
+ color: #00000066;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.36px;
+ line-height: 21.6px;
+ margin-top: -1.00px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitypage-wrapper .communitypage-13 {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.32px;
+ line-height: 19.2px;
+ position: relative;
+}
diff --git a/src/components/DivWrapper/DivWrapper.jsx b/src/components/DivWrapper/DivWrapper.jsx
new file mode 100644
index 0000000..d93923c
--- /dev/null
+++ b/src/components/DivWrapper/DivWrapper.jsx
@@ -0,0 +1,108 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React, { useState } from "react";
+import { Link } from "react-router-dom";
+import { useAuth } from "../../contexts/AuthContext";
+import { LoginPopup } from "../LoginPopup";
+import { SignupPopup } from "../SignupPopup";
+import { PreferencesPopup } from "../PreferencesPopup";
+import "./style.css";
+
+export const DivWrapper = ({ className }) => {
+ const { user, isAuthenticated, logout } = useAuth();
+ const [showLoginPopup, setShowLoginPopup] = useState(false);
+ const [showSignupPopup, setShowSignupPopup] = useState(false);
+ const [showPreferencesPopup, setShowPreferencesPopup] = useState(false);
+ const [userData, setUserData] = useState(null);
+
+ const handleSwitchToSignup = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(true);
+ };
+
+ const handleSwitchToLogin = () => {
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setShowLoginPopup(true);
+ };
+
+ const handleSwitchToPreferences = (data) => {
+ setUserData(data);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(true);
+ };
+
+ const handleCloseAll = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setUserData(null);
+ };
+
+ return (
+ <>
+
+
+
+

+
+
+
+
+
+ 알고리즘
+
+ 셰프
+
+
+
+
+
+
마이페이지
+
+ {isAuthenticated ? (
+
+ {user?.username}
+
+
+ ) : (
+
+ )}
+
+
+
+ {showLoginPopup && (
+
+ )}
+
+ {showSignupPopup && (
+
+ )}
+
+ {showPreferencesPopup && (
+
+ )}
+ >
+ );
+};
diff --git a/src/components/DivWrapper/index.js b/src/components/DivWrapper/index.js
new file mode 100644
index 0000000..b316d4a
--- /dev/null
+++ b/src/components/DivWrapper/index.js
@@ -0,0 +1 @@
+export { DivWrapper } from "./DivWrapper";
diff --git a/src/components/DivWrapper/style.css b/src/components/DivWrapper/style.css
new file mode 100644
index 0000000..4e022cf
--- /dev/null
+++ b/src/components/DivWrapper/style.css
@@ -0,0 +1,216 @@
+.div-wrapper {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ height: 120px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+}
+
+.div-wrapper .header-left-section {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ justify-content: flex-start;
+}
+
+.div-wrapper .none-frame-2 {
+ height: 80px;
+ width: auto;
+ position: relative;
+}
+
+.div-wrapper .algorithm-label-2-link {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.div-wrapper .algorithm-label-2 {
+ align-items: center;
+ color: transparent;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 1;
+ margin: 0;
+ position: relative;
+ text-align: center;
+ width: 100%;
+}
+
+.div-wrapper .text-wrapper-9 {
+ color: #000000;
+ letter-spacing: -0.46px;
+}
+
+.div-wrapper .text-wrapper-10 {
+ color: #f6910b;
+ letter-spacing: -0.46px;
+}
+
+.div-wrapper .header-right-section {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 10px;
+}
+
+.div-wrapper .mypage-button-2 {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.div-wrapper .mypage-button-2:hover {
+ transform: scale(1.05);
+}
+
+.div-wrapper .mypage-text-2 {
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ letter-spacing: -0.32px;
+ line-height: 19.2px;
+ text-align: center;
+}
+
+.div-wrapper .login-button-2 {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+}
+
+.div-wrapper .login-button-2:hover {
+ transform: scale(1.05);
+}
+
+.div-wrapper .login-text-3 {
+ align-items: center;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 600;
+ height: 24px;
+ justify-content: center;
+ left: calc(50.00% - 33px);
+ letter-spacing: -0.40px;
+ line-height: 24.0px;
+ position: absolute;
+ text-align: center;
+ top: calc(50.00% - 12px);
+ width: 66px;
+}
+
+.div-wrapper .user-info-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.div-wrapper .user-name-display {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ padding: 8px 16px;
+ background-color: #f6910b1a;
+ border-radius: 20px;
+}
+
+.div-wrapper .logout-button-2 {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 100px;
+ transition: transform 0.2s;
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.div-wrapper .logout-button-2:hover {
+ transform: scale(1.05);
+}
+
+@media (max-width: 768px) {
+ .div-wrapper {
+ padding: 10px 5px;
+ height: auto;
+ min-height: 80px;
+ }
+
+ .div-wrapper .none-frame-2 {
+ height: 50px;
+ }
+
+ .div-wrapper .algorithm-label-2 {
+ font-size: 24px;
+ }
+
+ .div-wrapper .mypage-button-2,
+ .div-wrapper .login-button-2,
+ .div-wrapper .logout-button-2 {
+ width: 90px;
+ height: 32px;
+ }
+
+ .div-wrapper .mypage-text-2,
+ .div-wrapper .login-text-3 {
+ font-size: 12px;
+ }
+
+ .div-wrapper .user-name-display {
+ font-size: 12px;
+ padding: 4px 10px;
+ }
+}
+
+@media (max-width: 480px) {
+ .div-wrapper .algorithm-label-2 {
+ font-size: 20px;
+ }
+
+ .div-wrapper .header-right-section {
+ gap: 5px;
+ padding: 5px;
+ }
+}
diff --git a/src/components/IngredientComponent/IngredientComponent.jsx b/src/components/IngredientComponent/IngredientComponent.jsx
new file mode 100644
index 0000000..102a14a
--- /dev/null
+++ b/src/components/IngredientComponent/IngredientComponent.jsx
@@ -0,0 +1,50 @@
+import React, { useState } from "react";
+import "./style.css";
+import ChangeIngredientPopup from "../AddIngredientPopup/ChangeIngrdientPopup";
+
+export const IngredientComponent = ({
+ className,
+ ingredientImage,
+ ingredient,
+ onUpdate,
+}) => {
+ const { name, dday } = ingredient;
+
+ const [showPopup, setShowPopup] = useState(false);
+
+ return (
+ <>
+ setShowPopup(true)}
+ style={{ cursor: "pointer" }}
+ >
+
+
+

+
+
+
+
+
+
+
+
+ {showPopup && (
+ setShowPopup(false)}
+ onUpdate={onUpdate}
+ />
+ )}
+ >
+ );
+};
diff --git a/src/components/IngredientComponent/index.js b/src/components/IngredientComponent/index.js
new file mode 100644
index 0000000..b5ee1c9
--- /dev/null
+++ b/src/components/IngredientComponent/index.js
@@ -0,0 +1 @@
+export { IngredientComponent } from "./IngredientComponent";
diff --git a/src/components/IngredientComponent/style.css b/src/components/IngredientComponent/style.css
new file mode 100644
index 0000000..c5cb482
--- /dev/null
+++ b/src/components/IngredientComponent/style.css
@@ -0,0 +1,107 @@
+.ingredient-component {
+ align-items: flex-start;
+ background-color: #ffffff;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ box-shadow: 0px 12px 32px #0000000a, 0px 4px 8px #00000005;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ height: 120px;
+ justify-content: space-around;
+ max-height: 120px;
+ min-width: 140px;
+ overflow: hidden;
+ padding: 10px;
+ position: relative;
+ flex-shrink: 0;
+}
+
+@media (max-width: 768px) {
+ .ingredient-component {
+ min-width: 120px;
+ }
+}
+
+.ingredient-component .ingredient-container {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ justify-content: space-between;
+ position: relative;
+ width: 100%;
+}
+
+.ingredient-component .ingredient-header {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ position: relative;
+ width: 100%;
+}
+
+.ingredient-component .ingredient-image {
+ aspect-ratio: 1;
+ height: 48px;
+ object-fit: cover;
+ position: relative;
+ width: 48px;
+}
+
+.ingredient-component .ingredient-name {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ padding: 2px;
+ position: relative;
+}
+
+.ingredient-component .ingredient-name-text {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.10px;
+ line-height: 29.0px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.ingredient-component .ingredient {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ justify-content: center;
+ padding: 2px;
+ position: relative;
+ width: 100%;
+}
+
+.ingredient-component .ingredient-2 {
+ align-self: stretch;
+ color: #000000;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: -0.06px;
+ line-height: 17.4px;
+ margin-top: -1.00px;
+ position: relative;
+}
diff --git a/src/components/IngredientInventoryWrapper/IngredientInventoryWrapper.jsx b/src/components/IngredientInventoryWrapper/IngredientInventoryWrapper.jsx
new file mode 100644
index 0000000..4162c4e
--- /dev/null
+++ b/src/components/IngredientInventoryWrapper/IngredientInventoryWrapper.jsx
@@ -0,0 +1,19 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const IngredientInventoryWrapper = ({ className }) => {
+ return (
+
+
나의 냉장고
+
+
+ 당신의 냉장고 속 재료는 무엇인가요?
+
+
+ );
+};
diff --git a/src/components/IngredientInventoryWrapper/index.js b/src/components/IngredientInventoryWrapper/index.js
new file mode 100644
index 0000000..9c45c63
--- /dev/null
+++ b/src/components/IngredientInventoryWrapper/index.js
@@ -0,0 +1 @@
+export { IngredientInventoryWrapper } from "./IngredientInventoryWrapper";
diff --git a/src/components/IngredientInventoryWrapper/style.css b/src/components/IngredientInventoryWrapper/style.css
new file mode 100644
index 0000000..f07ded5
--- /dev/null
+++ b/src/components/IngredientInventoryWrapper/style.css
@@ -0,0 +1,56 @@
+.ingredient-inventory-wrapper {
+ align-items: flex-start;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-color: #000000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ left: 64px;
+ padding: 5px 0px;
+ position: relative;
+ top: 25px;
+}
+
+.ingredient-inventory-wrapper .ingredient-inventory-2 {
+ align-items: center;
+ align-self: stretch;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: flex-start;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin-top: -1.00px;
+ position: relative;
+}
+
+@media (max-width: 768px) {
+ .ingredient-inventory-wrapper .ingredient-inventory-2 {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+}
+
+.ingredient-inventory-wrapper .ingredient-inventory-3 {
+ align-items: center;
+ align-self: stretch;
+ color: #0000008c;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ justify-content: flex-start;
+ letter-spacing: -0.10px;
+ line-height: 29.0px;
+ position: relative;
+}
+
+@media (max-width: 768px) {
+ .ingredient-inventory-wrapper .ingredient-inventory-3 {
+ font-size: 16px;
+ line-height: 23.2px;
+ }
+}
diff --git a/src/components/LoginPopup/ChangePassword/ChangePassword.jsx b/src/components/LoginPopup/ChangePassword/ChangePassword.jsx
new file mode 100644
index 0000000..2d45a78
--- /dev/null
+++ b/src/components/LoginPopup/ChangePassword/ChangePassword.jsx
@@ -0,0 +1,114 @@
+import { useState } from "react";
+import "../style.css";
+import { isPasswordValid } from "../../util/validation";
+import { updateTempPasswordApi } from "../../../api/authApi";
+
+function ChangePassword({ onSuccess }) {
+ const [password, setPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+
+ const [errors, setErrors] = useState({
+ password: "",
+ confirmPassword: "",
+ });
+
+ async function handleChangePassword(e) {
+ e.preventDefault();
+
+ let newErrors = {};
+
+ if (!isPasswordValid(password)) {
+ newErrors.password =
+ ", Ư ּ 10 ~ ִ 20ڷ Էּ.";
+ }
+
+ if (password !== confirmPassword) {
+ newErrors.confirmPassword = "йȣ ġ ʽϴ.";
+ }
+
+ setErrors(newErrors);
+ if (Object.keys(newErrors).length > 0) {
+ return;
+ }
+
+ try {
+ const result = await updateTempPasswordApi(password);
+
+ if (result) {
+ alert(result.message || "йȣ Ǿϴ.");
+
+ if (onSuccess) {
+ onSuccess();
+ }
+ }
+ } catch (error) {
+ console.error("йȣ :", error);
+ alert("йȣ 濡 ߽ϴ. ٽ õּ.");
+ }
+ }
+
+ const handleInputChange = (setter, field, value) => {
+ setter(value);
+ if (errors[field]) {
+ setErrors({ ...errors, [field]: "" });
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default ChangePassword;
diff --git a/src/components/LoginPopup/FindId/FindId.jsx b/src/components/LoginPopup/FindId/FindId.jsx
new file mode 100644
index 0000000..b145c32
--- /dev/null
+++ b/src/components/LoginPopup/FindId/FindId.jsx
@@ -0,0 +1,187 @@
+import "../style.css";
+import { useState } from "react";
+import { isNotEmpty, isEmail } from "../../util/validation";
+import { findUserIdApi } from "../../../api/authApi";
+import { FaCheckCircle } from "react-icons/fa";
+
+function FindId({ onClose, onSwitchToLogin }) {
+ const [email, setEmail] = useState("");
+ const [birthDate, setBirthDate] = useState("");
+ const [foundId, setFoundId] = useState(null);
+
+ const [errors, setErrors] = useState({
+ email: "",
+ birthDate: "",
+ });
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+
+ let newErrors = {};
+
+ if (!isNotEmpty(email)) {
+ newErrors.email = "̸ Էּ.";
+ } else if (!isEmail(email)) {
+ newErrors.email = "ùٸ ̸ ƴմϴ.";
+ }
+
+ if (!isNotEmpty(birthDate)) {
+ newErrors.birthDate = " ּ.";
+ }
+
+ setErrors(newErrors);
+ if (Object.keys(newErrors).length > 0) {
+ return;
+ }
+ const formattedBirthDate = `${birthDate}T00:00:00`;
+
+ try {
+ const result = await findUserIdApi(email, formattedBirthDate);
+ if (result && result.userId) {
+ setFoundId(result.userId);
+ } else {
+ alert("ġϴ ϴ.");
+ }
+ } catch (error) {
+ alert("ã : ߽ϴ.");
+ }
+ };
+
+ const handleInputChange = (setter, field, value) => {
+ setter(value);
+ if (errors[field]) {
+ setErrors({ ...errors, [field]: "" });
+ }
+ };
+
+ if (foundId) {
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
̵ ã
+
+
+
+
+
+
+
+
+ ȸ ̵ ãҽϴ!
+
+
+
+
+ {foundId}
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
̵ ã
+
+
+
+
+
+
+ α ư
+
+
+
+
+ );
+}
+
+export default FindId;
diff --git a/src/components/LoginPopup/FindPassword/FindPassword.jsx b/src/components/LoginPopup/FindPassword/FindPassword.jsx
new file mode 100644
index 0000000..303c37e
--- /dev/null
+++ b/src/components/LoginPopup/FindPassword/FindPassword.jsx
@@ -0,0 +1,199 @@
+import "../style.css";
+import { useState } from "react";
+import { isNotEmpty, isEmail } from "../../util/validation";
+import { findPasswordApi } from "../../../api/authApi";
+import { FaCheckCircle } from "react-icons/fa";
+
+function FindPassword({ onClose, onSwitchToLogin }) {
+ const [userId, setUserId] = useState("");
+ const [email, setEmail] = useState("");
+ const [resultMessage, setResultMessage] = useState(null);
+
+ const [errors, setErrors] = useState({
+ userId: "",
+ email: "",
+ });
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ let newErrors = {};
+
+ if (!isNotEmpty(userId)) {
+ newErrors.userId = "아이디를 입력해주세요.";
+ }
+
+ if (!isNotEmpty(email)) {
+ newErrors.email = "이메일을 입력해주세요.";
+ } else if (!isEmail(email)) {
+ newErrors.email = "올바른 이메일 형식이 아닙니다.";
+ }
+
+ setErrors(newErrors);
+ if (Object.keys(newErrors).length > 0) {
+ return;
+ }
+
+ try {
+ const result = await findPasswordApi(userId, email);
+
+ if (result) {
+ const message =
+ result.tempPassword ||
+ result.message ||
+ "이메일로 임시 비밀번호가 발송되었습니다.";
+ setResultMessage(message);
+ } else {
+ alert("일치하는 회원 정보가 없습니다.");
+ }
+ } catch (error) {
+ console.error(error);
+ if (error.response && error.response.data) {
+ alert(error.response.data);
+ } else {
+ alert("비밀번호 찾기 실패: 정보를 다시 확인해주세요.");
+ }
+ }
+ };
+
+ const handleInputChange = (setter, field, value) => {
+ setter(value);
+ if (errors[field]) {
+ setErrors({ ...errors, [field]: "" });
+ }
+ };
+
+ if (resultMessage) {
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
비밀번호 찾기 성공
+
+
+
+
+
+
+ 임시 비밀번호가 발급되었습니다. 이메일을 확인해주세요
+
+
+
+ {resultMessage}
+
+
+
+ 로그인 후 반드시 비밀번호를 변경해주세요.
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
비밀번호 찾기
+
+
+
+
+
+
+ 로그인으로 돌아가기
+
+
+
+
+ );
+}
+
+export default FindPassword;
diff --git a/src/components/LoginPopup/LoginPopup.jsx b/src/components/LoginPopup/LoginPopup.jsx
new file mode 100644
index 0000000..28c86b4
--- /dev/null
+++ b/src/components/LoginPopup/LoginPopup.jsx
@@ -0,0 +1,131 @@
+import React, { useState } from "react";
+import { useAuth } from "../../contexts/AuthContext";
+import "./style.css";
+import ChangePassword from "./ChangePassword/ChangePassword";
+
+export const LoginPopup = ({
+ onClose,
+ onSwitchToSignup,
+ onSwitchToFindId,
+ onSwitchToFindPassword,
+}) => {
+ const [id, setId] = useState("");
+ const [password, setPassword] = useState("");
+ const [viewMode, setViewMode] = useState("login");
+ const { login } = useAuth();
+
+ const handleLogin = async (e) => {
+ e.preventDefault();
+ const result = await login(id, password);
+ if (result.status === "SUCCESS") {
+ console.log("???? ????");
+ localStorage.setItem("userId", id);
+ onClose();
+ } else if (result.status === "FORCE_PASSWORD_CHANGE") {
+ console.log("?????? ?????????.");
+ setViewMode("changepw");
+ } else {
+ alert("???? ????: ????? ?????? ??????????.");
+ }
+ };
+
+ if (viewMode === "changepw") {
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
?????? ????
+
+
setViewMode("login")} />
+
+
+ );
+ }
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
?????
+
+
+
+
+
+ );
+};
diff --git a/src/components/LoginPopup/index.js b/src/components/LoginPopup/index.js
new file mode 100644
index 0000000..369fc41
--- /dev/null
+++ b/src/components/LoginPopup/index.js
@@ -0,0 +1 @@
+export { LoginPopup } from "./LoginPopup";
diff --git a/src/components/LoginPopup/style.css b/src/components/LoginPopup/style.css
new file mode 100644
index 0000000..516c188
--- /dev/null
+++ b/src/components/LoginPopup/style.css
@@ -0,0 +1,175 @@
+.login-popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.login-popup-container {
+ background-color: #ffffff;
+ border-radius: 20px;
+ padding: 40px;
+ width: 90%;
+ max-width: 400px;
+ position: relative;
+ box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease-out;
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+.login-popup-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ background: none;
+ border: none;
+ font-size: 32px;
+ color: #666666;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+}
+
+.login-popup-close:hover {
+ color: #000000;
+}
+
+.login-popup-header {
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.login-popup-title {
+ font-family: "Inter", Helvetica;
+ font-size: 28px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+}
+
+.login-popup-form {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.login-popup-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.login-popup-label {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 600;
+ color: #000000;
+}
+
+.login-popup-input {
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ transition: border-color 0.2s;
+}
+
+.login-popup-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.login-popup-input::placeholder {
+ color: #999999;
+}
+
+.login-popup-submit-btn {
+ padding: 14px;
+ background-color: #f6910b;
+ color: #ffffff;
+ border: none;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ margin-top: 10px;
+}
+
+.login-popup-submit-btn:hover {
+ background-color: #e58209;
+}
+
+.login-popup-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.login-popup-footer-text {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ color: #666666;
+}
+
+.login-popup-signup-btn {
+ background: none;
+ border: none;
+ color: #f6910b;
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.login-popup-signup-btn:hover {
+ color: #e58209;
+}
+
+.error-message {
+ color: #ff4d4f;
+ font-size: 12px;
+ margin-top: 5px;
+ display: block;
+ text-align: left;
+}
+
+.input-error {
+ border: 1px solid #ff4d4f !important;
+ background-color: #fff1f0;
+}
diff --git a/src/components/MenuInventoryWrapper/MenuInventoryWrapper.jsx b/src/components/MenuInventoryWrapper/MenuInventoryWrapper.jsx
new file mode 100644
index 0000000..9a9a275
--- /dev/null
+++ b/src/components/MenuInventoryWrapper/MenuInventoryWrapper.jsx
@@ -0,0 +1,19 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const MenuInventoryWrapper = ({ className }) => {
+ return (
+
+
메뉴 추천
+
+
+ 당신의 성향과 냉장고를 기반으로 메뉴를 추천해드립니다.
+
+
+ );
+};
diff --git a/src/components/MenuInventoryWrapper/index.js b/src/components/MenuInventoryWrapper/index.js
new file mode 100644
index 0000000..0064260
--- /dev/null
+++ b/src/components/MenuInventoryWrapper/index.js
@@ -0,0 +1 @@
+export { MenuInventoryWrapper } from "./MenuInventoryWrapper";
diff --git a/src/components/MenuInventoryWrapper/style.css b/src/components/MenuInventoryWrapper/style.css
new file mode 100644
index 0000000..0ca8d78
--- /dev/null
+++ b/src/components/MenuInventoryWrapper/style.css
@@ -0,0 +1,55 @@
+.menu-inventory-wrapper {
+ align-items: flex-start;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-color: #000000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ left: 64px;
+ padding: 5px 0px;
+ position: relative;
+ top: 25px;
+ width: 1152px;
+}
+
+.menu-inventory-wrapper .menu-inventory-label {
+ align-items: center;
+ align-self: stretch;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: flex-start;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin-top: -1.00px;
+ position: relative;
+}
+
+.menu-inventory-wrapper .menu-inventory-2 {
+ align-items: center;
+ align-self: stretch;
+ color: #0000008c;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ justify-content: flex-start;
+ letter-spacing: -0.10px;
+ line-height: 29.0px;
+ position: relative;
+}
+
+@media (max-width: 768px) {
+ .menu-inventory-wrapper .menu-inventory-label {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+
+ .menu-inventory-wrapper .menu-inventory-2 {
+ font-size: 16px;
+ line-height: 23.2px;
+ }
+}
diff --git a/src/components/MypageTendency/MypageTendency.jsx b/src/components/MypageTendency/MypageTendency.jsx
new file mode 100644
index 0000000..8f03892
--- /dev/null
+++ b/src/components/MypageTendency/MypageTendency.jsx
@@ -0,0 +1,19 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const MypageTendency = ({ className, text = "매콤칼칼" }) => {
+ return (
+
+ );
+};
diff --git a/src/components/MypageTendency/index.js b/src/components/MypageTendency/index.js
new file mode 100644
index 0000000..ccc46e0
--- /dev/null
+++ b/src/components/MypageTendency/index.js
@@ -0,0 +1 @@
+export { MypageTendency } from "./MypageTendency";
diff --git a/src/components/MypageTendency/style.css b/src/components/MypageTendency/style.css
new file mode 100644
index 0000000..079a288
--- /dev/null
+++ b/src/components/MypageTendency/style.css
@@ -0,0 +1,55 @@
+.mypage-tendency {
+ align-items: center;
+ background-color: #f6910b;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ box-shadow: 0px 6px 12px #00000008, 0px 4px 8px #00000005;
+ display: inline-flex;
+ flex-direction: column;
+ min-height: 40px;
+ justify-content: center;
+ overflow: hidden;
+ position: relative;
+ padding: 8px 16px;
+}
+
+.mypage-tendency .tendency-name-wrapper {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ justify-content: center;
+ padding: 2px 10px;
+ position: relative;
+ width: 100%;
+}
+
+.mypage-tendency .tendency-name-text-wrapper {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ position: relative;
+ width: 100%;
+}
+
+.mypage-tendency .tendency-name-text-2 {
+ align-items: center;
+ align-self: stretch;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.08px;
+ line-height: 22.4px;
+ margin: 0;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+}
diff --git a/src/components/PageButton/PageButton.jsx b/src/components/PageButton/PageButton.jsx
new file mode 100644
index 0000000..65c6620
--- /dev/null
+++ b/src/components/PageButton/PageButton.jsx
@@ -0,0 +1,18 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const PageButton = ({ className, text = "1", isActive = false, onClick }) => {
+ return (
+
+ );
+};
diff --git a/src/components/PageButton/index.js b/src/components/PageButton/index.js
new file mode 100644
index 0000000..b4b7bf5
--- /dev/null
+++ b/src/components/PageButton/index.js
@@ -0,0 +1 @@
+export { PageButton } from "./PageButton";
diff --git a/src/components/PageButton/style.css b/src/components/PageButton/style.css
new file mode 100644
index 0000000..37f0754
--- /dev/null
+++ b/src/components/PageButton/style.css
@@ -0,0 +1,46 @@
+.page-button {
+ align-items: center;
+ background: none;
+ border: none;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-color: #00000033;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ height: 20px;
+ justify-content: center;
+ position: relative;
+ width: 20px;
+ transition: border-color 0.2s;
+}
+
+.page-button:hover {
+ border-color: #000000cc;
+}
+
+.page-button.active {
+ border-color: #f6910b;
+}
+
+.page-button .element {
+ align-items: center;
+ align-self: stretch;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 15px;
+ font-weight: 600;
+ height: 17px;
+ justify-content: center;
+ letter-spacing: -0.30px;
+ line-height: 18.0px;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ transition: color 0.2s;
+}
+
+.page-button.active .element {
+ color: #f6910b;
+}
diff --git a/src/components/PreferencesPopup/PreferencesPopup.jsx b/src/components/PreferencesPopup/PreferencesPopup.jsx
new file mode 100644
index 0000000..8713860
--- /dev/null
+++ b/src/components/PreferencesPopup/PreferencesPopup.jsx
@@ -0,0 +1,261 @@
+import React, { useState, useEffect } from "react";
+import "./style.css";
+import { getAllergyInfo, getHealthGoalInfo } from "../../api/preferenceApi";
+import { makeSurveyApi } from "../../api/userInfo";
+import { useNavigate } from "react-router-dom";
+
+const SPICE_LEVELS = ["안매움", "보통", "매움"];
+const CUISINE_TYPES = ["한식", "중식", "일식", "양식", "기타"];
+
+export const PreferencesPopup = ({ onClose }) => {
+ const [healthGoalIds, setHealthGoalIds] = useState([]);
+ const [allergyIds, setAllergyIds] = useState([]);
+ const [dislikedIngredients, setDislikedIngredients] = useState("");
+ const [likedIngredients, setLikedIngredients] = useState("");
+ const [preferredCuisineList, setPreferredCuisineList] = useState([]);
+ const [spiceLevel, setSpiceLevel] = useState("");
+ const [allowPushConsumption, setAllowPushConsumption] = useState(true);
+ const [allowPushComment, setAllowPushComment] = useState(true);
+ const [allowPushNudge, setAllowPushNudge] = useState(true);
+
+ const [serverHealthGoals, setServerHealthGoals] = useState([]);
+ const [serverAllergies, setServerAllergies] = useState([]);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const [healthData, allergyData] = await Promise.all([
+ getHealthGoalInfo(),
+ getAllergyInfo(),
+ ]);
+
+ setServerHealthGoals(healthData || []);
+ setServerAllergies(allergyData || []);
+ } catch (error) {
+ console.error("데이터 로딩 실패:", error);
+ alert("목록을 불러오는 데 실패했습니다.");
+ }
+ };
+
+ fetchData();
+ }, []);
+
+ const toggleHealthGoal = (id) => {
+ setHealthGoalIds((prev) =>
+ prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
+ );
+ };
+
+ const toggleAllergy = (id) => {
+ setAllergyIds((prev) =>
+ prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
+ );
+ };
+
+ const toggleCuisine = (cuisine) => {
+ setPreferredCuisineList((prev) =>
+ prev.includes(cuisine)
+ ? prev.filter((item) => item !== cuisine)
+ : [...prev, cuisine]
+ );
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ const surveyData = {
+ healthGoalIds,
+ allergyIds,
+ dislikedIngredients,
+ likedIngredients,
+ preferredCuisine: preferredCuisineList.join(","),
+ spiceLevel,
+ allowPushConsumption,
+ allowPushComment,
+ allowPushNudge,
+ };
+
+ console.log("Sending survey data:", surveyData);
+
+ try {
+ const result = await makeSurveyApi(surveyData);
+
+ if (result) {
+ alert("성향 설정이 완료되었습니다! 가입을 환영합니다.");
+ onClose();
+ navigate("/desktop");
+ }
+ } catch (error) {
+ console.error("설문 전송 실패:", error);
+ alert("저장에 실패했습니다. 다시 시도해주세요.");
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
성향 설정
+
+ 맞춤 추천을 위한 정보를 입력해주세요
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/PreferencesPopup/UpdatePreference.jsx b/src/components/PreferencesPopup/UpdatePreference.jsx
new file mode 100644
index 0000000..a22195f
--- /dev/null
+++ b/src/components/PreferencesPopup/UpdatePreference.jsx
@@ -0,0 +1,270 @@
+import React, { useState, useEffect } from "react";
+import "./style.css";
+import { getUserInfo, updateUserTendency } from "../../api/userInfo";
+import { getAllergyInfo, getHealthGoalInfo } from "../../api/preferenceApi";
+
+const SPICE_LEVELS = ["안매움", "보통", "매움"];
+const CUISINE_TYPES = ["한식", "중식", "일식", "양식", "기타"];
+
+function UpdatePreference({ onClose }) {
+ const [healthGoalIds, setHealthGoalIds] = useState([]);
+ const [allergyIds, setAllergyIds] = useState([]);
+
+ const [dislikedIngredients, setDislikedIngredients] = useState("");
+ const [likedIngredients, setLikedIngredients] = useState("");
+
+ const [preferredCuisineList, setPreferredCuisineList] = useState([]);
+ const [spiceLevel, setSpiceLevel] = useState("");
+
+ const [allowPushConsumption, setAllowPushConsumption] = useState(false);
+ const [allowPushComment, setAllowPushComment] = useState(false);
+ const [allowPushNudge, setAllowPushNudge] = useState(false);
+
+ const [serverHealthGoals, setServerHealthGoals] = useState([]);
+ const [serverAllergies, setServerAllergies] = useState([]);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const [healthData, allergyData, myData] = await Promise.all([
+ getHealthGoalInfo(),
+ getAllergyInfo(),
+ getUserInfo(),
+ ]);
+
+ const healthList = healthData || [];
+ const allergyList = allergyData || [];
+
+ setServerHealthGoals(healthList);
+ setServerAllergies(allergyList);
+
+ if (myData) {
+ const myGoalIds = (myData.goals || [])
+ .map((name) => healthList.find((h) => h.name === name)?.id)
+ .filter((id) => id !== undefined);
+ setHealthGoalIds(myGoalIds);
+
+ const myAllergyIds = (myData.allergyList || [])
+ .map((name) => allergyList.find((a) => a.name === name)?.id)
+ .filter((id) => id !== undefined);
+ setAllergyIds(myAllergyIds);
+
+ setDislikedIngredients(myData.dislikedIngredients || "");
+
+ setLikedIngredients(myData.likedIngredients || "");
+
+ setSpiceLevel(myData.spiceLevel || "");
+
+ if (myData.preferredCuisine) {
+ setPreferredCuisineList(myData.preferredCuisine.split(","));
+ }
+
+ setAllowPushConsumption(myData.allowPushConsumption);
+ setAllowPushComment(myData.allowPushComment);
+ setAllowPushNudge(myData.allowPushNudge);
+ }
+ } catch (error) {
+ console.error("데이터 로딩 실패:", error);
+ alert("정보를 불러오는 데 실패했습니다.");
+ onClose();
+ }
+ };
+
+ fetchData();
+ }, [onClose]);
+
+ const toggleHealthGoal = (id) => {
+ setHealthGoalIds((prev) =>
+ prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
+ );
+ };
+
+ const toggleAllergy = (id) => {
+ setAllergyIds((prev) =>
+ prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
+ );
+ };
+
+ const toggleCuisine = (cuisine) => {
+ setPreferredCuisineList((prev) =>
+ prev.includes(cuisine)
+ ? prev.filter((item) => item !== cuisine)
+ : [...prev, cuisine]
+ );
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ const updatedData = {
+ healthGoalIds,
+ allergyIds,
+ dislikedIngredients,
+ likedIngredients,
+ preferredCuisine: preferredCuisineList.join(","),
+ spiceLevel,
+ allowPushConsumption,
+ allowPushComment,
+ allowPushNudge,
+ };
+
+ try {
+ const result = await updateUserTendency(updatedData);
+ if (result) {
+ alert("성향 정보가 수정되었습니다.");
+ onClose();
+ window.location.reload();
+ }
+ } catch (error) {
+ console.error("수정 실패:", error);
+ alert("정보 수정에 실패했습니다.");
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
성향 수정
+
+ 변경할 내용을 선택해주세요
+
+
+
+
+
+
+ );
+}
+
+export default UpdatePreference;
diff --git a/src/components/PreferencesPopup/index.js b/src/components/PreferencesPopup/index.js
new file mode 100644
index 0000000..d571f4a
--- /dev/null
+++ b/src/components/PreferencesPopup/index.js
@@ -0,0 +1 @@
+export { PreferencesPopup } from "./PreferencesPopup";
diff --git a/src/components/PreferencesPopup/style.css b/src/components/PreferencesPopup/style.css
new file mode 100644
index 0000000..f313960
--- /dev/null
+++ b/src/components/PreferencesPopup/style.css
@@ -0,0 +1,193 @@
+.preferences-popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-in-out;
+}
+
+.preferences-popup-container {
+ background-color: #ffffff;
+ border-radius: 20px;
+ padding: 40px;
+ width: 90%;
+ max-width: 600px;
+ max-height: 90vh;
+ overflow-y: auto;
+ position: relative;
+ box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease-out;
+}
+
+.preferences-popup-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ background: none;
+ border: none;
+ font-size: 32px;
+ color: #666666;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+ z-index: 1;
+}
+
+.preferences-popup-close:hover {
+ color: #000000;
+}
+
+.preferences-popup-header {
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.preferences-popup-title {
+ font-family: "Inter", Helvetica;
+ font-size: 28px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0 0 10px 0;
+}
+
+.preferences-popup-subtitle {
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ color: #666666;
+ margin: 0;
+}
+
+.preferences-popup-form {
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+}
+
+.preferences-popup-section {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.preferences-popup-section-label {
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ color: #000000;
+}
+
+.preferences-popup-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.preferences-chip {
+ padding: 8px 16px;
+ background-color: #e0e0e0;
+ border: 2px solid transparent;
+ border-radius: 20px;
+ color: #666666;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 600;
+ transition: all 0.2s;
+}
+
+.preferences-chip:hover {
+ background-color: #d0d0d0;
+}
+
+.preferences-chip.active {
+ background-color: #f6910b;
+ border-color: #f6910b;
+ color: #ffffff;
+}
+
+.preferences-popup-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.preferences-popup-label {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 600;
+ color: #000000;
+}
+
+.preferences-popup-input,
+.preferences-popup-select {
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ transition: border-color 0.2s;
+ background-color: #ffffff;
+}
+
+.preferences-popup-input:focus,
+.preferences-popup-select:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.preferences-popup-input::placeholder {
+ color: #999999;
+}
+
+.preferences-popup-checkboxes {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.preferences-checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 500;
+ color: #000000;
+}
+
+.preferences-checkbox-label input[type="checkbox"] {
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ accent-color: #f6910b;
+}
+
+.preferences-popup-submit-btn {
+ padding: 14px;
+ background-color: #f6910b;
+ color: #ffffff;
+ border: none;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ margin-top: 10px;
+}
+
+.preferences-popup-submit-btn:hover {
+ background-color: #e58209;
+}
diff --git a/src/components/Recipepage/Recipepage.jsx b/src/components/Recipepage/Recipepage.jsx
new file mode 100644
index 0000000..df0a519
--- /dev/null
+++ b/src/components/Recipepage/Recipepage.jsx
@@ -0,0 +1,15 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const Recipepage = ({ className, text = "닭고기" }) => {
+ return (
+
+ );
+};
diff --git a/src/components/Recipepage/index.js b/src/components/Recipepage/index.js
new file mode 100644
index 0000000..8a0ee11
--- /dev/null
+++ b/src/components/Recipepage/index.js
@@ -0,0 +1 @@
+export { Recipepage } from "./Recipepage";
diff --git a/src/components/Recipepage/style.css b/src/components/Recipepage/style.css
new file mode 100644
index 0000000..0ffc920
--- /dev/null
+++ b/src/components/Recipepage/style.css
@@ -0,0 +1,31 @@
+.recipepage {
+ align-items: center;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ display: inline-flex;
+ flex-direction: column;
+ gap: 10px;
+ justify-content: center;
+ left: 10px;
+ padding: 5px 15px;
+ position: relative;
+ top: 10px;
+}
+
+.recipepage .text-wrapper-11 {
+ align-items: center;
+ color: #000000cc;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 24px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.12px;
+ line-height: 33.6px;
+ margin-top: -1.00px;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ width: fit-content;
+}
diff --git a/src/components/ScrollToTop/ScrollToTop.jsx b/src/components/ScrollToTop/ScrollToTop.jsx
new file mode 100644
index 0000000..276b090
--- /dev/null
+++ b/src/components/ScrollToTop/ScrollToTop.jsx
@@ -0,0 +1,25 @@
+import { useLayoutEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+
+export const ScrollToTop = () => {
+ const { pathname } = useLocation();
+
+ useLayoutEffect(() => {
+ const scrollToTop = () => {
+ // Attempt to scroll immediately with instant behavior
+ window.scrollTo({ top: 0, behavior: 'instant' });
+ document.documentElement.scrollTo({ top: 0, behavior: 'instant' });
+ document.body.scrollTo({ top: 0, behavior: 'instant' });
+ };
+
+ scrollToTop(); // Run once immediately
+
+ // Add a small timeout as a fallback for stubborn cases
+ // This ensures it runs after any potential immediate DOM updates
+ const timer = setTimeout(scrollToTop, 0);
+
+ return () => clearTimeout(timer); // Clean up the timer
+ }, [pathname]);
+
+ return null;
+};
diff --git a/src/components/ScrollToTop/index.js b/src/components/ScrollToTop/index.js
new file mode 100644
index 0000000..04f3393
--- /dev/null
+++ b/src/components/ScrollToTop/index.js
@@ -0,0 +1 @@
+export { ScrollToTop } from "./ScrollToTop";
diff --git a/src/components/SignupPopup/SignupPopup.jsx b/src/components/SignupPopup/SignupPopup.jsx
new file mode 100644
index 0000000..de088c7
--- /dev/null
+++ b/src/components/SignupPopup/SignupPopup.jsx
@@ -0,0 +1,245 @@
+import React, { useState } from "react";
+import "./style.css";
+import { signUpApi } from "../../api/authApi";
+import { isEmail, isNotEmpty, isPasswordValid } from "../util/validation";
+
+export const SignupPopup = ({
+ onClose,
+ onSwitchToLogin,
+ onSwitchToPreferences,
+}) => {
+ const [userId, setUserId] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+ const [email, setEmail] = useState("");
+ const [gender, setGender] = useState("");
+ const [birthDate, setBirthDate] = useState("");
+
+ const [errors, setErrors] = useState({
+ userId: "",
+ password: "",
+ confirmPassword: "",
+ email: "",
+ gender: "",
+ birthDate: "",
+ });
+
+ const handleSignup = async (e) => {
+ e.preventDefault();
+
+ let newErrors = {};
+
+ if (!isNotEmpty(userId)) {
+ newErrors.userId = "아이디를 입력해주세요.";
+ }
+
+ if (!isNotEmpty(email)) {
+ newErrors.email = "이메일을 입력해주세요.";
+ } else if (!isEmail(email)) {
+ newErrors.email = "올바른 이메일 형식이 아닙니다.";
+ }
+
+ if (!isNotEmpty(password)) {
+ newErrors.password = "비밀번호를 입력해주세요.";
+ } else if (!isPasswordValid(password)) {
+ newErrors.password = "영문자, 특수문자 포함 10~20자로 작성해주세요.";
+ }
+
+ if (password !== confirmPassword) {
+ newErrors.confirmPassword = "비밀번호가 일치하지 않습니다.";
+ }
+
+ if (!isNotEmpty(gender)) {
+ newErrors.gender = "성별을 선택해주세요.";
+ }
+
+ if (!isNotEmpty(birthDate)) {
+ newErrors.birthDate = "생년월일을 입력해주세요.";
+ }
+
+ setErrors(newErrors);
+ if (Object.keys(newErrors).length > 0) {
+ return;
+ }
+
+ const userData = {
+ userId,
+ password,
+ email,
+ gender,
+ birthDate: `${birthDate}T00:00:00`,
+ };
+ console.log("Signup attempt:", userData);
+
+ try {
+ const result = await signUpApi(userData);
+
+ if (result) {
+ console.log("생성된 아이디 : " + result.userId);
+ onSwitchToPreferences(userData);
+ } else {
+ alert("회원가입 실패: 다시 시도해주세요.");
+ }
+ } catch (error) {
+ console.error("회원가입 에러:", error);
+ alert("서버 오류가 발생했습니다.");
+ }
+ };
+
+ const handleInputChange = (setter, field, value) => {
+ setter(value);
+ if (errors[field]) {
+ setErrors({ ...errors, [field]: "" });
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
회원가입
+
+
+
+
+
+ );
+};
diff --git a/src/components/SignupPopup/index.js b/src/components/SignupPopup/index.js
new file mode 100644
index 0000000..de7e73a
--- /dev/null
+++ b/src/components/SignupPopup/index.js
@@ -0,0 +1 @@
+export { SignupPopup } from "./SignupPopup";
diff --git a/src/components/SignupPopup/style.css b/src/components/SignupPopup/style.css
new file mode 100644
index 0000000..92d1b00
--- /dev/null
+++ b/src/components/SignupPopup/style.css
@@ -0,0 +1,167 @@
+.signup-popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.signup-popup-container {
+ background-color: #ffffff;
+ border-radius: 20px;
+ padding: 40px;
+ width: 90%;
+ max-width: 400px;
+ position: relative;
+ box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease-out;
+ max-height: 90vh;
+ overflow-y: auto;
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+.signup-popup-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ background: none;
+ border: none;
+ font-size: 32px;
+ color: #666666;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+}
+
+.signup-popup-close:hover {
+ color: #000000;
+}
+
+.signup-popup-header {
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.signup-popup-title {
+ font-family: "Inter", Helvetica;
+ font-size: 28px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+}
+
+.signup-popup-form {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.signup-popup-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.signup-popup-label {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 600;
+ color: #000000;
+}
+
+.signup-popup-input,
+.signup-popup-select {
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ transition: border-color 0.2s;
+ background-color: #ffffff;
+}
+
+.signup-popup-input:focus,
+.signup-popup-select:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.signup-popup-input::placeholder {
+ color: #999999;
+}
+
+.signup-popup-submit-btn {
+ padding: 14px;
+ background-color: #f6910b;
+ color: #ffffff;
+ border: none;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ margin-top: 10px;
+}
+
+.signup-popup-submit-btn:hover {
+ background-color: #e58209;
+}
+
+.signup-popup-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.signup-popup-footer-text {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ color: #666666;
+}
+
+.signup-popup-login-btn {
+ background: none;
+ border: none;
+ color: #f6910b;
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.signup-popup-login-btn:hover {
+ color: #e58209;
+}
diff --git a/src/components/Tendency/Tendency.jsx b/src/components/Tendency/Tendency.jsx
new file mode 100644
index 0000000..951a804
--- /dev/null
+++ b/src/components/Tendency/Tendency.jsx
@@ -0,0 +1,22 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const Tendency = ({ tendencyContainerClassName, text = "매콤칼칼", isActive = false, onClick }) => {
+ return (
+
+ );
+};
diff --git a/src/components/Tendency/index.js b/src/components/Tendency/index.js
new file mode 100644
index 0000000..4ba92b2
--- /dev/null
+++ b/src/components/Tendency/index.js
@@ -0,0 +1 @@
+export { Tendency } from "./Tendency";
diff --git a/src/components/Tendency/style.css b/src/components/Tendency/style.css
new file mode 100644
index 0000000..b02e42c
--- /dev/null
+++ b/src/components/Tendency/style.css
@@ -0,0 +1,70 @@
+.tendency {
+ align-items: center;
+ background-color: #e0e0e0;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ box-shadow: 0px 6px 12px #00000008, 0px 4px 8px #00000005;
+ cursor: pointer;
+ display: inline-flex;
+ justify-content: center;
+ overflow: hidden;
+ padding: 6px 16px;
+ position: relative;
+ transition: all 0.2s ease;
+ min-width: fit-content;
+}
+
+.tendency:hover {
+ transform: translateY(-2px);
+ box-shadow: 0px 8px 16px #00000012, 0px 6px 10px #00000008;
+}
+
+.tendency.tendency-active {
+ background-color: #f6910b;
+}
+
+.tendency .tendency-container {
+ align-items: center;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ position: relative;
+}
+
+.tendency .tendency-name {
+ align-items: center;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ position: relative;
+}
+
+.tendency .tendency-name-text {
+ align-items: center;
+ color: #666666;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 12px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.06px;
+ line-height: 16.8px;
+ text-align: center;
+ white-space: nowrap;
+ transition: color 0.2s ease;
+}
+
+.tendency.tendency-active .tendency-name-text {
+ color: #ffffff;
+}
+
+@media (max-width: 768px) {
+ .tendency {
+ padding: 5px 12px;
+ }
+
+ .tendency .tendency-name-text {
+ font-size: 11px;
+ }
+}
diff --git a/src/components/UnifiedHeader/UnifiedHeader.jsx b/src/components/UnifiedHeader/UnifiedHeader.jsx
new file mode 100644
index 0000000..2abd040
--- /dev/null
+++ b/src/components/UnifiedHeader/UnifiedHeader.jsx
@@ -0,0 +1,178 @@
+import React, { useState } from "react";
+import { Link } from "react-router-dom";
+import { useAuth } from "../../contexts/AuthContext";
+import { LoginPopup } from "../LoginPopup";
+import { SignupPopup } from "../SignupPopup";
+import { PreferencesPopup } from "../PreferencesPopup";
+import FindId from "../LoginPopup/FindId/FindId";
+import FindPassword from "../LoginPopup/FindPassword/FindPassword";
+import "./style.css";
+
+export const UnifiedHeader = () => {
+ const { user, isAuthenticated, logout } = useAuth();
+
+ const [showLoginPopup, setShowLoginPopup] = useState(false);
+ const [showSignupPopup, setShowSignupPopup] = useState(false);
+ const [showPreferencesPopup, setShowPreferencesPopup] = useState(false);
+ const [showFindIdPopup, setShowFindIdPopup] = useState(false);
+ const [showFindPasswordPopup, setShowFindPasswordPopup] = useState(false);
+
+ const [showMenu, setShowMenu] = useState(false);
+ const [userData, setUserData] = useState(null);
+ const userId = localStorage.getItem("userId");
+
+ const handleSwitchToSignup = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(true);
+ };
+
+ const handleSwitchToLogin = () => {
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setShowFindIdPopup(false);
+ setShowFindPasswordPopup(false);
+ setShowLoginPopup(true);
+ };
+
+ const handleSwitchToFindId = () => {
+ setShowLoginPopup(false);
+ setShowFindIdPopup(true);
+ };
+
+ const handleSwitchToFindPassword = () => {
+ setShowLoginPopup(false);
+ setShowFindPasswordPopup(true);
+ };
+
+ const handleSwitchToPreferences = (data) => {
+ setUserData(data);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(true);
+ };
+
+ const handleCloseAll = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setShowFindIdPopup(false);
+ setShowFindPasswordPopup(false);
+ setUserData(null);
+ };
+
+ return (
+ <>
+
+
+ {showLoginPopup && (
+
+ )}
+
+ {showSignupPopup && (
+
+ )}
+
+ {showPreferencesPopup && (
+
+ )}
+
+ {showFindIdPopup && (
+
+ )}
+
+ {showFindPasswordPopup && (
+
+ )}
+ >
+ );
+};
diff --git a/src/components/UnifiedHeader/index.js b/src/components/UnifiedHeader/index.js
new file mode 100644
index 0000000..cab05e2
--- /dev/null
+++ b/src/components/UnifiedHeader/index.js
@@ -0,0 +1 @@
+export { UnifiedHeader } from "./UnifiedHeader";
diff --git a/src/components/UnifiedHeader/style.css b/src/components/UnifiedHeader/style.css
new file mode 100644
index 0000000..af3725a
--- /dev/null
+++ b/src/components/UnifiedHeader/style.css
@@ -0,0 +1,268 @@
+.unified-header {
+ align-items: center;
+ background-color: transparent;
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ height: 120px;
+ padding: 10px 20px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.unified-header-left {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ flex: 1;
+ justify-content: flex-start;
+ position: relative;
+}
+
+.menu-toggle-btn {
+ background: none;
+ border: none;
+ font-size: 28px;
+ color: #000000;
+ cursor: pointer;
+ padding: 5px 10px;
+ transition: color 0.2s;
+}
+
+.menu-toggle-btn:hover {
+ color: #f6910b;
+}
+
+.menu-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 10px;
+ background-color: #ffffff;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ z-index: 100;
+ min-width: 200px;
+}
+
+.menu-item {
+ display: block;
+ padding: 15px 20px;
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ text-decoration: none;
+ transition: background-color 0.2s;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.menu-item:last-child {
+ border-bottom: none;
+}
+
+.menu-item:hover {
+ background-color: #f6910b1a;
+ color: #f6910b;
+}
+
+.unified-header-logo {
+ height: 80px;
+ width: auto;
+ display: block;
+}
+
+.unified-header-center {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ height: 100%;
+ text-decoration: none;
+}
+
+.unified-header-title {
+ align-items: center;
+ color: transparent;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 1;
+ margin: 0;
+ text-align: center;
+}
+
+.title-text-black {
+ color: #000000;
+ letter-spacing: -0.46px;
+}
+
+.title-text-orange {
+ color: #f6910b;
+ letter-spacing: -0.46px;
+}
+
+.unified-header-right {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.unified-mypage-btn {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.unified-mypage-btn:hover {
+ transform: scale(1.05);
+}
+
+.unified-mypage-text {
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ letter-spacing: -0.32px;
+ line-height: 19.2px;
+ text-align: center;
+}
+
+.unified-login-btn {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+}
+
+.unified-login-btn:hover {
+ transform: scale(1.05);
+}
+
+.unified-login-text {
+ align-items: center;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 600;
+ height: 24px;
+ justify-content: center;
+ left: calc(50.00% - 33px);
+ letter-spacing: -0.40px;
+ line-height: 24.0px;
+ position: absolute;
+ text-align: center;
+ top: calc(50.00% - 12px);
+ width: 66px;
+}
+
+.unified-user-info {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.unified-username {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ padding: 8px 16px;
+ background-color: #f6910b1a;
+ border-radius: 20px;
+}
+
+.unified-logout-btn {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ padding: 0 20px;
+ position: relative;
+ transition: transform 0.2s;
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.unified-logout-btn:hover {
+ transform: scale(1.05);
+}
+
+@media (max-width: 768px) {
+ .unified-header {
+ padding: 10px 10px;
+ height: auto;
+ min-height: 80px;
+ }
+
+ .unified-header-logo {
+ height: 50px;
+ }
+
+ .unified-header-title {
+ font-size: 24px;
+ }
+
+ .unified-mypage-btn,
+ .unified-login-btn,
+ .unified-logout-btn {
+ width: 90px;
+ height: 32px;
+ }
+
+ .unified-mypage-text,
+ .unified-login-text {
+ font-size: 12px;
+ }
+
+ .unified-username {
+ font-size: 12px;
+ padding: 4px 10px;
+ }
+
+ .menu-toggle-btn {
+ font-size: 24px;
+ }
+}
+
+@media (max-width: 480px) {
+ .unified-header-title {
+ font-size: 20px;
+ }
+
+ .unified-header-right {
+ gap: 5px;
+ }
+}
diff --git a/src/components/UsedIngredient/UsedIngredient.jsx b/src/components/UsedIngredient/UsedIngredient.jsx
new file mode 100644
index 0000000..24ef221
--- /dev/null
+++ b/src/components/UsedIngredient/UsedIngredient.jsx
@@ -0,0 +1,15 @@
+/*
+We're constantly improving the code you see.
+Please share your feedback here: https://form.asana.com/?k=uvp-HPgd3_hyoXRBw1IcNg&d=1152665201300829
+*/
+
+import React from "react";
+import "./style.css";
+
+export const UsedIngredient = ({ className, text = "닭고기" }) => {
+ return (
+
+ );
+};
diff --git a/src/components/UsedIngredient/index.js b/src/components/UsedIngredient/index.js
new file mode 100644
index 0000000..fc3b90e
--- /dev/null
+++ b/src/components/UsedIngredient/index.js
@@ -0,0 +1 @@
+export { UsedIngredient } from "./UsedIngredient";
diff --git a/src/components/UsedIngredient/style.css b/src/components/UsedIngredient/style.css
new file mode 100644
index 0000000..0ce632e
--- /dev/null
+++ b/src/components/UsedIngredient/style.css
@@ -0,0 +1,32 @@
+.used-ingredient {
+ align-items: center;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ display: inline-flex;
+ flex-direction: column;
+ gap: 10px;
+ height: 23px;
+ justify-content: center;
+ left: 4px;
+ padding: 3px 10px;
+ position: relative;
+ top: 2px;
+}
+
+.used-ingredient .use-ingredient-name {
+ align-items: center;
+ color: #000000cc;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 12px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.06px;
+ line-height: 16.8px;
+ margin-top: -1.00px;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ width: fit-content;
+}
diff --git a/src/components/util/validation.js b/src/components/util/validation.js
new file mode 100644
index 0000000..6439022
--- /dev/null
+++ b/src/components/util/validation.js
@@ -0,0 +1,34 @@
+export function isEmail(value) {
+ return value.includes('@');
+}
+
+export function isNotEmpty(value) {
+ return value.trim() !== '';
+}
+
+export function hasMinLength(value, minLength) {
+ return value.length >= minLength;
+}
+
+export function isEqualToOtherValue(value, otherValue) {
+ return value === otherValue;
+}
+export function isIdValid(value) {
+ if (!value || typeof value !== 'string') {
+ return false;
+ }
+
+ const regExp = /^[a-zA-Z0-9]+$/;
+
+ return regExp.test(value);
+}
+
+export function isPasswordValid(value) {
+ if (!value || typeof value !== 'string') {
+ return false;
+ }
+
+ const regExp = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*?_])[a-zA-Z0-9!@#$%^&*?_]{8,20}$/;
+
+ return regExp.test(value);
+}
\ No newline at end of file
diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js
new file mode 100644
index 0000000..328bb52
--- /dev/null
+++ b/src/contexts/AuthContext.js
@@ -0,0 +1 @@
+export { AuthProvider, useAuth } from "./AuthContext.jsx";
diff --git a/src/contexts/AuthContext.jsx b/src/contexts/AuthContext.jsx
new file mode 100644
index 0000000..2e90c55
--- /dev/null
+++ b/src/contexts/AuthContext.jsx
@@ -0,0 +1,53 @@
+import React, { createContext, useContext, useState, useEffect } from "react";
+import { loginAPi } from "../api/authApi";
+
+const AuthContext = createContext();
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error("useAuth must be used within an AuthProvider");
+ }
+ return context;
+};
+
+export const AuthProvider = ({ children }) => {
+ const [user, setUser] = useState(null);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+
+ useEffect(() => {
+ const token = localStorage.getItem("accessToken");
+ if (token) {
+ setIsAuthenticated(true);
+ }
+ }, []);
+
+ const login = async (userId, password) => {
+ try {
+ const data = await loginAPi(userId, password);
+ if (data.accessToken) {
+ localStorage.setItem("accessToken", data.accessToken);
+ }
+
+ if (data.status === "SUCCESS") {
+ setIsAuthenticated(true);
+ }
+ return data;
+ } catch (error) {
+ console.error("α :", error);
+ return { status: "FAIL", message: "α " };
+ }
+ };
+
+ const logout = () => {
+ setUser(null);
+ setIsAuthenticated(false);
+ localStorage.removeItem("currentUser");
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 08a3ac9..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,68 +0,0 @@
-:root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
diff --git a/src/index.jsx b/src/index.jsx
new file mode 100644
index 0000000..f55d523
--- /dev/null
+++ b/src/index.jsx
@@ -0,0 +1,9 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { App } from "./App";
+
+createRoot(document.getElementById("app")).render(
+
+
+ ,
+);
diff --git a/src/main.jsx b/src/main.jsx
deleted file mode 100644
index b9a1a6d..0000000
--- a/src/main.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.jsx'
-
-createRoot(document.getElementById('root')).render(
-
-
- ,
-)
diff --git a/src/screens/AddIngredientPage/AddIngredientPage.jsx b/src/screens/AddIngredientPage/AddIngredientPage.jsx
new file mode 100644
index 0000000..b500e2a
--- /dev/null
+++ b/src/screens/AddIngredientPage/AddIngredientPage.jsx
@@ -0,0 +1,286 @@
+import React, { useState, useRef, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import { BiSolidRightArrow, BiSolidLeftArrow } from "react-icons/bi";
+import "./style.css";
+import { useAuth } from "../../contexts/AuthContext";
+import { registerIngredientManual } from "../../api/fridgeApi";
+import { updateIngredients } from "../../api/fridgeApi";
+import {
+ filterIngredientsByName,
+ getIngredientsInfo,
+ filterIngredientsByCategory,
+} from "../../api/ingredientsApi";
+
+import { AddIngredientPopup } from "../../components/AddIngredientPopup/AddIngredientPopup";
+
+const CATEGORIES = [
+ "전체",
+ "육류",
+ "가공육",
+ "난류",
+ "수산물",
+ "가공수산물",
+ "건어물",
+ "젓갈",
+ "채소",
+ "버섯",
+ "유제품",
+ "두부류",
+ "콩가공품",
+ "콩",
+ "김치류",
+ "절임류",
+ "빵류",
+ "기타",
+ "가공식품",
+ "과일",
+ "조미료",
+ "향신료",
+ "곡류",
+ "견과",
+];
+const DEFAULT_IMAGE_URL =
+ "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png";
+
+export const AddIngredientPage = () => {
+ const navigate = useNavigate();
+ const scrollRef = useRef(null);
+
+ const [selectedCategory, setSelectedCategory] = useState("전체");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [selectedIngredients, setSelectedIngredients] = useState([]);
+ const [previewIngredients, setPreviewIngredients] = useState([]);
+ const [page, setPage] = useState(0);
+ const itemsPerPage = 7;
+ const [ingredients, setIngredients] = useState([]);
+ const { isAuthenticated } = useAuth();
+ const [currentView, setCurrentView] = useState(false);
+
+ useEffect(() => {
+ setPage(0);
+ }, [searchQuery, selectedCategory]);
+
+ useEffect(() => {
+ if (!isAuthenticated) {
+ setIngredients([]);
+ return;
+ }
+
+ const fetchData = async () => {
+ let ingredientData = [];
+ try {
+ if (searchQuery) {
+ ingredientData = await filterIngredientsByName(
+ page,
+ itemsPerPage,
+ searchQuery
+ );
+ } else if (selectedCategory && selectedCategory !== "전체") {
+ ingredientData = await filterIngredientsByCategory(
+ page,
+ itemsPerPage,
+ selectedCategory
+ );
+ } else {
+ ingredientData = await getIngredientsInfo(page, itemsPerPage);
+ }
+
+ if (ingredientData.content) {
+ setIngredients(ingredientData.content);
+ } else if (Array.isArray(ingredientData)) {
+ setIngredients(ingredientData);
+ } else {
+ setIngredients([]);
+ }
+ } catch (error) {
+ console.error("재료 데이터 로딩 실패");
+ }
+ };
+ fetchData();
+ }, [isAuthenticated, searchQuery, selectedCategory, page]);
+
+ function handlePrev() {
+ setPage((prev) => Math.max(prev - 1, 0));
+ }
+ function handleNext() {
+ if (ingredients.length === itemsPerPage) setPage((prev) => prev + 1);
+ }
+
+ const toggleIngredient = (ingredient) => {
+ setSelectedIngredients((prev) => {
+ const exists = prev.find((item) => item.id === ingredient.id);
+ if (exists) {
+ return prev.filter((item) => item.id !== ingredient.id);
+ } else {
+ const today = new Date().toISOString().split("T")[0];
+ return [
+ ...prev,
+ {
+ ...ingredient,
+ purchaseDate: today,
+ image: DEFAULT_IMAGE_URL,
+ },
+ ];
+ }
+ });
+ };
+
+ const handleAddIngredients = async () => {
+ if (selectedIngredients.length === 0) {
+ alert("추가할 식재료를 선택해주세요.");
+ return;
+ }
+
+ try {
+ const requestData = selectedIngredients.map((item) => ({
+ name: item.name,
+ purchaseDate: `${item.purchaseDate}T00:00:00`,
+ }));
+
+ const response = await registerIngredientManual(requestData);
+
+ if (response && response.ingredients) {
+ setPreviewIngredients(response.ingredients);
+ setCurrentView("확인");
+ }
+ } catch (error) {
+ console.error("1차 등록 실패:", error);
+ alert("재료 정보를 불러오는 데 실패했습니다.");
+ }
+ };
+
+ const handleFinalRegister = async (updatedItems) => {
+ try {
+ const requestData = {
+ ingredients: updatedItems.map((item) => ({
+ ingredientId: item.id,
+ purchaseDate: `${item.purchaseDate}T00:00:00`,
+ expiredDate: item.expiredDate ? `${item.expiredDate}T00:00:00` : null,
+ })),
+ };
+
+ const result = await updateIngredients(requestData);
+
+ if (result) {
+ alert("재료가 성공적으로 저장되었습니다!");
+ setSelectedIngredients([]);
+ setPreviewIngredients([]);
+ setCurrentView(false);
+ navigate("/desktop");
+ }
+ } catch (error) {
+ console.error("최종 저장 실패:", error);
+ alert("재료 수정에 실패했습니다.");
+ }
+ };
+
+ if (currentView === "확인") {
+ return (
+ setCurrentView(false)}
+ />
+ );
+ }
+
+ return (
+
+
+
+
+
식재료 추가
+
+ 냉장고에 추가할 식재료를 선택하세요
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+ {CATEGORIES.map((category) => (
+
+ ))}
+
+
+ {/* 가로 스크롤 그리드 */}
+
+
+
+
+ {ingredients.map((ingredient) => {
+ const isSelected = selectedIngredients.find(
+ (item) => item.id === ingredient.id
+ );
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
+ 선택된 식재료: {selectedIngredients.length}개
+
+
+
+ 취소
+
+
+
+
+
+
+ );
+};
diff --git a/src/screens/AddIngredientPage/index.js b/src/screens/AddIngredientPage/index.js
new file mode 100644
index 0000000..13def78
--- /dev/null
+++ b/src/screens/AddIngredientPage/index.js
@@ -0,0 +1 @@
+export { AddIngredientPage } from "./AddIngredientPage";
diff --git a/src/screens/AddIngredientPage/style.css b/src/screens/AddIngredientPage/style.css
new file mode 100644
index 0000000..bcadd52
--- /dev/null
+++ b/src/screens/AddIngredientPage/style.css
@@ -0,0 +1,358 @@
+.add-ingredient-page {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ position: relative;
+ width: 100%;
+}
+
+.add-ingredient-page .add-ingredient-header {
+ align-self: stretch !important;
+ width: 100% !important;
+ max-width: 1280px !important;
+ margin: 0 auto !important;
+}
+
+.add-ingredient-content {
+ align-items: flex-start;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: 20px;
+ padding: 20px 64px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.add-ingredient-title-section {
+ align-items: flex-start;
+ align-self: stretch;
+ border-bottom: 1px solid #000000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding-bottom: 20px;
+ width: 100%;
+}
+
+.add-ingredient-title {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin: 0;
+}
+
+.add-ingredient-subtitle {
+ color: #0000008c;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ letter-spacing: -0.1px;
+ line-height: 29px;
+ margin: 0;
+}
+
+.add-ingredient-search-section {
+ align-self: stretch;
+ display: flex;
+ gap: 10px;
+ width: 100%;
+}
+
+.add-ingredient-search-input {
+ flex: 1;
+ padding: 12px 20px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ transition: border-color 0.2s;
+}
+
+.add-ingredient-search-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.add-ingredient-category-section {
+ align-self: stretch;
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.add-ingredient-category-btn {
+ padding: 8px 20px;
+ background-color: #e0e0e0;
+ border: none;
+ border-radius: 12px;
+ color: #666666;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ transition: all 0.2s;
+}
+
+.add-ingredient-category-btn:hover {
+ background-color: #d0d0d0;
+}
+
+.add-ingredient-category-btn.active {
+ background-color: #f6910b;
+ color: #ffffff;
+}
+
+/* --- [가로 스크롤 핵심 스타일] --- */
+
+.add-ingredient-grid-wrapper {
+ position: relative;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px 0;
+}
+
+/* Flexbox를 사용하여 가로 배치를 강제합니다 */
+.add-ingredient-grid {
+ align-self: stretch;
+ display: flex;
+ flex-direction: row; /* 가로 방향 명시 */
+ flex-wrap: nowrap; /* 줄바꿈 금지 (핵심) */
+ overflow-x: auto; /* 가로 스크롤 허용 */
+ gap: 20px;
+ padding: 10px 5px;
+ width: 100%;
+ scroll-behavior: smooth;
+
+ /* 스크롤바 숨기기 (옵션) */
+ -ms-overflow-style: none; /* IE, Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+.add-ingredient-grid::-webkit-scrollbar {
+ display: none; /* Chrome, Safari */
+}
+
+/* 화살표 버튼 */
+.scroll-arrow-btn {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ background-color: #ffffff;
+ border: 1px solid #e0e0e0;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ z-index: 10;
+ color: #f6910b;
+ font-size: 20px;
+ transition: all 0.2s ease;
+}
+
+.scroll-arrow-btn:hover {
+ background-color: #f6910b;
+ color: white;
+ border-color: #f6910b;
+ transform: scale(1.1) translateY(-45%);
+}
+
+.scroll-arrow-btn.left {
+ left: -20px;
+}
+
+.scroll-arrow-btn.right {
+ right: -20px;
+}
+
+/* 재료 카드 */
+.add-ingredient-card {
+ align-items: center;
+ background-color: #ffffff;
+ border: 2px solid #e0e0e0;
+ border-radius: 16px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 15px;
+ position: relative;
+ transition: all 0.2s;
+
+ /* ★ 카드가 찌그러지지 않도록 크기 고정 */
+ min-width: 160px; /* 최소 너비 확보 */
+ width: 160px;
+ height: 200px;
+ flex-shrink: 0; /* 공간이 좁아도 줄어들지 않음 (핵심) */
+}
+
+.add-ingredient-card:hover {
+ border-color: #f6910b;
+ transform: translateY(-4px);
+ box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.add-ingredient-card.selected {
+ border-color: #f6910b;
+ background-color: #fff8f0;
+}
+
+.add-ingredient-card-image {
+ width: 90px;
+ height: 90px;
+ object-fit: contain;
+ margin-bottom: 5px;
+}
+
+.add-ingredient-card-name {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ text-align: center;
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.add-ingredient-card-category {
+ color: #666666;
+ font-family: "Inter", Helvetica;
+ font-size: 13px;
+ font-weight: 500;
+ text-align: center;
+}
+
+.add-ingredient-card-check {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: 24px;
+ height: 24px;
+ background-color: #f6910b;
+ border-radius: 50%;
+ color: #ffffff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ font-weight: 700;
+}
+
+.add-ingredient-footer {
+ align-items: center;
+ align-self: stretch;
+ border-top: 1px solid #e0e0e0;
+ display: flex;
+ justify-content: space-between;
+ padding: 20px 0;
+ width: 100%;
+ margin-top: auto;
+}
+
+.add-ingredient-selected-count {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.add-ingredient-footer-buttons {
+ display: flex;
+ gap: 15px;
+}
+
+.add-ingredient-cancel-btn {
+ padding: 12px 30px;
+ background-color: #e0e0e0;
+ border: none;
+ border-radius: 12px;
+ color: #666666;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ text-decoration: none;
+ transition: background-color 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.add-ingredient-cancel-btn:hover {
+ background-color: #d0d0d0;
+}
+
+.add-ingredient-submit-btn {
+ padding: 12px 30px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 12px;
+ color: #ffffff;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ transition: background-color 0.2s;
+}
+
+.add-ingredient-submit-btn:hover:not(:disabled) {
+ background-color: #e58209;
+}
+
+.add-ingredient-submit-btn:disabled {
+ background-color: #cccccc;
+ cursor: not-allowed;
+}
+
+/* 반응형 스타일 */
+@media (max-width: 768px) {
+ .add-ingredient-content {
+ padding: 20px 20px;
+ }
+
+ .add-ingredient-title {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+
+ .add-ingredient-subtitle {
+ font-size: 16px;
+ line-height: 23.2px;
+ }
+
+ .scroll-arrow-btn.left {
+ left: 0;
+ }
+ .scroll-arrow-btn.right {
+ right: 0;
+ }
+
+ .add-ingredient-footer {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .add-ingredient-footer-buttons {
+ width: 100%;
+ }
+
+ .add-ingredient-cancel-btn,
+ .add-ingredient-submit-btn {
+ flex: 1;
+ }
+}
diff --git a/src/screens/CommunityContent/CommunityContent.jsx b/src/screens/CommunityContent/CommunityContent.jsx
new file mode 100644
index 0000000..d3c060f
--- /dev/null
+++ b/src/screens/CommunityContent/CommunityContent.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import { Communitycontentpage } from "./sections/Communitycontentpage";
+import "./style.css";
+
+export const CommunityContent = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/screens/CommunityContent/index.js b/src/screens/CommunityContent/index.js
new file mode 100644
index 0000000..9ea88ad
--- /dev/null
+++ b/src/screens/CommunityContent/index.js
@@ -0,0 +1 @@
+export { CommunityContent } from "./CommunityContent";
diff --git a/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx b/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx
new file mode 100644
index 0000000..27274e1
--- /dev/null
+++ b/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx
@@ -0,0 +1,464 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate, useParams, useLocation } from "react-router-dom";
+import { useAuth } from "../../../../contexts/AuthContext";
+import "./style.css";
+import { HiArrowNarrowLeft } from "react-icons/hi";
+import UpdatePost from "./UpdatePost/UpdatePost";
+import {
+ getPostDetail,
+ deletePost,
+ updatePost,
+ createComment,
+ getReplies,
+} from "../../../../api/Community/community";
+
+export const Communitycontentpage = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { id } = useParams();
+ const { isAuthenticated } = useAuth();
+
+ const [currentPost, setCurrentPost] = useState(location.state?.post || null);
+ const [loading, setLoading] = useState(!location.state?.post);
+ const [showUpdatePage, setShowUpdatePage] = useState(false);
+
+ const [comments, setComments] = useState([]);
+ const [newComment, setNewComment] = useState("");
+ const [replyingTo, setReplyingTo] = useState(null);
+ const [repliesState, setRepliesState] = useState({});
+
+ const formatDate = (dateString) => {
+ if (!dateString) return "";
+ return String(dateString).substring(0, 10);
+ };
+
+ useEffect(() => {
+ //게시글의 댓글 가져오는 함수
+ const fetchPostData = async () => {
+ try {
+ if (!currentPost) setLoading(true);
+
+ const response = await getPostDetail(parseInt(id));
+ setCurrentPost(response);
+
+ if (response.comments) {
+ setComments(response.comments);
+ }
+ } catch (error) {
+ console.error("게시글 로드 실패:", error);
+ if (!currentPost) {
+ alert("게시글을 불러오는 데 실패했습니다.");
+ navigate("/communitypage");
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (id) {
+ fetchPostData();
+ }
+ }, [id, navigate]);
+
+ const handleToggleReplies = async (commentId) => {
+ const currentState = repliesState[commentId] || {
+ list: [],
+ page: 0,
+ expanded: false,
+ };
+
+ if (currentState.expanded) {
+ setRepliesState((prev) => ({
+ ...prev,
+ [commentId]: { ...currentState, expanded: false },
+ }));
+ return;
+ }
+ if (currentState.list.length > 0) {
+ setRepliesState((prev) => ({
+ ...prev,
+ [commentId]: { ...currentState, expanded: true },
+ }));
+ return;
+ }
+ loadReplies(commentId, 0);
+ };
+
+ const loadReplies = async (commentId, page) => {
+ try {
+ const response = await getReplies(commentId, page);
+
+ let newReplies = [];
+ if (Array.isArray(response)) {
+ newReplies = response;
+ } else if (response && Array.isArray(response.replies)) {
+ newReplies = response.replies;
+ } else if (response && Array.isArray(response.content)) {
+ newReplies = response.content;
+ } else {
+ newReplies = [];
+ }
+
+ setRepliesState((prev) => {
+ const prevState = prev[commentId] || { list: [] };
+ return {
+ ...prev,
+ [commentId]: {
+ list: page === 0 ? newReplies : [...prevState.list, ...newReplies],
+ page: page,
+ expanded: true,
+ hasMore: newReplies.length >= 20,
+ },
+ };
+ });
+ } catch (error) {
+ console.error("대댓글 로드 실패:", error);
+ }
+ };
+
+ const handleDelete = async () => {
+ if (!isAuthenticated) return alert("로그인이 필요합니다.");
+ if (window.confirm("정말 삭제하시겠습니까?")) {
+ try {
+ await deletePost(parseInt(id));
+ alert("삭제되었습니다.");
+ navigate("/communitypage");
+ } catch (error) {
+ alert("삭제 실패");
+ }
+ }
+ };
+
+ const handlePostUpdate = async (updatedData) => {
+ try {
+ const response = await updatePost(parseInt(id), updatedData);
+ setCurrentPost((prev) => ({
+ ...prev,
+ ...updatedData,
+ ...(response && typeof response === "object" ? response : {}),
+ }));
+ alert("게시글이 수정되었습니다.");
+ setShowUpdatePage(false);
+ } catch (error) {
+ alert("수정 실패");
+ }
+ };
+
+ const handleCommentSubmit = async (e) => {
+ e.preventDefault();
+ if (!isAuthenticated) return alert("로그인이 필요합니다.");
+ if (!newComment.trim()) return;
+
+ const commentRequest = {
+ parentCommentId: replyingTo ? replyingTo.commentId : null,
+ content: newComment,
+ };
+
+ try {
+ await createComment(parseInt(id), commentRequest);
+
+ const updatedPost = await getPostDetail(parseInt(id));
+ if (updatedPost.comments) {
+ setComments(updatedPost.comments);
+ }
+
+ if (replyingTo) {
+ loadReplies(replyingTo.commentId, 0);
+ }
+
+ setNewComment("");
+ setReplyingTo(null);
+ } catch (error) {
+ console.error("댓글 작성 실패:", error);
+ alert("댓글 작성 중 오류가 발생했습니다.");
+ }
+ };
+
+ const renderCommentItem = (comment) => {
+ const repliesInfo = repliesState[comment.commentId];
+ const isExpanded = repliesInfo?.expanded;
+
+ const rawList = repliesInfo?.list;
+ const repliesList = Array.isArray(rawList) ? rawList : [];
+
+ return (
+
+
+
+
+ {comment.userId || comment.author}
+
+
+ {formatDate(comment.createdAt)}
+
+
+
+ {comment.content}
+
+
+
+ {isAuthenticated && (
+ {
+ setReplyingTo(comment);
+ document.querySelector(".comment-input")?.focus();
+ }}
+ style={{ cursor: "pointer", color: "#666" }}
+ >
+ 답글 달기
+
+ )}
+
+ {comment.replyCount > 0 && (
+ handleToggleReplies(comment.commentId)}
+ style={{
+ cursor: "pointer",
+ color: "#f6910b",
+ fontWeight: "bold",
+ }}
+ >
+ {isExpanded
+ ? "답글 숨기기"
+ : `답글 ${comment.replyCount}개 보기`}
+
+ )}
+
+
+
+ {isExpanded && (
+
+ {repliesList.map((reply) => (
+
+
+ ↳ {reply.userId}
+
+
+ {reply.content}
+
+
+ {formatDate(reply.createdAt)}
+
+
+ ))}
+
+ {repliesInfo?.hasMore && (
+
+ )}
+
+ )}
+
+ );
+ };
+
+ if (loading) return 로딩 중...
;
+ if (!currentPost)
+ return 게시글 없음
;
+
+ if (showUpdatePage) {
+ return (
+ setShowUpdatePage(false)}
+ onUpdate={handlePostUpdate}
+ />
+ );
+ }
+
+ return (
+
+
+
+
navigate("/communitypage")}
+ style={{
+ cursor: "pointer",
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ marginRight: "5px",
+ padding: "5px",
+ minWidth: "60px",
+ }}
+ >
+
+ 뒤로
+
+
+
+
+

+
{currentPost.category}
+
+
+
+
+
{currentPost.title}
+ {isAuthenticated && (
+
+
+
+
+ )}
+
+
+
+
작성자
+
+ {currentPost.author || currentPost.userId}
+
+
작성일
+
+ {formatDate(currentPost.createdAt)}
+
+
+
+
+
{currentPost.content}
+
+
+ {/* 댓글 섹션 */}
+
+
+
댓글
+
+
+
+ {comments && comments.length > 0 ? (
+ comments.map((comment) => renderCommentItem(comment))
+ ) : (
+
+ 아직 댓글이 없습니다.
+
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/src/screens/CommunityContent/sections/Communitycontentpage/UpdatePost/UpdatePost.jsx b/src/screens/CommunityContent/sections/Communitycontentpage/UpdatePost/UpdatePost.jsx
new file mode 100644
index 0000000..b6d354d
--- /dev/null
+++ b/src/screens/CommunityContent/sections/Communitycontentpage/UpdatePost/UpdatePost.jsx
@@ -0,0 +1,171 @@
+import React, { useState } from "react";
+import "../style.css";
+
+function UpdatePost({ post, onClose, onUpdate }) {
+ const [title, setTitle] = useState(post?.title || "");
+ const [content, setContent] = useState(post?.content || "");
+ const [category, setCategory] = useState(post?.category || "나눔");
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (!title.trim() || !content.trim()) {
+ alert("제목과 내용을 모두 입력해주세요.");
+ return;
+ }
+ const updatedData = {
+ title,
+ content,
+ category,
+ };
+ onUpdate(updatedData);
+ };
+
+ return (
+
+
+ {/* 헤더 */}
+
+
+ 게시글 수정
+
+
+
+
+
+
+ );
+}
+
+export default UpdatePost;
diff --git a/src/screens/CommunityContent/sections/Communitycontentpage/index.js b/src/screens/CommunityContent/sections/Communitycontentpage/index.js
new file mode 100644
index 0000000..1002677
--- /dev/null
+++ b/src/screens/CommunityContent/sections/Communitycontentpage/index.js
@@ -0,0 +1 @@
+export { Communitycontentpage } from "./Communitycontentpage";
diff --git a/src/screens/CommunityContent/sections/Communitycontentpage/style.css b/src/screens/CommunityContent/sections/Communitycontentpage/style.css
new file mode 100644
index 0000000..8de861e
--- /dev/null
+++ b/src/screens/CommunityContent/sections/Communitycontentpage/style.css
@@ -0,0 +1,399 @@
+.communitycontentpage {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 10px;
+ padding: 10px 64px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+/* Removed unused CSS classes */
+/*
+.communitycontentpage .communitycontentpage-wrapper {
+ align-items: flex-start;
+ align-self: stretch;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-color: #000000;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .text-wrapper-5 {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin-top: -1.00px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+*/
+
+.communitycontentpage .div-3 {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 10px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .content-category {
+ align-items: flex-start;
+ background-color: #0000001a;
+ border-radius: 16px;
+ display: inline-flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ padding: 0px 20px;
+ position: relative;
+}
+
+.communitycontentpage .category-icon {
+ aspect-ratio: 1;
+ height: 54px;
+ position: relative;
+ width: 54px;
+}
+
+.communitycontentpage .content-categorytext {
+ align-items: center;
+ color: #00000099;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin-top: -1px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitycontentpage .content-header {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ padding: 0px 20px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .content-headertext {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 36px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.72px;
+ line-height: 43.2px;
+ margin-top: -1px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitycontentpage .navbar {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ padding: 0px 25px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .text-wrapper-6 {
+ align-items: center;
+ color: #00000066;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 12px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.24px;
+ line-height: 14.4px;
+ margin-top: -1px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitycontentpage .content {
+ align-items: flex-start;
+ align-self: stretch;
+ background-color: #f9f9f9;
+ border-radius: 12px;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ min-height: 200px;
+ padding: 20px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .content-text {
+ align-self: stretch;
+ color: #000000;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ letter-spacing: -0.08px;
+ line-height: 24px;
+ margin: 0;
+ position: relative;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.communitycontentpage .comments-section {
+ align-self: stretch;
+ background-color: #f5f5f5;
+ border-radius: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ padding: 20px;
+ width: 100%;
+}
+
+.communitycontentpage .comments-header {
+ border-bottom: 1px solid #e0e0e0;
+ padding-bottom: 10px;
+}
+
+.communitycontentpage .comments-title {
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+}
+
+.communitycontentpage .comments-list {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.communitycontentpage .comment-item {
+ background-color: #ffffff;
+ border-radius: 10px;
+ padding: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.communitycontentpage .comment-author {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 700;
+ color: #f6910b;
+}
+
+.communitycontentpage .comment-content {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 500;
+ color: #000000;
+ line-height: 1.5;
+}
+
+.communitycontentpage .comment-date {
+ font-family: "Inter", Helvetica;
+ font-size: 12px;
+ font-weight: 500;
+ color: #999999;
+}
+
+.communitycontentpage .comment-form {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-top: 10px;
+}
+
+.communitycontentpage .comment-input {
+ padding: 12px;
+ border: 1px solid #e0e0e0;
+ border-radius: 10px;
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ resize: vertical;
+ transition: border-color 0.2s;
+}
+
+.communitycontentpage .comment-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.communitycontentpage .comment-input:disabled {
+ background-color: #f5f5f5;
+ cursor: not-allowed;
+}
+
+.communitycontentpage .comment-submit-btn {
+ align-self: flex-end;
+ padding: 10px 20px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 10px;
+ color: #ffffff;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 700;
+ transition: background-color 0.2s;
+}
+
+.communitycontentpage .comment-submit-btn:hover:not(:disabled) {
+ background-color: #e58209;
+}
+
+.communitycontentpage .comment-submit-btn:disabled {
+ background-color: #cccccc;
+ cursor: not-allowed;
+}
+
+.communitycontentpage .div-4 {
+ align-items: center;
+ align-self: stretch;
+ border-color: #000000;
+ border-top-style: solid;
+ border-top-width: 1px;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ padding: 20px 10px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .related-posts-header {
+ align-self: stretch;
+ padding: 10px 0;
+ border-bottom: 1px solid #e0e0e0;
+ margin-bottom: 10px;
+}
+
+.communitycontentpage .related-posts-title {
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+ text-align: center;
+}
+
+.communitycontentpage .design-component-instance-node {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1px !important;
+ margin-right: -1px !important;
+ margin-top: -1px !important;
+ top: unset !important;
+}
+
+.communitycontentpage .communitypage-2 {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1px !important;
+ margin-right: -1px !important;
+ top: unset !important;
+}
+
+.communitycontentpage .content-header {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ justify-content: space-between;
+ gap: 15px;
+ padding: 0px 20px 15px 20px;
+ border-bottom: 1px solid #e0e0e0;
+ margin-bottom: 10px;
+ position: relative;
+ width: 100%;
+}
+
+.communitycontentpage .content-headertext {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 32px;
+ font-weight: 700;
+ letter-spacing: -0.72px;
+ line-height: 1.3;
+ margin: 0;
+ position: relative;
+ justify-content: flex-start;
+ text-align: left;
+ flex: 1;
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+.communitycontentpage .content-header button {
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-radius: 8px;
+ padding: 8px 16px;
+
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 600;
+ color: #555555;
+
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+}
+
+.communitycontentpage .content-header button:hover {
+ background-color: #f5f5f5;
+ border-color: #bbbbbb;
+ color: #333333;
+ transform: translateY(-1px);
+}
+
+.communitycontentpage .content-header button:last-child:hover {
+ background-color: #fff5f5;
+ border-color: #ff4d4f;
+ color: #ff4d4f;
+}
diff --git a/src/screens/CommunityContent/sections/HeaderWrapper/HeaderWrapper.jsx b/src/screens/CommunityContent/sections/HeaderWrapper/HeaderWrapper.jsx
new file mode 100644
index 0000000..d30f954
--- /dev/null
+++ b/src/screens/CommunityContent/sections/HeaderWrapper/HeaderWrapper.jsx
@@ -0,0 +1,62 @@
+import React, { useState } from "react";
+import { Link } from "react-router-dom";
+import { LoginPopup } from "../../../../components/LoginPopup";
+import { SignupPopup } from "../../../../components/SignupPopup";
+import "./style.css";
+
+export const HeaderWrapper = () => {
+ const [showLoginPopup, setShowLoginPopup] = useState(false);
+ const [showSignupPopup, setShowSignupPopup] = useState(false);
+
+ const handleSwitchToSignup = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(true);
+ };
+
+ const handleSwitchToLogin = () => {
+ setShowSignupPopup(false);
+ setShowLoginPopup(true);
+ };
+
+ return (
+
+
+
+

+
+
+
+
+
+ 알고리즘
+
+ 셰프
+
+
+
+
+
+
+
+ {showLoginPopup && (
+ setShowLoginPopup(false)}
+ onSwitchToSignup={handleSwitchToSignup}
+ />
+ )}
+
+ {showSignupPopup && (
+ setShowSignupPopup(false)}
+ onSwitchToLogin={handleSwitchToLogin}
+ />
+ )}
+
+ );
+};
diff --git a/src/screens/CommunityContent/sections/HeaderWrapper/index.js b/src/screens/CommunityContent/sections/HeaderWrapper/index.js
new file mode 100644
index 0000000..2f50715
--- /dev/null
+++ b/src/screens/CommunityContent/sections/HeaderWrapper/index.js
@@ -0,0 +1 @@
+export { HeaderWrapper } from "./HeaderWrapper";
diff --git a/src/screens/CommunityContent/sections/HeaderWrapper/style.css b/src/screens/CommunityContent/sections/HeaderWrapper/style.css
new file mode 100644
index 0000000..749abf8
--- /dev/null
+++ b/src/screens/CommunityContent/sections/HeaderWrapper/style.css
@@ -0,0 +1,105 @@
+.header-wrapper {
+ align-items: center;
+ align-self: stretch;
+ background-color: transparent;
+ display: flex;
+ gap: 10px;
+ height: 120px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.header-wrapper .icon-image-wrapper {
+ align-items: center;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ justify-content: flex-end;
+ overflow: hidden;
+ position: relative;
+ height: 100%;
+}
+
+.header-wrapper .icon-image-2 {
+ height: 80px;
+ width: auto;
+ display: block;
+ position: relative;
+}
+
+.header-wrapper .p {
+ align-items: center;
+ color: transparent;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 1;
+ position: relative;
+ text-align: center;
+ width: 271px;
+ height: 100%;
+}
+
+.header-wrapper .text-wrapper-3 {
+ color: #000000;
+ letter-spacing: -0.46px;
+}
+
+.header-wrapper .text-wrapper-4 {
+ color: #f6910b;
+ letter-spacing: -0.46px;
+}
+
+.header-wrapper .login-button-wrapper {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 10px;
+ position: relative;
+}
+
+.header-wrapper .login-text-wrapper {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+}
+
+.header-wrapper .login-text-wrapper:hover {
+ transform: scale(1.05);
+}
+
+.header-wrapper .login-text-2 {
+ align-items: center;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 600;
+ height: 24px;
+ justify-content: center;
+ left: calc(50.00% - 33px);
+ letter-spacing: -0.40px;
+ line-height: 24.0px;
+ position: absolute;
+ text-align: center;
+ top: calc(50.00% - 12px);
+ width: 66px;
+}
diff --git a/src/screens/CommunityContent/style.css b/src/screens/CommunityContent/style.css
new file mode 100644
index 0000000..ca2c8b3
--- /dev/null
+++ b/src/screens/CommunityContent/style.css
@@ -0,0 +1,9 @@
+.community-content {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 1080px;
+ position: relative;
+ width: 100%;
+}
diff --git a/src/screens/CommunityPage/CommunityPage.jsx b/src/screens/CommunityPage/CommunityPage.jsx
new file mode 100644
index 0000000..fc190e3
--- /dev/null
+++ b/src/screens/CommunityPage/CommunityPage.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import { Communitypage } from "./sections/Communitypage";
+import "./style.css";
+
+export const CommunityPage = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/screens/CommunityPage/index.js b/src/screens/CommunityPage/index.js
new file mode 100644
index 0000000..b50944e
--- /dev/null
+++ b/src/screens/CommunityPage/index.js
@@ -0,0 +1 @@
+export { CommunityPage } from "./CommunityPage";
diff --git a/src/screens/CommunityPage/sections/Communitypage/Communitypage.jsx b/src/screens/CommunityPage/sections/Communitypage/Communitypage.jsx
new file mode 100644
index 0000000..13f31a7
--- /dev/null
+++ b/src/screens/CommunityPage/sections/Communitypage/Communitypage.jsx
@@ -0,0 +1,153 @@
+import React, { useState, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { useAuth } from "../../../../contexts/AuthContext";
+import { CommunitypageWrapper } from "../../../../components/CommunitypageWrapper";
+import { PageButton } from "../../../../components/PageButton";
+import "./style.css";
+import { getPostList } from "../../../../api/Community/community";
+
+const CATEGORIES = ["전체", "나눔", "교환", "판매", "요청"];
+const POSTS_PER_PAGE = 20;
+export const Communitypage = () => {
+ const navigate = useNavigate();
+ const { isAuthenticated } = useAuth();
+ const [currentPage, setCurrentPage] = useState(0);
+ const [allPosts, setAllPosts] = useState([]);
+ const [category, setCategory] = useState("전체");
+
+ useEffect(() => {
+ setCurrentPage(0);
+ }, category);
+ useEffect(() => {
+ if (!isAuthenticated) {
+ alert("로그인 필요");
+ setAllPosts([]);
+ return;
+ }
+ const fetchData = async () => {
+ const selectedCategory = category === "전체" ? null : category;
+ try {
+ const response = await getPostList(
+ currentPage,
+ POSTS_PER_PAGE,
+ selectedCategory
+ );
+ console.log(response);
+ if (response && response.posts.length > 0) {
+ console.log("첫 번째 게시글:", response.posts[0]);
+ setAllPosts(response.posts);
+ } else {
+ alert("게시글 가져오기 실패");
+ }
+ } catch (error) {
+ console.error("게시글 가쟈오는 도중 오류 발생", error);
+ }
+ };
+ fetchData();
+ }, [category, isAuthenticated]);
+
+ const handleCreatePost = () => {
+ if (!isAuthenticated) {
+ alert("로그인이 필요합니다.");
+ return;
+ }
+ navigate("/createpost");
+ };
+
+ const totalPages = Math.min(Math.ceil(allPosts.length / POSTS_PER_PAGE), 5);
+
+ const handlePageChange = (page) => {
+ if (page >= 1 && page <= totalPages) {
+ setCurrentPage(page);
+ }
+ };
+
+ const handleCategory = (e) => {
+ setCategory(e.target.value);
+ };
+ return (
+
+
+
+
+
+
+
+
+ {allPosts.map((post, index) => (
+
+ ))}
+
+
+
+
+
+
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
+
handlePageChange(page)}
+ />
+ ))}
+
+
+
+
+ );
+};
diff --git a/src/screens/CommunityPage/sections/Communitypage/index.js b/src/screens/CommunityPage/sections/Communitypage/index.js
new file mode 100644
index 0000000..35d0d64
--- /dev/null
+++ b/src/screens/CommunityPage/sections/Communitypage/index.js
@@ -0,0 +1 @@
+export { Communitypage } from "./Communitypage";
diff --git a/src/screens/CommunityPage/sections/Communitypage/style.css b/src/screens/CommunityPage/sections/Communitypage/style.css
new file mode 100644
index 0000000..31a3d7b
--- /dev/null
+++ b/src/screens/CommunityPage/sections/Communitypage/style.css
@@ -0,0 +1,245 @@
+.communitypage {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 10px;
+ padding: 10px 64px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.communitypage .communitypage-header {
+ align-items: flex-start;
+ align-self: stretch;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-color: #000000;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+}
+
+.communitypage .div {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin-top: -1px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitypage .div-2 {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 10px;
+ max-height: 800px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+}
+
+.communitypage .communitypage-postheader {
+ align-self: stretch !important;
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1px !important;
+ margin-right: -1px !important;
+ margin-top: -1px !important;
+ top: unset !important;
+ width: 100% !important;
+}
+
+.communitypage .communitypage-instance {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1px !important;
+ margin-right: -1px !important;
+ top: unset !important;
+}
+
+.communitypage .communitypage-postheader-instance {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1px !important;
+ margin-right: -1px !important;
+ overflow: hidden !important;
+ top: unset !important;
+}
+
+.communitypage .communitypage-wrapper-2 {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 10px 0;
+ position: relative;
+ width: 100%;
+}
+
+.communitypage .communitypage-wrapper-3 {
+ align-items: center;
+ background-color: #f5900b;
+ border: none;
+ border-radius: 16px;
+ cursor: pointer;
+ display: inline-flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ justify-content: center;
+ overflow: hidden;
+ padding: 15px 20px;
+ position: relative;
+ text-decoration: none;
+ transition: background-color 0.2s, transform 0.2s;
+}
+
+.communitypage .communitypage-wrapper-3:hover {
+ background-color: #e58209;
+ transform: translateY(-2px);
+}
+
+.communitypage .text-wrapper-2 {
+ align-items: center;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.32px;
+ line-height: 19.2px;
+ margin-top: -1px;
+ position: relative;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.communitypage .communitypage-nav {
+ align-items: center;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ justify-content: center;
+ padding: 20px 10px;
+ position: relative;
+ width: 317px;
+}
+
+.communitypage .page-nav-arrow {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 5px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: opacity 0.2s;
+}
+
+.communitypage .page-nav-arrow:disabled {
+ opacity: 0.3;
+ cursor: not-allowed;
+}
+
+.communitypage .page-nav-arrow:hover:not(:disabled) {
+ opacity: 0.7;
+}
+
+.communitypage .img {
+ height: 20px;
+ position: relative;
+ width: 20px;
+}
+
+.communitypage .page-button-container {
+ left: unset !important;
+ top: unset !important;
+}
+
+@media (max-width: 768px) {
+ .communitypage {
+ padding: 10px 20px;
+ }
+
+ .communitypage .div {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+
+ .communitypage .text-wrapper-2 {
+ font-size: 14px;
+ }
+}
+
+.communitypage-category-select {
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 15px;
+ font-weight: 500;
+ color: #333;
+
+ background-color: #ffffff;
+ background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23333333%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
+ background-repeat: no-repeat;
+ background-position: right 12px center;
+ background-size: 10px;
+
+ padding: 10px 32px 10px 16px;
+ border: 1px solid #ddd;
+ border-radius: 12px;
+
+ cursor: pointer;
+ outline: none;
+ transition: all 0.2s ease;
+ min-width: 120px;
+}
+
+.communitypage-category-select:hover {
+ border-color: #f5900b;
+}
+
+.communitypage-category-select:focus {
+ border-color: #f5900b;
+ box-shadow: 0 0 0 3px rgba(245, 144, 11, 0.1);
+}
+
+.communitypage-filter-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 15px;
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+.filter-label {
+ font-size: 16px;
+ font-weight: 700;
+ color: #333;
+ min-width: fit-content;
+}
diff --git a/src/screens/CommunityPage/sections/Header/Header.jsx b/src/screens/CommunityPage/sections/Header/Header.jsx
new file mode 100644
index 0000000..0129aa7
--- /dev/null
+++ b/src/screens/CommunityPage/sections/Header/Header.jsx
@@ -0,0 +1,101 @@
+import React, { useState } from "react";
+import { Link } from "react-router-dom";
+import { useAuth } from "../../../../contexts/AuthContext";
+import { LoginPopup } from "../../../../components/LoginPopup";
+import { SignupPopup } from "../../../../components/SignupPopup";
+import { PreferencesPopup } from "../../../../components/PreferencesPopup";
+import "./style.css";
+
+export const Header = () => {
+ const { user, isAuthenticated, logout } = useAuth();
+ const [showLoginPopup, setShowLoginPopup] = useState(false);
+ const [showSignupPopup, setShowSignupPopup] = useState(false);
+ const [showPreferencesPopup, setShowPreferencesPopup] = useState(false);
+ const [userData, setUserData] = useState(null);
+
+ const handleSwitchToSignup = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(true);
+ };
+
+ const handleSwitchToLogin = () => {
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setShowLoginPopup(true);
+ };
+
+ const handleSwitchToPreferences = (data) => {
+ setUserData(data);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(true);
+ };
+
+ const handleCloseAll = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setUserData(null);
+ };
+
+ return (
+
+ );
+};
diff --git a/src/screens/CommunityPage/sections/Header/index.js b/src/screens/CommunityPage/sections/Header/index.js
new file mode 100644
index 0000000..c940126
--- /dev/null
+++ b/src/screens/CommunityPage/sections/Header/index.js
@@ -0,0 +1 @@
+export { Header } from "./Header";
diff --git a/src/screens/CommunityPage/sections/Header/style.css b/src/screens/CommunityPage/sections/Header/style.css
new file mode 100644
index 0000000..6a13ba6
--- /dev/null
+++ b/src/screens/CommunityPage/sections/Header/style.css
@@ -0,0 +1,217 @@
+.header {
+ align-items: center;
+ background-color: transparent;
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ height: 120px;
+ padding: 10px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.header .header-left-section {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ justify-content: flex-start;
+}
+
+.header .icon-image {
+ height: 80px;
+ width: auto;
+ display: block;
+ position: relative;
+}
+
+.header .algorithm-label-link {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.header .algorithm-label {
+ align-items: center;
+ color: transparent;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.96px;
+ line-height: 1;
+ position: relative;
+ text-align: center;
+ margin: 0;
+}
+
+.header .text-wrapper {
+ color: #000000;
+ letter-spacing: -0.46px;
+}
+
+.header .span {
+ color: #f6910b;
+ letter-spacing: -0.46px;
+}
+
+.header .header-right-section {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 10px;
+}
+
+.header .mypage-button {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.header .mypage-button:hover {
+ transform: scale(1.05);
+}
+
+.header .mypage-text {
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ letter-spacing: -0.32px;
+ line-height: 19.2px;
+ text-align: center;
+}
+
+.header .login-button {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 120px;
+ transition: transform 0.2s;
+}
+
+.header .login-button:hover {
+ transform: scale(1.05);
+}
+
+.header .login-text {
+ align-items: center;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 600;
+ height: 24px;
+ justify-content: center;
+ left: calc(50.00% - 33px);
+ letter-spacing: -0.40px;
+ line-height: 24.0px;
+ position: absolute;
+ text-align: center;
+ top: calc(50.00% - 12px);
+ width: 66px;
+}
+
+.header .user-info-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.header .user-name-display {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ padding: 8px 16px;
+ background-color: #f6910b1a;
+ border-radius: 20px;
+}
+
+.header .logout-button {
+ background-image: url(https://c.animaapp.com/sjWITF5i/img/loginvector-3.svg);
+ background-size: 100% 100%;
+ border: none;
+ border-radius: 30px;
+ cursor: pointer;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ width: 100px;
+ transition: transform 0.2s;
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.header .logout-button:hover {
+ transform: scale(1.05);
+}
+
+@media (max-width: 768px) {
+ .header {
+ padding: 10px 5px;
+ height: auto;
+ min-height: 80px;
+ }
+
+ .header .icon-image {
+ height: 50px;
+ }
+
+ .header .algorithm-label {
+ font-size: 24px;
+ }
+
+ .header .mypage-button,
+ .header .login-button,
+ .header .logout-button {
+ width: 90px;
+ height: 32px;
+ }
+
+ .header .mypage-text,
+ .header .login-text {
+ font-size: 12px;
+ }
+
+ .header .user-name-display {
+ font-size: 12px;
+ padding: 4px 10px;
+ }
+}
+
+@media (max-width: 480px) {
+ .header .algorithm-label {
+ font-size: 20px;
+ }
+
+ .header .header-right-section {
+ gap: 5px;
+ padding: 5px;
+ }
+}
diff --git a/src/screens/CommunityPage/style.css b/src/screens/CommunityPage/style.css
new file mode 100644
index 0000000..20ece7e
--- /dev/null
+++ b/src/screens/CommunityPage/style.css
@@ -0,0 +1,23 @@
+.community-page {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 1080px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+@media (max-width: 1280px) {
+ .community-page {
+ width: 100%;
+ }
+}
+
+@media (max-width: 768px) {
+ .community-page {
+ min-height: auto;
+ }
+}
diff --git a/src/screens/CreatePostPage/CreatePostPage.jsx b/src/screens/CreatePostPage/CreatePostPage.jsx
new file mode 100644
index 0000000..f7baff9
--- /dev/null
+++ b/src/screens/CreatePostPage/CreatePostPage.jsx
@@ -0,0 +1,112 @@
+import React, { useState, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { useAuth } from "../../contexts/AuthContext";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import "./style.css";
+import { createPost } from "../../api/Community/community";
+
+export const CreatePostPage = () => {
+ const navigate = useNavigate();
+ const { isAuthenticated } = useAuth();
+ const [title, setTitle] = useState("");
+ const [content, setContent] = useState("");
+ const [category, setCategory] = useState("나눔");
+
+ useEffect(() => {
+ if (!isAuthenticated) {
+ alert("로그인이 필요합니다.");
+ navigate("/communitypage");
+ }
+ }, [isAuthenticated, navigate]);
+
+ const handleCategory = (e) => {
+ setCategory(e.target.value);
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!isAuthenticated) {
+ alert("로그인이 필요합니다.");
+ return;
+ }
+ const newPost = {
+ category: category,
+ title: title,
+ content: content,
+ };
+
+ try {
+ const response = await createPost(newPost);
+ if (response) {
+ console.log("게시글 작성:", newPost);
+ alert(response.message);
+ navigate("/communitypage");
+ } else {
+ alert("게시글 작성 실패");
+ }
+ } catch (error) {
+ console.error("게시글 작성 중 에러:", error);
+ alert("서버 오류가 발생했습니다.");
+ }
+ };
+
+ return (
+
+
+
+
+
+
새 글 작성
+
+ 재료 나눔 게시판에 글을 작성하세요
+
+
+
+
+
+
+ );
+};
diff --git a/src/screens/CreatePostPage/index.js b/src/screens/CreatePostPage/index.js
new file mode 100644
index 0000000..b99e657
--- /dev/null
+++ b/src/screens/CreatePostPage/index.js
@@ -0,0 +1 @@
+export { CreatePostPage } from "./CreatePostPage";
diff --git a/src/screens/CreatePostPage/style.css b/src/screens/CreatePostPage/style.css
new file mode 100644
index 0000000..88bf770
--- /dev/null
+++ b/src/screens/CreatePostPage/style.css
@@ -0,0 +1,180 @@
+.create-post-page {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ position: relative;
+ width: 100%;
+}
+
+.create-post-page .create-post-header {
+ align-self: stretch !important;
+ width: 100% !important;
+ max-width: 1280px !important;
+ margin: 0 auto !important;
+}
+
+.create-post-content {
+ align-items: flex-start;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: 30px;
+ padding: 20px 64px;
+ position: relative;
+ width: 100%;
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+.create-post-title-section {
+ align-items: flex-start;
+ align-self: stretch;
+ border-bottom: 1px solid #000000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding-bottom: 20px;
+ width: 100%;
+}
+
+.create-post-title {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin: 0;
+}
+
+.create-post-subtitle {
+ color: #0000008c;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ letter-spacing: -0.10px;
+ line-height: 29.0px;
+ margin: 0;
+}
+
+.create-post-form {
+ align-self: stretch;
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ width: 100%;
+}
+
+.create-post-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.create-post-label {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.create-post-input {
+ padding: 12px 20px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ transition: border-color 0.2s;
+}
+
+.create-post-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.create-post-textarea {
+ padding: 12px 20px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ resize: vertical;
+ transition: border-color 0.2s;
+}
+
+.create-post-textarea:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.create-post-footer {
+ display: flex;
+ gap: 15px;
+ justify-content: flex-end;
+ padding-top: 20px;
+}
+
+.create-post-cancel-btn {
+ padding: 12px 30px;
+ background-color: #e0e0e0;
+ border: none;
+ border-radius: 12px;
+ color: #666666;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ text-decoration: none;
+ transition: background-color 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.create-post-cancel-btn:hover {
+ background-color: #d0d0d0;
+}
+
+.create-post-submit-btn {
+ padding: 12px 30px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 12px;
+ color: #ffffff;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ transition: background-color 0.2s;
+}
+
+.create-post-submit-btn:hover {
+ background-color: #e58209;
+}
+
+@media (max-width: 768px) {
+ .create-post-content {
+ padding: 20px 20px;
+ }
+
+ .create-post-title {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+
+ .create-post-subtitle {
+ font-size: 16px;
+ line-height: 23.2px;
+ }
+
+ .create-post-footer {
+ flex-direction: column;
+ }
+
+ .create-post-cancel-btn,
+ .create-post-submit-btn {
+ width: 100%;
+ }
+}
diff --git a/src/screens/Desktop/Desktop.jsx b/src/screens/Desktop/Desktop.jsx
new file mode 100644
index 0000000..599f6c4
--- /dev/null
+++ b/src/screens/Desktop/Desktop.jsx
@@ -0,0 +1,73 @@
+import React, { useState, useEffect } from "react";
+import { useLocation } from "react-router-dom"; // Import useLocation
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import { Community } from "./sections/Community";
+import { Footer } from "./sections/Footer";
+import { IngredientInventory } from "./sections/IngredientInventory";
+import { MenuInventory } from "./sections/MenuInventory";
+import "./style.css";
+
+const INITIAL_INGREDIENTS = [
+ { id: 1, name: "양파", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-1.png", expiryDays: 5 },
+ { id: 2, name: "닭고기", category: "육류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 3 },
+ { id: 3, name: "고추장", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 30 },
+ { id: 4, name: "고춧가루", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 60 },
+ { id: 5, name: "양배추", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 7 },
+ { id: 6, name: "스테비아", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 90 },
+ { id: 7, name: "마늘", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 14 },
+];
+
+export const Desktop = () => {
+ const [ingredients, setIngredients] = useState([]);
+ const location = useLocation(); // Initialize useLocation
+
+ useEffect(() => {
+ // This effect will run on mount and when location changes,
+ // ensuring fresh data when returning from AddIngredientPage
+ const storedIngredients = JSON.parse(localStorage.getItem("userIngredients") || "[]");
+ if (storedIngredients.length > 0) {
+ setIngredients(storedIngredients);
+ } else {
+ setIngredients(INITIAL_INGREDIENTS);
+ localStorage.setItem("userIngredients", JSON.stringify(INITIAL_INGREDIENTS));
+ }
+ }, [location.pathname]); // Re-run when pathname changes
+
+ const handleAddIngredient = (newIngredient) => {
+ setIngredients((prevIngredients) => {
+ const updatedIngredients = [...prevIngredients, newIngredient];
+ localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients));
+ // TODO: Backend Integration: Send newIngredient to backend
+ console.log("Added ingredient to mock DB:", newIngredient);
+ return updatedIngredients;
+ });
+ };
+
+ const handleAddMultipleIngredients = (newIngredients) => {
+ setIngredients((prevIngredients) => {
+ const updatedIngredients = [...prevIngredients, ...newIngredients];
+ localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients));
+ // TODO: Backend Integration: Send newIngredients to backend
+ console.log("Added multiple ingredients to mock DB:", newIngredients);
+ return updatedIngredients;
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/src/screens/Desktop/index.js b/src/screens/Desktop/index.js
new file mode 100644
index 0000000..4d75c1e
--- /dev/null
+++ b/src/screens/Desktop/index.js
@@ -0,0 +1 @@
+export { Desktop } from "./Desktop";
diff --git a/src/screens/Desktop/sections/Community/Community.jsx b/src/screens/Desktop/sections/Community/Community.jsx
new file mode 100644
index 0000000..0770c76
--- /dev/null
+++ b/src/screens/Desktop/sections/Community/Community.jsx
@@ -0,0 +1,79 @@
+import React from "react";
+import { Link } from "react-router-dom";
+import { CommunitypageWrapper } from "../../../../components/CommunitypageWrapper";
+import "./style.css";
+
+const MOCK_POSTS = [
+ // TODO: Backend Integration: Replace with API call to fetch recent community posts
+ {
+ id: 1,
+ title: "돼지고기 100g 나눔합니다.",
+ author: "test3User",
+ date: "2025-11-03",
+ category: "나눔",
+ content: "돼지고기를 너무 많이 샀네요 남는 돼지고기 나눔해요",
+ },
+ {
+ id: 2,
+ title: "양파 2개 나눔해요",
+ author: "user123",
+ date: "2025-11-02",
+ category: "나눔",
+ content: "양파 2개 필요하신 분 가져가세요",
+ },
+ {
+ id: 3,
+ title: "닭고기 500g 나눔",
+ author: "foodlover",
+ date: "2025-11-01",
+ category: "나눔",
+ content: "신선한 닭고기 나눔합니다",
+ },
+ {
+ id: 4,
+ title: "고추장 새것 나눔합니다",
+ author: "chef99",
+ date: "2025-10-31",
+ category: "나눔",
+ content: "개봉 안한 고추장 드립니다",
+ },
+ {
+ id: 5,
+ title: "양배추 한통 가져가세요",
+ author: "veggie_fan",
+ date: "2025-10-30",
+ category: "나눔",
+ content: "양배추 한통 나눔해요",
+ },
+];
+
+export const Community = () => {
+ return (
+
+
+
재료 나눔 게시판
+
+
+
+ {MOCK_POSTS.map((post, index) => (
+
+ ))}
+
+
+ );
+};
diff --git a/src/screens/Desktop/sections/Community/index.js b/src/screens/Desktop/sections/Community/index.js
new file mode 100644
index 0000000..95a905a
--- /dev/null
+++ b/src/screens/Desktop/sections/Community/index.js
@@ -0,0 +1 @@
+export { Community } from "./Community";
diff --git a/src/screens/Desktop/sections/Community/style.css b/src/screens/Desktop/sections/Community/style.css
new file mode 100644
index 0000000..2e024bd
--- /dev/null
+++ b/src/screens/Desktop/sections/Community/style.css
@@ -0,0 +1,83 @@
+.community {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px 64px;
+ position: relative;
+ width: 100%;
+}
+
+.community .community-header {
+ align-self: stretch;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-color: #000000b2;
+ display: flex;
+ flex: 0 0 auto;
+ padding: 10px 0;
+ position: relative;
+ width: 100%;
+}
+
+.community .community-header-2 {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ justify-content: flex-start;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ white-space: nowrap;
+}
+
+@media (max-width: 768px) {
+ .community .community-header-2 {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+}
+
+.community .community-contents {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ max-height: 300px;
+ overflow: hidden;
+ padding: 10px 20px;
+ position: relative;
+ width: 100%;
+}
+
+.community .communitypage-3 {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1.00px !important;
+ margin-right: -21.00px !important;
+ margin-top: -1.00px !important;
+ top: unset !important;
+}
+
+.community .communitypage-4 {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-left: -1.00px !important;
+ margin-right: -21.00px !important;
+ top: unset !important;
+}
+
+.community .communitypage-5 {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ margin-bottom: -1.00px !important;
+ margin-left: -1.00px !important;
+ margin-right: -21.00px !important;
+ top: unset !important;
+}
diff --git a/src/screens/Desktop/sections/Footer/Footer.jsx b/src/screens/Desktop/sections/Footer/Footer.jsx
new file mode 100644
index 0000000..c6a7cd0
--- /dev/null
+++ b/src/screens/Desktop/sections/Footer/Footer.jsx
@@ -0,0 +1,42 @@
+import React from "react";
+import "./style.css";
+
+export const Footer = () => {
+ return (
+
+ );
+};
diff --git a/src/screens/Desktop/sections/Footer/index.js b/src/screens/Desktop/sections/Footer/index.js
new file mode 100644
index 0000000..e5ea0e5
--- /dev/null
+++ b/src/screens/Desktop/sections/Footer/index.js
@@ -0,0 +1 @@
+export { Footer } from "./Footer";
diff --git a/src/screens/Desktop/sections/Footer/style.css b/src/screens/Desktop/sections/Footer/style.css
new file mode 100644
index 0000000..9ff750f
--- /dev/null
+++ b/src/screens/Desktop/sections/Footer/style.css
@@ -0,0 +1,156 @@
+.footer {
+ align-items: center;
+ align-self: stretch;
+ background-color: transparent;
+ border-radius: 20px;
+ display: flex;
+ gap: 96px;
+ min-height: 266px;
+ justify-content: space-around;
+ padding: 20px 64px;
+ position: relative;
+ width: 100%;
+ margin-top: 40px;
+}
+
+.footer .footer-container {
+ align-items: flex-start;
+ align-self: stretch;
+ border-color: #000000b2;
+ border-top-style: solid;
+ border-top-width: 1px;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 120px;
+ padding: 20px 0px;
+ position: relative;
+}
+
+.footer .footer-left-content {
+ align-items: flex-start;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 10px;
+ position: relative;
+}
+
+.footer .company-container {
+ align-items: flex-start;
+ display: inline-flex;
+ flex: 0 0 auto;
+ gap: 8px;
+ position: relative;
+}
+
+.footer .company-text {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 24px;
+ font-weight: 600;
+ justify-content: center;
+ letter-spacing: -0.48px;
+ line-height: 34.8px;
+ margin-top: -1.00px;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+.footer .developer-container {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ justify-content: center;
+ padding: 0px 10px;
+ position: relative;
+ width: 100%;
+}
+
+.footer .developer-text {
+ align-items: flex-start;
+ color: #0000008c;
+ display: flex;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ justify-content: flex-start;
+ letter-spacing: -0.08px;
+ line-height: 23.2px;
+ margin-top: -1.00px;
+ position: relative;
+ text-align: left;
+}
+
+@media (max-width: 768px) {
+ .footer .developer-text {
+ font-size: 14px;
+ line-height: 20.3px;
+ }
+}
+
+.footer .footer-right-content {
+ align-items: flex-start;
+ display: inline-flex;
+ flex: 0 0 auto;
+ gap: 40px;
+ position: relative;
+}
+
+.footer .support-container {
+ align-items: flex-end;
+ align-self: stretch;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ justify-content: center;
+ position: relative;
+ width: 130px;
+}
+
+.footer .support-header {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 4px;
+ padding: 0px 0px 16px;
+ position: relative;
+ width: 100%;
+}
+
+.footer .support-text {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ justify-content: center;
+ letter-spacing: -0.08px;
+ line-height: 23.2px;
+ margin-top: -1.00px;
+ position: relative;
+ width: 63px;
+}
+
+.footer .text-wrapper-8 {
+ align-items: center;
+ align-self: stretch;
+ color: #0000008c;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.08px;
+ line-height: 23.2px;
+ position: relative;
+}
diff --git a/src/screens/Desktop/sections/HeaderInstanceWrapper/HeaderInstanceWrapper.jsx b/src/screens/Desktop/sections/HeaderInstanceWrapper/HeaderInstanceWrapper.jsx
new file mode 100644
index 0000000..737b9bb
--- /dev/null
+++ b/src/screens/Desktop/sections/HeaderInstanceWrapper/HeaderInstanceWrapper.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+import { DivWrapper } from "../../../../components/DivWrapper";
+import "./style.css";
+
+export const HeaderInstanceWrapper = () => {
+ return ;
+};
diff --git a/src/screens/Desktop/sections/HeaderInstanceWrapper/index.js b/src/screens/Desktop/sections/HeaderInstanceWrapper/index.js
new file mode 100644
index 0000000..05c0f61
--- /dev/null
+++ b/src/screens/Desktop/sections/HeaderInstanceWrapper/index.js
@@ -0,0 +1 @@
+export { HeaderInstanceWrapper } from "./HeaderInstanceWrapper";
diff --git a/src/screens/Desktop/sections/HeaderInstanceWrapper/style.css b/src/screens/Desktop/sections/HeaderInstanceWrapper/style.css
new file mode 100644
index 0000000..43b0cd3
--- /dev/null
+++ b/src/screens/Desktop/sections/HeaderInstanceWrapper/style.css
@@ -0,0 +1,7 @@
+.header-instance {
+ align-self: stretch !important;
+ background-color: transparent !important;
+ width: 100% !important;
+ max-width: 1280px !important;
+ margin: 0 auto !important;
+}
diff --git a/src/screens/Desktop/sections/IngredientInventory/IngredientInventory.jsx b/src/screens/Desktop/sections/IngredientInventory/IngredientInventory.jsx
new file mode 100644
index 0000000..328713b
--- /dev/null
+++ b/src/screens/Desktop/sections/IngredientInventory/IngredientInventory.jsx
@@ -0,0 +1,168 @@
+import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
+import { IngredientComponent } from "../../../../components/IngredientComponent";
+import { IngredientInventoryWrapper } from "../../../../components/IngredientInventoryWrapper";
+import { AddIngredientPopup } from "../../../../components/AddIngredientPopup";
+import "./style.css";
+import { useAuth } from "../../../../contexts/AuthContext";
+import {
+ filterFridgeIngredientsByCategory,
+ getFridgeIngredients,
+} from "../../../../api/fridgeApi";
+import ReceiptPopup from "../../../../components/AddIngredientPopup/ReceiptPopup";
+
+const CATEGORIES = [
+ "전체",
+ "육류",
+ "가공육",
+ "난류",
+ "수산물",
+ "가공수산물",
+ "건어물",
+ "젓갈",
+ "채소",
+ "버섯",
+ "유제품",
+ "두부류",
+ "콩가공품",
+ "콩",
+ "김치류",
+ "절임류",
+ "빵류",
+ "기타",
+ "가공식품",
+ "과일",
+ "조미료",
+ "향신료",
+ "곡류",
+ "견과",
+];
+const DEFAULT_IMAGE_URL =
+ "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png";
+
+export const IngredientInventory = () => {
+ const [selectedCategory, setSelectedCategory] = useState("전체");
+ const [showCategoryMenu, setShowCategoryMenu] = useState(false);
+ const [showAddPopup, setShowAddPopup] = useState(false);
+ const { isAuthenticated } = useAuth();
+ const [fridgeIngredients, setFridgeIngredients] = useState([]);
+
+ const fetchData = async () => {
+ if (!isAuthenticated) {
+ setFridgeIngredients([]);
+ return;
+ }
+
+ try {
+ let ingredientData = null;
+ if (selectedCategory && selectedCategory !== "전체") {
+ ingredientData = await filterFridgeIngredientsByCategory(
+ selectedCategory
+ );
+ } else {
+ ingredientData = await getFridgeIngredients();
+ }
+
+ if (ingredientData && ingredientData.ingredients) {
+ setFridgeIngredients(ingredientData.ingredients);
+ } else {
+ setFridgeIngredients([]);
+ }
+ } catch (error) {
+ console.error("재료 데이터 로딩 실패", error);
+ setFridgeIngredients([]);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, [isAuthenticated, selectedCategory]);
+
+ return (
+
+
+
+
+ {showCategoryMenu && (
+
+ {CATEGORIES.map((category) => (
+
+ ))}
+
+ )}
+
+
+
+ {fridgeIngredients.length === 0 && (
+
+
+ 냉장고에 등록된 식재료가 없습니다.
+
+
+ )}
+
+ {Array.isArray(fridgeIngredients) &&
+ fridgeIngredients.map((ingredient) => (
+
+ ))}
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+ {showAddPopup &&
setShowAddPopup(false)} />}
+
+ );
+};
diff --git a/src/screens/Desktop/sections/IngredientInventory/index.js b/src/screens/Desktop/sections/IngredientInventory/index.js
new file mode 100644
index 0000000..3bd227e
--- /dev/null
+++ b/src/screens/Desktop/sections/IngredientInventory/index.js
@@ -0,0 +1 @@
+export { IngredientInventory } from "./IngredientInventory";
diff --git a/src/screens/Desktop/sections/IngredientInventory/style.css b/src/screens/Desktop/sections/IngredientInventory/style.css
new file mode 100644
index 0000000..da60aea
--- /dev/null
+++ b/src/screens/Desktop/sections/IngredientInventory/style.css
@@ -0,0 +1,254 @@
+.ingredient-inventory {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ padding: 25px 64px;
+ position: relative;
+ width: 100%;
+}
+
+@media (max-width: 768px) {
+ .ingredient-inventory {
+ padding: 20px 20px;
+ }
+}
+
+.ingredient-inventory .ingredient-inventory-header {
+ align-self: stretch !important;
+ flex: 0 0 auto !important;
+ left: unset !important;
+ top: unset !important;
+ width: 100% !important;
+}
+
+.ingredient-inventory .ingredient-category-wrapper {
+ position: relative;
+ display: inline-flex;
+ flex: 0 0 auto;
+}
+
+.ingredient-inventory .ingredient-category-button {
+ align-items: center;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 16px;
+ cursor: pointer;
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ overflow: hidden;
+ padding: 8px 25px;
+ position: relative;
+ transition: background-color 0.2s;
+}
+
+.ingredient-inventory .ingredient-category-button:hover {
+ background-color: #e58209;
+}
+
+.ingredient-inventory .ingredient-category-text {
+ align-items: center;
+ color: #ffffff;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.09px;
+ line-height: 26.1px;
+ text-align: center;
+ white-space: nowrap;
+}
+
+.ingredient-inventory .ingredient-category-arrow {
+ color: #ffffff;
+ font-size: 12px;
+}
+
+.ingredient-inventory .ingredient-category-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 5px;
+ background-color: #ffffff;
+ border: 1px solid #0000001a;
+ border-radius: 12px;
+ box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ z-index: 10;
+ min-width: 120px;
+}
+
+.ingredient-inventory .ingredient-category-menu-item {
+ width: 100%;
+ padding: 10px 20px;
+ background: none;
+ border: none;
+ text-align: left;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ color: #000000;
+ transition: background-color 0.2s;
+}
+
+.ingredient-inventory .ingredient-category-menu-item:hover {
+ background-color: #f5f5f5;
+}
+
+.ingredient-inventory .ingredient-category-menu-item.active {
+ background-color: #f6910b1a;
+ color: #f6910b;
+ font-weight: 600;
+}
+
+.ingredient-inventory-list,
+.ingrdient-invnetory {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex-direction: row;
+ gap: 16px;
+ padding: 10px 5px;
+
+ overflow-x: auto;
+ overflow-y: hidden;
+ flex-wrap: nowrap;
+ position: relative;
+ width: 100%;
+}
+
+.ingredient-inventory-list::-webkit-scrollbar,
+.ingrdient-invnetory::-webkit-scrollbar {
+ display: none;
+}
+
+.ingredient-component-instance,
+.add-new-ingredient,
+.receipt-register-btn,
+.empty-fridge-card {
+ flex-shrink: 0 !important;
+ height: 140px;
+ min-width: 140px;
+}
+
+.ingredient-inventory .add-new-ingredient {
+ align-items: center;
+ background-color: #ffffff;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ box-shadow: 0px 12px 32px #0000000a, 0px 4px 8px #00000005;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ justify-content: center;
+ overflow: hidden;
+ padding: 20px 10px;
+ position: relative;
+ transition: transform 0.2s, box-shadow 0.2s;
+ text-decoration: none;
+ box-sizing: border-box;
+}
+
+.ingredient-inventory .add-new-ingredient:hover {
+ transform: translateY(-2px);
+ box-shadow: 0px 16px 40px #0000001a, 0px 6px 12px #0000000a;
+}
+
+.ingredient-inventory .receipt-register-btn {
+ align-items: center;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 16px;
+ box-shadow: 0px 12px 32px #0000000a, 0px 4px 8px #00000005;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ justify-content: center;
+ overflow: hidden;
+ padding: 20px 10px;
+ position: relative;
+ transition: transform 0.2s, box-shadow 0.2s;
+ box-sizing: border-box;
+}
+
+.ingredient-inventory .receipt-register-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0px 16px 40px #0000001a, 0px 6px 12px #0000000a;
+ background-color: #e58209;
+}
+
+.ingredient-inventory .add-new-container,
+.ingredient-inventory .receipt-register-container {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+}
+
+.ingredient-inventory .add-new-header,
+.ingredient-inventory .add-new-section {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+}
+
+.ingredient-inventory .add-new-header-text {
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 600;
+ text-align: center;
+ color: #000000;
+}
+
+.ingredient-inventory .receipt-register-text {
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ text-align: center;
+ color: #ffffff;
+ white-space: nowrap;
+}
+
+.ingredient-inventory .add-new-icon,
+.ingredient-inventory .receipt-register-icon {
+ width: 32px;
+ height: 32px;
+ object-fit: contain;
+}
+
+.ingredient-inventory .receipt-register-icon {
+ filter: brightness(0) invert(1);
+}
+
+.empty-fridge-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-width: 200px;
+ height: 140px;
+ background-color: #ffffff;
+ border-radius: 12px;
+ box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08);
+ padding: 16px;
+ text-align: center;
+ color: #888;
+ border: 1px dashed #ccc;
+}
+
+.empty-fridge-text {
+ font-size: 14px;
+ line-height: 1.4;
+ font-weight: 500;
+ color: #999;
+ margin: 0;
+}
diff --git a/src/screens/Desktop/sections/MenuInventory/MenuInventory.jsx b/src/screens/Desktop/sections/MenuInventory/MenuInventory.jsx
new file mode 100644
index 0000000..3525a84
--- /dev/null
+++ b/src/screens/Desktop/sections/MenuInventory/MenuInventory.jsx
@@ -0,0 +1,130 @@
+import React, { useState, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { MenuInventoryWrapper } from "../../../../components/MenuInventoryWrapper";
+import { Tendency } from "../../../../components/Tendency";
+import { UsedIngredient } from "../../../../components/UsedIngredient";
+import "./style.css";
+
+const MOCK_RECIPES = [
+ {
+ id: 1,
+ name: "저당 닭갈비",
+ ingredients: ["닭고기", "양파", "고추장", "고춧가루", "양배추", "스테비아", "마늘"],
+ steps: ["닭고기 손질 및 양념 재우기", "야채 손질", "볶기"],
+ tendencies: ["매콤칼칼", "스트레스 해소", "저당"]
+ },
+ {
+ id: 2,
+ name: "저당 닭볶음탕",
+ ingredients: ["닭고기", "감자", "당근", "양파", "고추장"],
+ steps: ["닭고기 손질 및 초벌", "양념장 만들기", "끓이기", "채소 넣고 마무리"],
+ tendencies: ["매콤칼칼", "저당"]
+ }
+];
+
+const AVAILABLE_TENDENCIES = ["매콤칼칼", "스트레스 해소", "저당", "담백", "건강식"];
+
+export const MenuInventory = () => {
+ const navigate = useNavigate();
+ const [selectedTendencies, setSelectedTendencies] = useState([]);
+ const [filteredRecipes, setFilteredRecipes] = useState([]);
+
+ // TODO: Backend Integration: Fetch user's ingredients and preferences for initial recommendation
+ useEffect(() => {
+ // Simulate fetching initial ingredient-based recommendations
+ // Replace with actual API call to get recommended recipes
+ setFilteredRecipes(MOCK_RECIPES.slice(0, 2)); // Show a couple of default recipes
+ }, []);
+
+ const toggleTendency = (tendency) => {
+ setSelectedTendencies(prev => {
+ const newTendencies = prev.includes(tendency)
+ ? prev.filter(t => t !== tendency)
+ : [...prev, tendency];
+
+ // TODO: Backend Integration: Fetch recipes based on newTendencies
+ const updatedRecipes = MOCK_RECIPES.filter(recipe =>
+ newTendencies.length === 0 || newTendencies.some(t => recipe.tendencies.includes(t))
+ );
+ setFilteredRecipes(updatedRecipes.slice(0, 2)); // Limit to 2 for main screen
+ return newTendencies;
+ });
+ };
+
+ return (
+
+
+
+
+
+
+ 추천받기 →
+
+
+ {/*
+ {AVAILABLE_TENDENCIES.map((tendency) => (
+ toggleTendency(tendency)}
+ />
+ ))}
+
*/}
+
+ {/*
+ {filteredRecipes.map((recipe) => (
+
+
+
+
+
+ {recipe.ingredients.map((ingredient, index) => (
+
+ ))}
+
+
+
+
+ {recipe.steps.join('\n')}
+
+
+

+
+
+
+ ))}
+
*/}
+
+
🍽️
+
+ 오른쪽의 '추천받기' 버튼을 눌러 맛있는 레시피를 만나보세요!
+
+
+
+ );
+};
diff --git a/src/screens/Desktop/sections/MenuInventory/index.js b/src/screens/Desktop/sections/MenuInventory/index.js
new file mode 100644
index 0000000..be34bd4
--- /dev/null
+++ b/src/screens/Desktop/sections/MenuInventory/index.js
@@ -0,0 +1 @@
+export { MenuInventory } from "./MenuInventory";
diff --git a/src/screens/Desktop/sections/MenuInventory/style.css b/src/screens/Desktop/sections/MenuInventory/style.css
new file mode 100644
index 0000000..721e81e
--- /dev/null
+++ b/src/screens/Desktop/sections/MenuInventory/style.css
@@ -0,0 +1,257 @@
+.menu-inventory {
+ align-items: flex-start;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 10px;
+ justify-content: center;
+ padding: 25px 64px;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .menu-inventory-header-wrapper {
+ align-self: stretch;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ width: 100%;
+}
+
+.menu-inventory .menu-inventory-header-link {
+ text-decoration: none;
+ color: inherit;
+}
+
+.menu-inventory .design-component-instance-node-2 {
+ flex: 0 0 auto !important;
+ left: unset !important;
+ top: unset !important;
+}
+
+.menu-inventory .menu-more-btn {
+ padding: 22px 14px;
+ background-color: #f6910b;
+ border-radius: 50px;
+
+ color: #ffffff;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ text-decoration: none;
+ line-height: 1;
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+
+ writing-mode: vertical-rl;
+ text-orientation: upright;
+ white-space: nowrap;
+ text-align: center;
+
+ cursor: pointer;
+ transition: all 0.2s ease;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.menu-inventory .menu-more-btn:hover {
+ background-color: #e58209;
+ transform: translateX(3px);
+}
+
+.menu-inventory .tendency-inventory {
+ align-items: center;
+ display: inline-flex;
+ flex: 0 0 auto;
+ gap: 8px;
+ position: relative;
+}
+
+.menu-inventory .tendency-instance {
+ margin-left: -14.50px !important;
+}
+
+.menu-inventory .tendency-2 {
+ margin-left: unset !important;
+}
+
+.menu-inventory .div-6 {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 32px;
+ overflow-x: scroll;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .div-6::-webkit-scrollbar {
+ display: none;
+ width: 0;
+}
+
+.menu-inventory .recipe {
+ align-items: flex-start;
+ background-color: #ffffff;
+ border: 1px solid;
+ border-color: #0000001a;
+ border-radius: 16px;
+ box-shadow: 0px 6px 12px #00000008, 0px 4px 8px #00000005;
+ display: flex;
+ flex-direction: column;
+ height: 140px;
+ justify-content: center;
+ max-width: 240px;
+ overflow: hidden;
+ padding: 8px 10px;
+ position: relative;
+ width: 240px;
+}
+
+.menu-inventory .recipe-text {
+ align-items: flex-end;
+ align-self: stretch;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 2px;
+ padding: 3px;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .recipe-name {
+ align-items: center;
+ align-self: stretch;
+ border-radius: 15px;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+ justify-content: center;
+ overflow: hidden;
+ padding: 0px 5px;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .recipe-name-text {
+ align-items: center;
+ color: #000000e6;
+ display: flex;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.08px;
+ line-height: 22.4px;
+ margin-top: -1.00px;
+ position: relative;
+}
+
+.menu-inventory .use-ingredient {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 2px;
+ overflow: hidden;
+ overflow-x: scroll;
+ padding: 2px 4px;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .use-ingredient::-webkit-scrollbar {
+ display: none;
+ width: 0;
+}
+
+.menu-inventory .used-ingredient-instance {
+ align-self: stretch !important;
+ flex: 0 0 auto !important;
+ height: unset !important;
+ left: unset !important;
+ top: unset !important;
+}
+
+.menu-inventory .recipe-container {
+ align-items: center;
+ align-self: stretch;
+ background-color: #0000001a;
+ border-radius: 16px;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 5px;
+ justify-content: center;
+ padding: 5px 10px;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .recipe-text-2 {
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 4;
+ align-items: center;
+ color: #0000008c;
+ display: -webkit-box;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 11px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.06px;
+ line-height: 15.4px;
+ margin-bottom: -1.50px;
+ margin-top: -3.50px;
+ overflow: hidden;
+ position: relative;
+ text-overflow: ellipsis;
+}
+
+.menu-inventory .recipe-extension {
+ height: 24px;
+ position: relative;
+ width: 24px;
+}
+
+.menu-inventory .recipe-container-2 {
+ align-items: center;
+ align-self: stretch;
+ background-color: #0000001a;
+ border-radius: 16px;
+ display: flex;
+ flex: 1;
+ flex-grow: 1;
+ gap: 5px;
+ justify-content: center;
+ padding: 3px 10px;
+ position: relative;
+ width: 100%;
+}
+
+.menu-inventory .text-wrapper-7 {
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 4;
+ align-items: center;
+ color: #0000008c;
+ display: -webkit-box;
+ flex: 1;
+ font-family: "Inter", Helvetica;
+ font-size: 11px;
+ font-weight: 500;
+ justify-content: center;
+ letter-spacing: -0.06px;
+ line-height: 15.4px;
+ margin-top: -1.50px;
+ overflow: hidden;
+ position: relative;
+ text-overflow: ellipsis;
+}
diff --git a/src/screens/Desktop/style.css b/src/screens/Desktop/style.css
new file mode 100644
index 0000000..e790d15
--- /dev/null
+++ b/src/screens/Desktop/style.css
@@ -0,0 +1,59 @@
+.desktop {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 1080px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+@media (max-width: 1280px) {
+ .desktop {
+ width: 100%;
+ }
+}
+
+@media (max-width: 768px) {
+ .desktop {
+ min-height: auto;
+ }
+}
+
+.desktop .site-header-name {
+ align-items: center;
+ align-self: stretch;
+ display: flex;
+ flex: 0 0 auto;
+ gap: 48px;
+ justify-content: center;
+ padding: 20px 0px;
+ position: relative;
+ width: 100%;
+}
+
+.desktop .choose-your-receipt {
+ align-items: center;
+ color: #000000;
+ display: flex;
+ font-family: "Inter", Helvetica;
+ font-size: 36px;
+ font-weight: 700;
+ justify-content: center;
+ letter-spacing: -0.72px;
+ line-height: 43.2px;
+ margin: 0;
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ width: fit-content;
+}
+
+@media (max-width: 768px) {
+ .desktop .choose-your-receipt {
+ font-size: 24px;
+ line-height: 28.8px;
+ }
+}
diff --git a/src/screens/MenuRecommendationPage/MenuRecommendationPage.jsx b/src/screens/MenuRecommendationPage/MenuRecommendationPage.jsx
new file mode 100644
index 0000000..8be952e
--- /dev/null
+++ b/src/screens/MenuRecommendationPage/MenuRecommendationPage.jsx
@@ -0,0 +1,461 @@
+import React, { useState, useRef, useEffect } from "react";
+import { Link } from "react-router-dom";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import "./style.css";
+import Voice from "./Voice/Voice";
+import { useAuth } from "../../contexts/AuthContext";
+import { getFridgeIngredients } from "../../api/fridgeApi";
+import {
+ getRandomRecipe,
+ getRandomRecipeMultiple,
+} from "../../api/Recipe/recipeApi";
+import {
+ recommendExpir,
+ recommendPrefer,
+ recommendCondition,
+} from "../../api/Recipe/geminiRecipeApi";
+
+const RECIPES_PER_PAGE = 12;
+const DEFAULT_IMAGE_URL =
+ "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png";
+
+export const MenuRecommendationPage = () => {
+ const [searchQuery, setSearchQuery] = useState("");
+ const [recipes, setRecipes] = useState([]);
+ const [currentPage, setCurrentPage] = useState(0);
+ const [fridgeIngredients, setFridgeIngredients] = useState([]);
+ const [recommendationType, setRecommendationType] = useState(null);
+ const [isVoiceMode, setIsVoiceMode] = useState(false);
+ const [showRecipe, setShowRecipe] = useState(false);
+ const scrollContainerRef = useRef(null);
+ const { isAuthenticated } = useAuth();
+ const [selectedIngredients, setSelectedIngredients] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const startVoiceRecognition = () => {
+ setIsVoiceMode(true);
+ };
+
+ const closeVoicePopup = () => {
+ setIsVoiceMode(false);
+ };
+
+ const handleVoiceResult = (voiceRecipes) => {
+ if (searchQuery) {
+ setSearchQuery("");
+ }
+ if (voiceRecipes && voiceRecipes.length > 0) {
+ setSearchQuery("음성 검색 결과");
+ setRecipes(voiceRecipes);
+ setRecommendationType("voice");
+ setCurrentPage(0);
+ setShowRecipe(true);
+ } else {
+ alert("음성 검색 결과에 해당하는 레시피가 없습니다.");
+ }
+ closeVoicePopup();
+ };
+
+ const handleGeminiSearch = async () => {
+ if (!isAuthenticated) {
+ console.log("인증 안되어 있음");
+ return;
+ }
+ if (!searchQuery) {
+ alert("조건을 먼저 입력해주세요");
+ return;
+ }
+ setIsLoading(true);
+ setShowRecipe(false);
+ setRecommendationType("condition");
+
+ const userId = localStorage.getItem("userId");
+ const requestData = {
+ userId,
+ excludedTitles: [],
+ condition: searchQuery,
+ };
+
+ try {
+ const result = await recommendCondition(requestData);
+ if (result && result.length > 0) {
+ setRecipes(result);
+ setShowRecipe(true);
+ } else {
+ alert("레시피가 존재하지 않습니다.");
+ }
+ } catch (error) {
+ console.error("추천 실패:", error);
+ alert("오류가 발생했습니다.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleGetIngredients = async () => {
+ setRecommendationType("ingredient");
+ setShowRecipe(false);
+
+ try {
+ const response = await getFridgeIngredients();
+ const ingredientsList = response?.ingredients || [];
+ setFridgeIngredients(ingredientsList);
+
+ if (ingredientsList.length === 0) {
+ alert(
+ "냉장고에 등록된 재료가 없으므로 추천을 진행할 수 없습니다. 재료를 추가해주세요."
+ );
+ return;
+ }
+ } catch (error) {
+ console.error(
+ "API 호출 실패:",
+ error.response?.status,
+ error.response?.data
+ );
+ alert("냉장고 재료를 불러오는 데 실패했습니다. 다시 시도해주세요.");
+ }
+ };
+
+ const handleTendencyBasedRecommendation = async () => {
+ if (!isAuthenticated) {
+ console.log("로그인 하고 오세요!!");
+ setRecipes([]);
+ return;
+ }
+ if (searchQuery) {
+ setSearchQuery("");
+ }
+ const userId = localStorage.getItem("userId");
+ const requestData = {
+ userId,
+ excludedTitles: [],
+ };
+ setRecommendationType("tendency");
+ setIsLoading(true);
+ setShowRecipe(false);
+ try {
+ const result = await recommendPrefer(requestData);
+ console.log("추천결과", result);
+ if (result && result.length > 0) {
+ setRecipes(result);
+ setShowRecipe(true);
+ } else {
+ alert("추천 결과가 없습니다.");
+ }
+ } catch (error) {
+ console.error("추천 실패:", error);
+ alert("오류가 발생했습니다.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const totalPages = Math.ceil(recipes.length / RECIPES_PER_PAGE);
+ const displayedRecipes = recipes.slice(
+ currentPage * RECIPES_PER_PAGE,
+ (currentPage + 1) * RECIPES_PER_PAGE
+ );
+
+ const handleScroll = (direction) => {
+ if (direction === "left" && currentPage > 0) {
+ setCurrentPage(currentPage - 1);
+ } else if (direction === "right" && currentPage < totalPages - 1) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ const handleIngredientClick = (ingredientName) => {
+ setSelectedIngredients((prevSelected) => {
+ if (prevSelected.includes(ingredientName)) {
+ return prevSelected.filter((name) => name !== ingredientName);
+ } else {
+ return [...prevSelected, ingredientName];
+ }
+ });
+ };
+
+ const handleIngredientBasedRecommendation = async () => {
+ if (!isAuthenticated) {
+ console.log("인증 안되어 있음");
+ return;
+ }
+ if (searchQuery) {
+ setSearchQuery("");
+ }
+ if (selectedIngredients.length === 0) {
+ alert("재료를 먼저 선택해주세요.");
+ return;
+ }
+
+ setIsLoading(true);
+ setRecommendationType("ingredient");
+ setShowRecipe(false);
+
+ try {
+ let result = null;
+
+ if (selectedIngredients.length === 1) {
+ console.log("단일 재료 검색:", selectedIngredients[0]);
+ result = await getRandomRecipe(selectedIngredients[0]);
+ } else {
+ console.log("다중 재료 검색:", selectedIngredients);
+ result = await getRandomRecipeMultiple(selectedIngredients);
+ }
+
+ console.log("서버 응답 데이터:", result);
+
+ if (!result) {
+ setRecipes([]);
+ alert("해당 재료로 추천할 레시피가 없습니다.");
+ } else if (Array.isArray(result)) {
+ setRecipes(result);
+ setCurrentPage(0);
+ setShowRecipe(true);
+ } else if (typeof result === "object") {
+ setRecipes([result]);
+ setCurrentPage(0);
+ setShowRecipe(true);
+ } else if (typeof result === "string") {
+ setRecipes([]);
+ alert(result);
+ } else {
+ setRecipes([]);
+ alert("알 수 없는 데이터 형식입니다.");
+ }
+ } catch (error) {
+ console.error("레시피 추천 로직 실패:", error);
+ alert("레시피를 불러오는 중 오류가 발생했습니다.");
+ setRecipes([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleExpiredIngredientBasedRecommendation = async () => {
+ if (!isAuthenticated) {
+ console.log("로그인 하고 오세요!!");
+ setRecipes([]);
+ return;
+ }
+ if (searchQuery) {
+ setSearchQuery("");
+ }
+
+ if (fridgeIngredients.length === 0) {
+ alert("먼저 '식재료 기반 추천' 버튼을 눌러 냉장고 재료를 불러와주세요.");
+ return;
+ }
+
+ setIsLoading(true);
+ setRecommendationType("expire");
+ setShowRecipe(false);
+
+ try {
+ const userId = localStorage.getItem("userId");
+
+ const expiredIngredients = fridgeIngredients.filter(
+ (ingredient) => ingredient.dday < 3
+ );
+
+ if (expiredIngredients.length === 0) {
+ alert("유통기한이 3일 이내로 남은 재료가 없습니다.");
+ setIsLoading(false);
+ return;
+ }
+
+ const requestData = {
+ userId,
+ excludedTitles: [],
+ ingredients: expiredIngredients.map((ing) => ing.name),
+ };
+
+ const result = await recommendExpir(requestData);
+
+ if (result && result.length > 0) {
+ setRecipes(result);
+ setShowRecipe(true);
+ } else {
+ alert("추천된 음식이 없습니다.");
+ }
+ } catch (error) {
+ console.error("레시피 추천 로직 실패:", error);
+ alert("레시피를 불러오는 중 오류가 발생했습니다.");
+ setRecipes([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
메뉴 추천
+
+ AI가 추천하는 맞춤 레시피를 찾아보세요
+
+
+
+
+
+
+
+
+
+ {recommendationType === "ingredient" && (
+
+ {" "}
+ {fridgeIngredients.map((ingredient) => (
+
+ ))}
+ {fridgeIngredients.length === 0 && (
+
+ 냉장고에 등록된 재료가 없습니다.
+
+ )}
+
+
+ )}
+
+
+
+ setSearchQuery(e.target.value)}
+ onKeyPress={(e) => e.key === "Enter" && handleGeminiSearch()}
+ />
+
+
+
+
+
+ {isLoading && (
+
+
AI가 최적의 레시피를 찾고 있습니다...
+
+ )}
+
+ {showRecipe && recipes.length > 0 && !isLoading && (
+
+
+
+
+ {currentPage + 1} / {totalPages}
+
+
+
+
+
+ {displayedRecipes.map((recipe) => (
+
+

+
+
{recipe.name}
+
+ {recipe.description}
+
+
+ {(recipe.tendencies || [])
+ .slice(0, 2)
+ .map((tendency, index) => (
+
+ {tendency}
+
+ ))}
+
+
+
+ ))}
+
+
+ )}
+
+ {showRecipe && recipes.length === 0 && !isLoading && (
+
+
검색된 레시피가 없습니다.
+
다른 조건이나 재료로 검색해보세요.
+
+ )}
+
+ {isVoiceMode && (
+
+ )}
+
+
+ );
+};
diff --git a/src/screens/MenuRecommendationPage/Voice/Voice.jsx b/src/screens/MenuRecommendationPage/Voice/Voice.jsx
new file mode 100644
index 0000000..144689f
--- /dev/null
+++ b/src/screens/MenuRecommendationPage/Voice/Voice.jsx
@@ -0,0 +1,159 @@
+import React, { useState, useRef } from "react";
+import { FaMicrophone, FaStop } from "react-icons/fa";
+import "./style.css";
+import { recommendByVoice } from "../../../api/Stt/sttApi";
+
+const Voice = ({ onClose, onVoiceResult }) => {
+ const [isRecording, setIsRecording] = useState(false);
+ const [isProcessing, setIsProcessing] = useState(false);
+
+ const mediaRecorderRef = useRef(null);
+ const audioChunksRef = useRef([]);
+ const startRecording = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ const mediaRecorder = new MediaRecorder(stream, {
+ mimeType: "audio/webm",
+ });
+ mediaRecorderRef.current = mediaRecorder;
+ audioChunksRef.current = [];
+
+ mediaRecorder.ondataavailable = (event) => {
+ if (event.data.size > 0) {
+ audioChunksRef.current.push(event.data);
+ }
+ };
+
+ mediaRecorder.onstop = async () => {
+ const audioBlob = new Blob(audioChunksRef.current, {
+ type: "audio/webm",
+ });
+
+ stream.getTracks().forEach((track) => track.stop());
+
+ await handleSendAudio(audioBlob);
+ };
+
+ mediaRecorder.start();
+ setIsRecording(true);
+ } catch (error) {
+ console.error("마이크 접근 실패:", error);
+ alert("마이크를 사용할 수 없습니다. 권한을 확인해주세요.");
+ }
+ };
+
+ // 2. 녹음 중단 (버튼 클릭 시)
+ const stopRecording = () => {
+ if (mediaRecorderRef.current && isRecording) {
+ mediaRecorderRef.current.stop(); // -> onstop 이벤트 발생 -> handleSendAudio 실행
+ setIsRecording(false);
+ }
+ };
+
+ // 3. 서버로 오디오 파일 전송 및 결과 처리
+ const handleSendAudio = async (audioBlob) => {
+ setIsProcessing(true); // 로딩 UI 표시
+ try {
+ console.log("오디오 파일 전송 시작...");
+
+ // [API 호출] 오디오 Blob을 서버로 보냄 -> 레시피 리스트 받음
+ const recipes = await recommendByVoice(audioBlob);
+
+ console.log("음성 추천 결과:", recipes);
+
+ if (recipes && recipes.length > 0) {
+ onVoiceResult(recipes); // 부모에게 레시피 리스트 전달
+ onClose(); // 팝업 닫기
+ } else {
+ alert("음성 인식 결과에 맞는 레시피를 찾지 못했습니다.");
+ setIsProcessing(false); // 실패 시 로딩만 끄고 팝업 유지 (재시도 가능하게)
+ }
+ } catch (error) {
+ console.error("음성 검색 실패:", error);
+ alert("서버 통신 중 오류가 발생했습니다.");
+ onClose();
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
음성 레시피 검색
+
+
+
+ {/* 로딩 중 (서버 분석 중) */}
+ {isProcessing ? (
+
+
+
+ AI가 목소리를 듣고 레시피를 찾는 중...
+
+
+ ) : (
+ /* 녹음 대기/진행 화면 */
+ <>
+
+ {/* 녹음 중이면 정지 아이콘, 아니면 마이크 아이콘 */}
+ {isRecording ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {isRecording
+ ? "듣고 있어요... (클릭하면 검색 시작)"
+ : "버튼을 눌러 말씀하세요 (예: 닭고기 요리해줘)"}
+
+
+ >
+ )}
+
+
+ {/* 하단 버튼 (로딩 중에는 숨김) */}
+ {!isProcessing && (
+
+
+
+ )}
+
+
+ );
+};
+
+export default Voice;
diff --git a/src/screens/MenuRecommendationPage/Voice/style.css b/src/screens/MenuRecommendationPage/Voice/style.css
new file mode 100644
index 0000000..9949753
--- /dev/null
+++ b/src/screens/MenuRecommendationPage/Voice/style.css
@@ -0,0 +1,168 @@
+/* * Voice Popup Styles
+ * 기존 signup-popup에서 voice-popup으로 클래스명을 변경했습니다.
+ */
+
+ .voice-popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-in-out;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+
+ .voice-popup-container {
+ background-color: #ffffff;
+ border-radius: 20px;
+ padding: 40px;
+ width: 90%;
+ max-width: 400px;
+ position: relative;
+ box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease-out;
+ max-height: 90vh;
+ overflow-y: auto;
+ }
+
+ @keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+ }
+
+ .voice-popup-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ background: none;
+ border: none;
+ font-size: 32px;
+ color: #666666;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+ }
+
+ .voice-popup-close:hover {
+ color: #000000;
+ }
+
+ .voice-popup-header {
+ margin-bottom: 30px;
+ text-align: center;
+ }
+
+ .voice-popup-title {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 28px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+ }
+
+ .voice-popup-content { /* Form 대신 더 포괄적인 Content로 변경 가능하지만, 입력 폼이 있다면 그대로 둠 */
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ }
+
+ .voice-popup-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .voice-popup-label {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ color: #000000;
+ }
+
+ /* Input과 Select 스타일 */
+ .voice-popup-input,
+ .voice-popup-select {
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 16px;
+ transition: border-color 0.2s;
+ background-color: #ffffff;
+ }
+
+ .voice-popup-input:focus,
+ .voice-popup-select:focus {
+ outline: none;
+ border-color: #f6910b;
+ }
+
+ .voice-popup-input::placeholder {
+ color: #999999;
+ }
+
+ .voice-popup-action-btn {
+ padding: 14px;
+ background-color: #f6910b;
+ color: #ffffff;
+ border: none;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ margin-top: 10px;
+ }
+
+ .voice-popup-action-btn:hover {
+ background-color: #e58209;
+ }
+
+ .voice-popup-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 10px;
+ }
+
+ .voice-popup-footer-text {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 14px;
+ color: #666666;
+ }
+
+ .voice-popup-link-btn {
+ background: none;
+ border: none;
+ color: #f6910b;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+ text-decoration: underline;
+ }
+
+ .voice-popup-link-btn:hover {
+ color: #e58209;
+ }
\ No newline at end of file
diff --git a/src/screens/MenuRecommendationPage/index.js b/src/screens/MenuRecommendationPage/index.js
new file mode 100644
index 0000000..13935ed
--- /dev/null
+++ b/src/screens/MenuRecommendationPage/index.js
@@ -0,0 +1 @@
+export { MenuRecommendationPage } from "./MenuRecommendationPage";
diff --git a/src/screens/MenuRecommendationPage/style.css b/src/screens/MenuRecommendationPage/style.css
new file mode 100644
index 0000000..662635e
--- /dev/null
+++ b/src/screens/MenuRecommendationPage/style.css
@@ -0,0 +1,376 @@
+.menu-recommendation-page {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ position: relative;
+ width: 100%;
+}
+
+.menu-recommendation-content {
+ align-items: flex-start;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: 30px;
+ padding: 20px 64px;
+ position: relative;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+}
+
+.menu-recommendation-header {
+ align-items: flex-start;
+ align-self: stretch;
+ border-bottom: 1px solid #000000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding-bottom: 20px;
+ width: 100%;
+}
+
+.menu-recommendation-title {
+ color: #000000;
+ font-family: "Inter", Helvetica;
+ font-size: 48px;
+ font-weight: 700;
+ letter-spacing: -0.96px;
+ line-height: 57.6px;
+ margin: 0;
+}
+
+.menu-recommendation-subtitle {
+ color: #0000008c;
+ font-family: "Inter", Helvetica;
+ font-size: 20px;
+ font-weight: 500;
+ letter-spacing: -0.1px;
+ line-height: 29px;
+ margin: 0;
+}
+
+.menu-search-section {
+ align-self: stretch;
+ display: flex;
+ gap: 10px;
+ width: 100%;
+}
+
+.menu-search-input {
+ flex: 1;
+ padding: 14px 20px;
+ border: 2px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ transition: border-color 0.2s;
+}
+
+.menu-search-input:focus {
+ outline: none;
+ border-color: #f6910b;
+}
+
+.menu-search-btn {
+ padding: 14px 30px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 12px;
+ color: #ffffff;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ transition: background-color 0.2s;
+}
+
+.menu-search-btn:hover {
+ background-color: #e58209;
+}
+
+.menu-voice-btn {
+ width: 50px;
+ height: 50px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 12px;
+ color: #ffffff;
+ cursor: pointer;
+ font-size: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.2s;
+}
+
+.menu-voice-btn:hover {
+ background-color: #e58209;
+}
+
+.menu-recommendation-buttons {
+ align-self: stretch;
+ display: flex;
+ gap: 15px;
+ justify-content: center;
+}
+
+.menu-recommendation-btn {
+ padding: 14px 30px;
+ background-color: #e0e0e0;
+ border: 2px solid transparent;
+ border-radius: 12px;
+ color: #666666;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ transition: all 0.2s;
+}
+
+.menu-recommendation-btn:hover {
+ background-color: #d0d0d0;
+}
+
+.menu-recommendation-btn.active {
+ background-color: #f6910b;
+ border-color: #f6910b;
+ color: #ffffff;
+}
+
+.menu-recipes-section {
+ align-self: stretch;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ width: 100%;
+}
+
+.menu-recipes-navigation {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+}
+
+.menu-scroll-btn {
+ width: 40px;
+ height: 40px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 50%;
+ color: #ffffff;
+ cursor: pointer;
+ font-size: 20px;
+ font-weight: 700;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.menu-scroll-btn:hover:not(:disabled) {
+ background-color: #e58209;
+ transform: scale(1.1);
+}
+
+.menu-scroll-btn:disabled {
+ background-color: #cccccc;
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+.menu-page-indicator {
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 600;
+ color: #000000;
+}
+
+.menu-recipes-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 20px;
+ width: 100%;
+ transition: transform 0.3s ease-in-out;
+}
+
+.menu-recipe-card {
+ background-color: #ffffff;
+ border: 1px solid #e0e0e0;
+ border-radius: 16px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: all 0.2s;
+ text-decoration: none;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.05);
+}
+
+.menu-recipe-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.1);
+ border-color: #f6910b;
+}
+
+.menu-recipe-card-image {
+ width: 100%;
+ height: 180px;
+ object-fit: cover;
+}
+
+.menu-recipe-card-content {
+ padding: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.menu-recipe-card-title {
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+}
+
+.menu-recipe-card-description {
+ font-family: "Inter", Helvetica;
+ font-size: 14px;
+ font-weight: 500;
+ color: #666666;
+ margin: 0;
+ line-height: 1.5;
+}
+
+.menu-recipe-card-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.menu-recipe-card-tag {
+ padding: 4px 10px;
+ background-color: #f6910b1a;
+ border-radius: 12px;
+ color: #f6910b;
+ font-family: "Inter", Helvetica;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.menu-empty-state {
+ align-self: stretch;
+ text-align: center;
+ padding: 60px 40px;
+ background-color: #f5f5f5;
+ border-radius: 20px;
+}
+
+.menu-empty-state p {
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 500;
+ color: #666666;
+ margin: 10px 0;
+}
+
+@media (max-width: 768px) {
+ .menu-recommendation-content {
+ padding: 20px 20px;
+ }
+
+ .menu-recommendation-title {
+ font-size: 32px;
+ line-height: 38.4px;
+ }
+
+ .menu-recommendation-subtitle {
+ font-size: 16px;
+ line-height: 23.2px;
+ }
+
+ .menu-recipes-grid {
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 15px;
+ }
+
+ .menu-recommendation-buttons {
+ flex-direction: column;
+ }
+
+ .menu-recommendation-btn {
+ width: 100%;
+ }
+}
+
+.menu-condition-label {
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ color: white;
+ background-color: #f6910b;
+ border-radius: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 12px 20px;
+ height: auto;
+ white-space: nowrap;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.ingredient-tags-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ width: 100%;
+ padding: 10px 0;
+}
+
+.ingredient-tag-button {
+ padding: 10px 18px;
+ border: 1px solid #ddd;
+ border-radius: 20px;
+ background-color: #ffffff;
+ color: #444;
+ font-size: 15px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+}
+
+.ingredient-tag-button:hover {
+ border-color: #f6910b;
+ background-color: #fff8f0;
+ color: #f6910b;
+}
+
+.ingredient-tag-button.selected {
+ background-color: #f6910b;
+ color: white;
+ border-color: #f6910b;
+ box-shadow: 0 4px 10px rgba(246, 145, 11, 0.3);
+}
+
+.search-recipes-button {
+ padding: 15px;
+ background-color: #4caf50;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 17px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ width: 100%;
+}
+
+.search-recipes-button:disabled {
+ background-color: #cccccc;
+ cursor: not-allowed;
+}
diff --git a/src/screens/MyPage/FoodTendency.jsx b/src/screens/MyPage/FoodTendency.jsx
new file mode 100644
index 0000000..7c038d8
--- /dev/null
+++ b/src/screens/MyPage/FoodTendency.jsx
@@ -0,0 +1,82 @@
+import React from "react";
+import { FaFire, FaUtensils, FaHeart, FaBan } from "react-icons/fa";
+import "./style.css";
+
+function FoodTendency({ dislikedIngredients, preferredIngredients, preferredCuisine, spiceLevel }) {
+
+ const renderTags = (data) => {
+ if (!data || data.length === 0) return 없음;
+
+ const items = typeof data === 'string' ? data.split(',') : data;
+
+ return (
+
+ {items.map((item, index) => (
+
+ {item.trim()}
+
+ ))}
+
+ );
+ };
+
+ return (
+
+
+
식습관 성향
+
+
+
+
+
+
+
+
+
+
선호하는 요리
+
+ {renderTags(preferredCuisine)}
+
+
+
+
+
+
+
+
+
+ 매운맛 단계
+ {spiceLevel || "보통"}
+
+
+
+
+
+
+
+
+
좋아하는 재료
+
+ {renderTags(preferredIngredients)}
+
+
+
+
+
+
+
+
+
+
싫어하는 재료
+
+ {renderTags(dislikedIngredients)}
+
+
+
+
+
+
+ );
+}
+
+export default FoodTendency;
\ No newline at end of file
diff --git a/src/screens/MyPage/MyPage.jsx b/src/screens/MyPage/MyPage.jsx
new file mode 100644
index 0000000..54df607
--- /dev/null
+++ b/src/screens/MyPage/MyPage.jsx
@@ -0,0 +1,263 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate, useLocation } from "react-router-dom";
+import { useAuth } from "../../contexts/AuthContext";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import { LoginPopup } from "../../components/LoginPopup";
+import { SignupPopup } from "../../components/SignupPopup";
+import { PreferencesPopup } from "../../components/PreferencesPopup";
+import { MypageTendency } from "../../components/MypageTendency";
+import FindId from "../../components/LoginPopup/FindId/FindId";
+import FindPassword from "../../components/LoginPopup/FindPassword/FindPassword";
+import MyPageInfo from "./MyPageInfo";
+import FoodTendency from "./FoodTendency";
+import UpdatePreference from "../../components/PreferencesPopup/UpdatePreference";
+import "./style.css";
+
+import { getUserInfo } from "../../api/userInfo";
+
+export const MyPage = () => {
+ const { user, isAuthenticated } = useAuth();
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const [preferences, setPreferences] = useState(null);
+
+ const [showLoginPopup, setShowLoginPopup] = useState(false);
+ const [showSignupPopup, setShowSignupPopup] = useState(false);
+ const [showPreferencesPopup, setShowPreferencesPopup] = useState(false);
+ const [showFindIdPopup, setShowFindIdPopup] = useState(false);
+ const [showFindPasswordPopup, setShowFindPasswordPopup] = useState(false);
+
+ const [showUpdatePreferencePopup, setShowUpdatePreferencePopup] =
+ useState(false);
+
+ useEffect(() => {
+ if (isAuthenticated) {
+ const fetchData = async () => {
+ try {
+ const myInfoData = await getUserInfo();
+ setPreferences(myInfoData);
+ console.log("내 정보 로드 완료:", myInfoData);
+ } catch (error) {
+ console.error("데이터 불러오기 실패:", error);
+ }
+ };
+ fetchData();
+ } else {
+ setPreferences(null);
+ }
+ }, [isAuthenticated]);
+
+ useEffect(() => {
+ if (location.state?.showLogin) {
+ setShowLoginPopup(true);
+ navigate(location.pathname, { replace: true, state: {} });
+ }
+ }, [location, navigate]);
+
+ const handleLoginClick = () => setShowLoginPopup(true);
+
+ const handleSwitchToSignup = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(true);
+ };
+
+ const handleSwitchToLogin = () => {
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setShowFindIdPopup(false);
+ setShowFindPasswordPopup(false);
+ setShowLoginPopup(true);
+ };
+
+ const handleSwitchToFindId = () => {
+ setShowLoginPopup(false);
+ setShowFindIdPopup(true);
+ };
+
+ const handleSwitchToFindPassword = () => {
+ setShowLoginPopup(false);
+ setShowFindPasswordPopup(true);
+ };
+
+ const handleSwitchToPreferences = () => {
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(true);
+ };
+
+ const handleSwitchToUpdatePreference = () => {
+ setShowUpdatePreferencePopup(true);
+ };
+
+ const handleCloseAll = () => {
+ setShowLoginPopup(false);
+ setShowSignupPopup(false);
+ setShowPreferencesPopup(false);
+ setShowFindIdPopup(false);
+ setShowFindPasswordPopup(false);
+ setShowUpdatePreferencePopup(false);
+ };
+
+ return (
+
+
+
+
+
+
+ {!isAuthenticated ? (
+
+
로그인이 필요합니다
+
+
+ ) : preferences ? (
+
+
+
+
+
+
+
+
+
건강 목표
+
+
+ {preferences.goals && preferences.goals.length > 0 ? (
+ preferences.goals.map((goalName, index) => (
+
+ ))
+ ) : (
+
설정된 목표가 없습니다
+ )}
+
+
+
+
+
+
알레르기
+
+
+ {preferences.allergyList &&
+ preferences.allergyList.length > 0 ? (
+ preferences.allergyList.map((allergyName, index) => (
+
+ ))
+ ) : (
+
없음
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
알림 설정
+
+
+
+ 소비 알림
+
+ {preferences.allowPushConsumption ? "ON" : "OFF"}
+
+
+
+ 댓글 알림
+
+ {preferences.allowPushComment ? "ON" : "OFF"}
+
+
+
+ 넛지 알림
+
+ {preferences.allowPushNudge ? "ON" : "OFF"}
+
+
+
+
+
+
+ ) : (
+
+
정보를 불러오는 중이거나 성향 정보가 없습니다.
+
+ )}
+
+
+ {showLoginPopup && (
+
+ )}
+ {showSignupPopup && (
+
+ )}
+ {showPreferencesPopup &&
}
+
+ {showUpdatePreferencePopup && (
+
+ )}
+
+ {showFindIdPopup && (
+
+ )}
+ {showFindPasswordPopup && (
+
+ )}
+
+ );
+};
diff --git a/src/screens/MyPage/MyPageInfo.jsx b/src/screens/MyPage/MyPageInfo.jsx
new file mode 100644
index 0000000..d53fc5f
--- /dev/null
+++ b/src/screens/MyPage/MyPageInfo.jsx
@@ -0,0 +1,50 @@
+import { FaUser, FaBirthdayCake, FaIdCard } from "react-icons/fa";
+import "./style.css";
+
+function MyPageInfo({ age, id, birthDate }) {
+ return (
+
+
+
내 정보
+
+
+
+
+
+
+
+
+ 아이디
+ {id || "skt2008"}
+
+
+
+
+
+
+
+
+
+
+ 생년월일
+ {birthDate || "2000-01-01"}
+
+
+
+
+
+
+
+
+
+
+ 나이
+ {`${age}세`}
+
+
+
+
+ );
+}
+
+export default MyPageInfo;
diff --git a/src/screens/MyPage/index.js b/src/screens/MyPage/index.js
new file mode 100644
index 0000000..b2f5896
--- /dev/null
+++ b/src/screens/MyPage/index.js
@@ -0,0 +1 @@
+export { MyPage } from "./MyPage";
diff --git a/src/screens/MyPage/style.css b/src/screens/MyPage/style.css
new file mode 100644
index 0000000..7a28ce4
--- /dev/null
+++ b/src/screens/MyPage/style.css
@@ -0,0 +1,439 @@
+.my-page {
+ align-items: center;
+ background-color: #ffffff;
+ display: flex;
+ flex-direction: column;
+ min-height: 1080px;
+ position: relative;
+ width: 100%;
+}
+
+.mypage-nav {
+ width: 90%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 0 20px 0;
+ border-bottom: 2px solid #000;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: center;
+}
+
+.mypage-text {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 32px;
+ font-weight: 800;
+ color: #000;
+}
+
+.mypage-section {
+ width: 90%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 0 60px 0;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.mypage-grid-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 40px;
+ width: 100%;
+ align-items: start;
+ box-sizing: border-box;
+}
+
+.mypage-left-col,
+.mypage-right-col {
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+ width: 100%;
+}
+
+.mypage-card-wrapper {
+ width: 100%;
+}
+
+.simple-card {
+ background-color: #ffffff;
+ border-radius: 16px;
+ padding: 30px 40px;
+ box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.05);
+ border: 1px solid #f0f0f0;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.mypage-card-header h3 {
+ font-family: "Inter", sans-serif;
+ font-size: 20px;
+ font-weight: 800;
+ color: #333;
+ margin: 0 0 20px 0;
+ padding-bottom: 15px;
+ border-bottom: 2px solid #f5f5f5;
+}
+
+.mypage-info-card {
+ background-color: #ffffff;
+ border-radius: 16px;
+ padding: 30px 40px;
+ box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.05);
+ width: 100%;
+ box-sizing: border-box;
+ border: 1px solid #f0f0f0;
+}
+
+.mypage-info-header {
+ margin-bottom: 25px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid #f5f5f5;
+}
+
+.mypage-info-title {
+ font-family: "Inter", sans-serif;
+ font-size: 20px;
+ font-weight: 800;
+ color: #333;
+ margin: 0;
+}
+
+.mypage-info-content {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ gap: 20px;
+}
+
+.info-item {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 15px;
+ flex: 1;
+ padding: 10px 0;
+}
+
+.info-icon-box {
+ width: 45px;
+ height: 45px;
+ background-color: #fff8f0;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #f6910b;
+ font-size: 20px;
+ flex-shrink: 0;
+}
+
+.info-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.info-label {
+ font-family: "Inter", sans-serif;
+ font-size: 13px;
+ color: #888;
+ font-weight: 500;
+}
+
+.info-value {
+ font-family: "Inter", sans-serif;
+ font-size: 17px;
+ color: #222;
+ font-weight: 700;
+}
+
+.info-divider {
+ width: 1px;
+ height: 40px;
+ background-color: #e0e0e0;
+}
+
+.food-tendency-card {
+ background-color: #ffffff;
+ border-radius: 16px;
+ padding: 30px 40px;
+ box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.05);
+ width: 100%;
+ box-sizing: border-box;
+ border: 1px solid #f0f0f0;
+}
+
+.tendency-header {
+ margin-bottom: 25px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid #f5f5f5;
+}
+
+.tendency-title {
+ font-family: "Inter", sans-serif;
+ font-size: 20px;
+ font-weight: 800;
+ color: #333;
+ margin: 0;
+}
+
+.tendency-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 30px 20px;
+ align-items: flex-start;
+}
+
+.tendency-item {
+ display: flex;
+ flex-direction: row;
+ gap: 15px;
+ flex: 1 1 200px;
+ padding: 10px 0;
+}
+
+.tendency-item.item-wide {
+ flex-grow: 2;
+ min-width: 300px;
+}
+
+.tendency-icon-box {
+ width: 45px;
+ height: 45px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+ flex-shrink: 0;
+}
+
+.tendency-icon-box.blue {
+ background-color: #e3f2fd;
+ color: #1e88e5;
+}
+.tendency-icon-box.red {
+ background-color: #ffebee;
+ color: #e53935;
+}
+.tendency-icon-box.orange {
+ background-color: #fff3e0;
+ color: #f6910b;
+}
+.tendency-icon-box.gray {
+ background-color: #f5f5f5;
+ color: #757575;
+}
+
+.tendency-text-group {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ flex: 1;
+}
+
+.tendency-label {
+ font-family: "Inter", sans-serif;
+ font-size: 13px;
+ color: #888;
+ font-weight: 500;
+}
+
+.tendency-value {
+ font-family: "Inter", sans-serif;
+ font-size: 17px;
+ color: #222;
+ font-weight: 700;
+}
+
+.fire-text {
+ color: #e53935;
+}
+.empty-text {
+ color: #aaa;
+ font-size: 15px;
+ font-weight: 400;
+}
+
+.tendency-tags-container {
+ margin-top: 4px;
+}
+
+.tags-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.tendency-tag {
+ background-color: #f8f9fa;
+ border: 1px solid #e9ecef;
+ border-radius: 6px;
+ padding: 6px 12px;
+ font-size: 14px;
+ color: #495057;
+ font-family: "Inter", sans-serif;
+ font-weight: 500;
+}
+
+.mypage-tendency-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.mypage-notification-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+}
+
+.mypage-notification-item {
+ display: flex;
+ justify-content: space-between;
+ padding: 15px 0;
+ border-bottom: 1px solid #f9f9f9;
+}
+.mypage-notification-item:last-child {
+ border-bottom: none;
+}
+.mypage-notification-item span:first-child {
+ font-weight: 600;
+ color: #444;
+ font-family: "Inter", sans-serif;
+}
+
+.active {
+ color: #f6910b;
+ font-weight: 700;
+}
+.inactive {
+ color: #ccc;
+ font-weight: 700;
+}
+
+.mypage-empty {
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 500;
+ color: #999;
+}
+
+.mypage-empty-state {
+ text-align: center;
+ padding: 60px;
+ color: #666;
+ font-family: "Inter", Helvetica;
+ font-size: 18px;
+ font-weight: 500;
+ width: 100%;
+}
+
+.mypage-login-required {
+ text-align: center;
+ padding: 80px 40px;
+ background-color: #f9f9f9;
+ border-radius: 20px;
+ width: 100%;
+ max-width: 600px;
+ margin: 40px auto;
+}
+
+.mypage-login-title {
+ font-family: "Inter", Helvetica;
+ font-size: 28px;
+ font-weight: 700;
+ color: #000;
+ margin-bottom: 20px;
+}
+
+.mypage-login-text {
+ font-size: 16px;
+ color: #666;
+ margin-bottom: 30px;
+}
+
+.mypage-login-btn {
+ padding: 14px 40px;
+ background-color: #f6910b;
+ border: none;
+ border-radius: 12px;
+ color: #ffffff;
+ cursor: pointer;
+ font-family: "Inter", Helvetica;
+ font-size: 16px;
+ font-weight: 700;
+ transition: background-color 0.2s;
+}
+
+.mypage-login-btn:hover {
+ background-color: #e58209;
+}
+
+@media (max-width: 1024px) {
+ .mypage-grid-container {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 600px) {
+ .mypage-info-content {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 15px;
+ }
+ .info-divider {
+ display: none;
+ }
+ .info-item {
+ width: 100%;
+ border-bottom: 1px solid #f9f9f9;
+ padding-bottom: 15px;
+ }
+ .info-item:last-child {
+ border-bottom: none;
+ }
+
+ .tendency-list {
+ flex-direction: column;
+ gap: 20px;
+ }
+ .tendency-item,
+ .tendency-item.item-wide {
+ width: 100%;
+ border-bottom: 1px solid #f9f9f9;
+ padding-bottom: 20px;
+ }
+ .tendency-item:last-child {
+ border-bottom: none;
+ }
+}
+.mypage-edit-btn {
+ padding: 6px 14px;
+ font-size: 13px;
+ font-weight: 500;
+ color: #666;
+ background-color: white;
+ border: 1px solid #ddd;
+ border-radius: 20px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.mypage-edit-btn:hover {
+ background-color: #f5f5f5;
+ color: #333;
+ border-color: #bbb;
+ transform: translateY(-1px);
+}
+
+.mypage-textcontainer {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
diff --git a/src/screens/RecipePage/RecipePage.jsx b/src/screens/RecipePage/RecipePage.jsx
new file mode 100644
index 0000000..4a07dfd
--- /dev/null
+++ b/src/screens/RecipePage/RecipePage.jsx
@@ -0,0 +1,149 @@
+import React, { useState } from "react";
+import { useLocation, useParams, useNavigate } from "react-router-dom";
+import { UnifiedHeader } from "../../components/UnifiedHeader";
+import RecipeReview from "./RecipeReview/RecipeReview";
+import "./style.css";
+
+export const RecipePage = () => {
+ const location = useLocation();
+ const { id } = useParams();
+ const navigate = useNavigate();
+ const [isReviewOpen, setIsReviewOpen] = useState(false);
+
+ const stateRecipe = location.state?.recipe;
+
+ const processIngredients = (data) => {
+ if (!data) return [];
+ if (Array.isArray(data)) return data;
+ if (typeof data === "string") {
+ return data
+ .split(",")
+ .map((item) => item.trim())
+ .filter((item) => item !== "");
+ }
+ return [];
+ };
+
+ const processSteps = (data) => {
+ if (!data) return [];
+
+ let list = [];
+ if (Array.isArray(data)) {
+ list = data;
+ } else if (typeof data === "string") {
+ list = data
+ .split("\n")
+ .map((item) => item.trim())
+ .filter((item) => item !== "");
+ }
+
+ const refinedList = [];
+ list.forEach((item) => {
+ const matches = item.split(/(\d+\.\s+)/).filter(Boolean);
+ if (matches.length > 1) {
+ let currentStep = "";
+ matches.forEach((fragment) => {
+ if (/^\d+\.\s+$/.test(fragment)) {
+ if (currentStep) refinedList.push(currentStep.trim());
+ currentStep = "";
+ } else {
+ currentStep += fragment;
+ }
+ });
+ if (currentStep) refinedList.push(currentStep.trim());
+ } else {
+ refinedList.push(item);
+ }
+ });
+
+ return refinedList.filter((item) => item.length > 1);
+ };
+
+ const recipe = {
+ id: stateRecipe?.id || id,
+ name: stateRecipe?.name || "레시피 이름 없음",
+ description: stateRecipe?.description || "레시피 설명이 없습니다.",
+ ingredients: processIngredients(stateRecipe?.ingredients),
+ steps: processSteps(stateRecipe?.instructions || stateRecipe?.steps),
+ };
+
+ if (!stateRecipe && !id) {
+ return (
+
+
레시피 정보를 불러올 수 없습니다.
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
{recipe.name}
+
+
+ {recipe.ingredients.length > 0 ? (
+ recipe.ingredients.map((ing, index) => (
+
+ {ing}
+
+ ))
+ ) : (
+ 재료 정보가 없습니다.
+ )}
+
+
+
+
+
레시피 소개
+
{recipe.description}
+
+
+
+
조리 순서
+
+ {recipe.steps.length > 0 ? (
+ recipe.steps.map((step, index) => (
+
+
{index + 1}
+
+ {step.replace(/^\d+\.\s*/, "")}
+
+
+ ))
+ ) : (
+
+ 조리 방법 데이터가 없습니다.
+
+ )}
+
+
+
+
+
+
+
+ {isReviewOpen && (
+
setIsReviewOpen(false)}
+ name={stateRecipe.name}
+ />
+ )}
+
+ );
+};
diff --git a/src/screens/RecipePage/RecipeReview/RecipeReview.jsx b/src/screens/RecipePage/RecipeReview/RecipeReview.jsx
new file mode 100644
index 0000000..bf05afc
--- /dev/null
+++ b/src/screens/RecipePage/RecipeReview/RecipeReview.jsx
@@ -0,0 +1,109 @@
+import React, { useState } from "react";
+import { MdRateReview } from "react-icons/md";
+import "./style.css";
+import { useAuth } from "../../../contexts/AuthContext";
+import { makeReview } from "../../../api/Community/recipeReview";
+
+const RecipeReview = ({ onClose, name }) => {
+ const { isAuthenticated } = useAuth();
+ const [content, setContent] = useState("");
+ const [rating, setRating] = useState(0);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (!isAuthenticated) {
+ alert("로그인 안해서 리뷰 작성 불가");
+ }
+ const review = {
+ name,
+ rating,
+ content,
+ };
+ try {
+ const response = await makeReview(review);
+ if (response) {
+ alert("리뷰가 등록되었습니다!");
+ } else {
+ console.log("리뷰 실패");
+ }
+ } catch (error) {
+ console.error("서버 에라");
+ alert("서버 에러로 인한 리뷰 등록 실패");
+ } finally {
+ onClose();
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
+
+
+
+
레시피 후기 작성
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default RecipeReview;
diff --git a/src/screens/RecipePage/RecipeReview/style.css b/src/screens/RecipePage/RecipeReview/style.css
new file mode 100644
index 0000000..c8365b8
--- /dev/null
+++ b/src/screens/RecipePage/RecipeReview/style.css
@@ -0,0 +1,165 @@
+.review-popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn 0.2s ease-in-out;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+
+ .review-popup-container {
+ background-color: #ffffff;
+ border-radius: 20px;
+ padding: 40px;
+ width: 90%;
+ max-width: 500px;
+ position: relative;
+ box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease-out;
+ max-height: 90vh;
+ overflow-y: auto;
+ }
+
+ @keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+ }
+
+ .review-popup-close {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ background: none;
+ border: none;
+ font-size: 32px;
+ color: #666666;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+ }
+
+ .review-popup-close:hover {
+ color: #000000;
+ }
+
+ .review-popup-header {
+ margin-bottom: 30px;
+ text-align: center;
+ }
+
+ .review-popup-title {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 24px;
+ font-weight: 700;
+ color: #000000;
+ margin: 0;
+ }
+
+ .review-popup-form {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ }
+
+ .review-popup-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .review-popup-label {
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ color: #000000;
+ }
+
+ .review-popup-input,
+ .review-popup-textarea {
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 16px;
+ transition: border-color 0.2s;
+ background-color: #fff;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ .review-popup-textarea {
+ min-height: 120px;
+ resize: vertical;
+ }
+
+ .review-popup-input:focus,
+ .review-popup-textarea:focus {
+ outline: none;
+ border-color: #f6910b;
+ }
+
+ .review-popup-input::placeholder,
+ .review-popup-textarea::placeholder {
+ color: #999999;
+ }
+
+ .review-popup-submit-btn {
+ padding: 14px;
+ background-color: #f6910b;
+ color: #ffffff;
+ border: none;
+ border-radius: 12px;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ margin-top: 10px;
+ }
+
+ .review-popup-submit-btn:hover {
+ background-color: #e58209;
+ }
+
+ .review-popup-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 10px;
+ }
+
+ .review-popup-cancel-btn {
+ background: none;
+ border: none;
+ color: #666666;
+ font-family: "Inter", Helvetica, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ text-decoration: underline;
+ }
+
+ .review-popup-cancel-btn:hover {
+ color: #333333;
+ }
\ No newline at end of file
diff --git a/src/screens/RecipePage/index.js b/src/screens/RecipePage/index.js
new file mode 100644
index 0000000..7c5922e
--- /dev/null
+++ b/src/screens/RecipePage/index.js
@@ -0,0 +1 @@
+export { RecipePage } from "./RecipePage";
diff --git a/src/screens/RecipePage/style.css b/src/screens/RecipePage/style.css
new file mode 100644
index 0000000..cdaeaf6
--- /dev/null
+++ b/src/screens/RecipePage/style.css
@@ -0,0 +1,363 @@
+/* style.css - 이미지 제거 버전 (최종 수정) */
+
+/* 전체 페이지 컨테이너 */
+.recipe-page {
+ background-color: #ffffff;
+ min-height: 100vh;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, sans-serif;
+}
+
+/* 콘텐츠 중앙 정렬 래퍼 */
+.recipe-content-wrapper {
+ width: 100%;
+ max-width: 800px; /* 본문 너비 적절히 제한 */
+ margin: 0 auto;
+ padding: 40px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+}
+
+/* 1. 요리 제목 영역 */
+.recipe-header-section {
+ text-align: center;
+ border-bottom: 2px solid #f6910b; /* 주황색 포인트 라인 */
+ padding-bottom: 20px;
+ margin-bottom: 10px;
+}
+
+.recipe-title {
+ color: #333;
+ font-size: 32px;
+ font-weight: 800;
+ margin-bottom: 15px;
+ word-break: keep-all;
+}
+
+/* 2. 재료 태그 영역 */
+.ingredients-section {
+ display: flex;
+ flex-wrap: wrap; /* 줄바꿈 허용 (중요) */
+ justify-content: center;
+ gap: 8px; /* 태그 사이 간격 */
+ margin-bottom: 10px;
+}
+
+.ingredient-badge {
+ background-color: #fff3e0; /* 연한 주황 배경 */
+ color: #f6910b; /* 진한 주황 글씨 */
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-size: 15px;
+ font-weight: 600;
+ border: 1px solid #ffe0b2;
+ white-space: nowrap; /* 글자 줄바꿈 방지 */
+}
+
+/* 3. 레시피 설명 박스 (이미지 없이 꽉 채움) */
+.description-section {
+ background-color: #f9f9f9;
+ border-radius: 12px;
+ padding: 30px;
+ border-left: 5px solid #f6910b; /* 왼쪽 강조선 */
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+}
+
+.section-label {
+ font-size: 20px;
+ font-weight: 700;
+ color: #333;
+ margin-bottom: 15px;
+ display: block;
+}
+
+.description-text {
+ font-size: 16px;
+ line-height: 1.7; /* 줄간격 넉넉하게 */
+ color: #444;
+ word-break: keep-all;
+}
+
+/* 4. 조리 순서 영역 */
+.steps-section {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.step-card {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+ background-color: #ffffff;
+ padding: 20px;
+ border-radius: 12px;
+ border: 1px solid #eeeeee;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.03);
+ transition: transform 0.2s, border-color 0.2s;
+}
+
+.step-card:hover {
+ transform: translateY(-2px);
+ border-color: #f6910b;
+}
+
+.step-number {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f6910b;
+ color: #ffffff;
+ border-radius: 50%;
+ min-width: 28px;
+ height: 28px;
+ font-size: 14px;
+ font-weight: 700;
+ margin-top: 2px; /* 텍스트와 높이 맞춤 */
+}
+
+.step-content {
+ flex: 1;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 1.6;
+ color: #333;
+ word-break: keep-all;
+}
+
+/* 5. 리뷰 버튼 */
+.button-section {
+ text-align: center;
+ margin-top: 20px;
+}
+
+.review-btn {
+ background-color: #f6910b;
+ color: white;
+ border: none;
+ padding: 15px 50px;
+ font-size: 18px;
+ font-weight: 700;
+ border-radius: 30px;
+ cursor: pointer;
+ box-shadow: 0 4px 10px rgba(246, 145, 11, 0.3);
+ transition: all 0.2s;
+}
+
+.review-btn:hover {
+ background-color: #e08200;
+ transform: translateY(-2px);
+}
+
+/* 모바일 대응 */
+@media (max-width: 768px) {
+ .recipe-content-wrapper {
+ padding: 20px;
+ }
+ .recipe-title {
+ font-size: 26px;
+ }
+ .description-section {
+ padding: 20px;
+ }
+}
+/* style.css - 뒤로가기 버튼 추가됨 */
+
+.recipe-page {
+ background-color: #ffffff;
+ min-height: 100vh;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, sans-serif;
+}
+
+.recipe-content-wrapper {
+ width: 100%;
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 20px 20px 60px; /* 상단 패딩 줄임 */
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+}
+
+/* --- [추가] 뒤로 가기 버튼 스타일 --- */
+.back-button-area {
+ width: 100%;
+ display: flex;
+ justify-content: flex-start;
+ margin-bottom: 10px;
+}
+
+.back-link-btn {
+ background: none;
+ border: none;
+ font-size: 16px;
+ color: #666;
+ cursor: pointer;
+ padding: 8px 0;
+ display: flex;
+ align-items: center;
+ font-weight: 600;
+ transition: color 0.2s;
+}
+
+.back-link-btn:hover {
+ color: #f6910b; /* 호버 시 포인트 컬러 */
+}
+
+/* 에러 화면용 뒤로가기 버튼 */
+.error-back-btn {
+ padding: 10px 20px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ margin-top: 15px;
+}
+
+.recipe-header-section {
+ text-align: center;
+ border-bottom: 2px solid #f6910b;
+ padding-bottom: 20px;
+ margin-bottom: 10px;
+}
+
+.recipe-title {
+ color: #333;
+ font-size: 32px;
+ font-weight: 800;
+ margin-bottom: 15px;
+ word-break: keep-all;
+}
+
+.ingredients-section {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+ margin-bottom: 10px;
+}
+
+.ingredient-badge {
+ background-color: #fff3e0;
+ color: #f6910b;
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-size: 15px;
+ font-weight: 600;
+ border: 1px solid #ffe0b2;
+ white-space: nowrap;
+}
+
+.description-section {
+ background-color: #f9f9f9;
+ border-radius: 12px;
+ padding: 30px;
+ border-left: 5px solid #f6910b;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+}
+
+.section-label {
+ font-size: 20px;
+ font-weight: 700;
+ color: #333;
+ margin-bottom: 15px;
+ display: block;
+}
+
+.description-text {
+ font-size: 16px;
+ line-height: 1.7;
+ color: #444;
+ word-break: keep-all;
+}
+
+.steps-section {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.step-card {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+ background-color: #ffffff;
+ padding: 20px;
+ border-radius: 12px;
+ border: 1px solid #eeeeee;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.03);
+ transition: transform 0.2s, border-color 0.2s;
+}
+
+.step-card:hover {
+ transform: translateY(-2px);
+ border-color: #f6910b;
+}
+
+.step-number {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f6910b;
+ color: #ffffff;
+ border-radius: 50%;
+ min-width: 28px;
+ height: 28px;
+ font-size: 14px;
+ font-weight: 700;
+ margin-top: 2px;
+}
+
+.step-content {
+ flex: 1;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 1.6;
+ color: #333;
+ word-break: keep-all;
+}
+
+.button-section {
+ text-align: center;
+ margin-top: 20px;
+}
+
+.review-btn {
+ background-color: #f6910b;
+ color: white;
+ border: none;
+ padding: 15px 50px;
+ font-size: 18px;
+ font-weight: 700;
+ border-radius: 30px;
+ cursor: pointer;
+ box-shadow: 0 4px 10px rgba(246, 145, 11, 0.3);
+ transition: all 0.2s;
+}
+
+.review-btn:hover {
+ background-color: #e08200;
+ transform: translateY(-2px);
+}
+
+/* 모바일 대응 */
+@media (max-width: 768px) {
+ .recipe-content-wrapper {
+ padding: 20px;
+ }
+ .recipe-title {
+ font-size: 26px;
+ }
+ .description-section {
+ padding: 20px;
+ }
+}
diff --git a/static/_redirects b/static/_redirects
new file mode 100644
index 0000000..7797f7c
--- /dev/null
+++ b/static/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
diff --git a/vite.config.js b/vite.config.js
index 4efe189..154431f 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,13 +1,11 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+import { screenGraphPlugin } from "@animaapp/vite-plugin-screen-graph";
// https://vite.dev/config/
-export default defineConfig({
- plugins: [
- react({
- babel: {
- plugins: [['babel-plugin-react-compiler']],
- },
- }),
- ],
-})
+export default defineConfig(({ mode }) => ({
+ plugins: [react(), mode === "development" && screenGraphPlugin()],
+ publicDir: "./static",
+ base: "./",
+}));