diff --git a/ddip/.gitignore b/.gitignore
similarity index 96%
rename from ddip/.gitignore
rename to .gitignore
index 5ef6a52..9135c73 100644
--- a/ddip/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
# next.js
/.next/
/out/
+/next-container/
# production
/build
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..3d44941
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Ʈ õ
+/shelf/
+/workspace.xml
+# HTTP Ŭ̾Ʈ û
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/ddip.iml b/.idea/ddip.iml
new file mode 100644
index 0000000..f9f881e
--- /dev/null
+++ b/.idea/ddip.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..8fefff1
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..282b76e
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..8306744
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..90686c1
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,458 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1749200115075
+
+
+ 1749200115075
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "lastFilter": {
+ "state": "OPEN",
+ "assignee": "ArgX2"
+ }
+}
+ {
+ "selectedUrlAndAccountId": {
+ "url": "https://github.com/Siul49/DDIP.git",
+ "accountId": "72c675de-739d-426a-b925-26941b57e4d3"
+ }
+}
+ {
+ "associatedIndex": 7
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1748258372673
+
+
+ 1748258372673
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "lastFilter": {
+ "state": "OPEN",
+ "assignee": "ArgX2"
+ }
+}
+ {
+ "selectedUrlAndAccountId": {
+ "url": "https://github.com/Siul49/DDIP.git",
+ "accountId": "72c675de-739d-426a-b925-26941b57e4d3"
+ }
+}
+ {
+ "associatedIndex": 7
+}
+
+
+
+
+
+
+
+
+ {
+ "keyToString": {
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "git-widget-placeholder": "release-0.2-refactory",
+ "js.debugger.nextJs.config.created.client": "true",
+ "js.debugger.nextJs.config.created.server": "true",
+ "last_opened_file_path": "C:/Users/pkpk0/WebstormProjects/ddip/src/app/components",
+ "node.js.detected.package.eslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "ts.external.directory.path": "C:\\Users\\pkpk0\\AppData\\Local\\Programs\\WebStorm\\plugins\\javascript-plugin\\jsLanguageServicesImpl\\external",
+ "vue.rearranger.settings.migration": "true"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1748258372673
+
+
+ 1748258372673
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7d1840d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+# DDIP
+
+## 현재 구현기능
+### FE
+- 로그인
+- 회원가입
+- 로그아웃
+
+
+### BE
+- 로그인
+- 회원가입
+- 로그아웃
+
diff --git a/ddip/README.md b/ddip/README.md
deleted file mode 100644
index 66bb426..0000000
--- a/ddip/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
-
-## Getting Started
-
-First, run the development server:
-
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
-
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-
-You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
-
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
-
-## Learn More
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/ddip/jsconfig.json b/ddip/jsconfig.json
deleted file mode 100644
index 774c46b..0000000
--- a/ddip/jsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "compilerOptions": {
- "baseUrl": "src",
- "paths": {
- "@home/*": ["app/home/*"]
- }
- }
-}
diff --git a/ddip/public/DDIP.png b/ddip/public/DDIP.png
deleted file mode 100644
index 255b5fe..0000000
Binary files a/ddip/public/DDIP.png and /dev/null differ
diff --git a/ddip/public/smolDDIP.png b/ddip/public/smolDDIP.png
deleted file mode 100644
index 9a24145..0000000
Binary files a/ddip/public/smolDDIP.png and /dev/null differ
diff --git a/ddip/src/app/home/category.js b/ddip/src/app/home/category.js
deleted file mode 100644
index 5a3b5e2..0000000
--- a/ddip/src/app/home/category.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import Image from 'next/image'
-import { useState } from 'react';
-
-export function Category({onSelect}) {
-
- const [activeIndex, setActiveIndex] = useState(null);
- const handleClick = (selected_index) => {
- setActiveIndex(selected_index);
- if (onSelect) onSelect(selected_index);
- };
- return (
-
- 카테고리
-
- {Array.from({ length: 6 }).map((_,index_category) => (
- handleClick(index_category+1)} className="relative w-full h-full">
-
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/ddip/src/app/home/item.js b/ddip/src/app/home/item.js
deleted file mode 100644
index c6126f0..0000000
--- a/ddip/src/app/home/item.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Image from 'next/image'
-
-export function Item({onSelect}) {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/ddip/src/app/home/main.js b/ddip/src/app/home/main.js
deleted file mode 100644
index 269c3be..0000000
--- a/ddip/src/app/home/main.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Image from 'next/image'
-
-export function Main() {
-
- return (
-
-
-
-
-
-
-
-
-
-
- SEARCH
-
-
-
-
- {Array.from({ length: 10 }).map((_, index) => (
-
- ))}
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/ddip/src/app/home/nav.js b/ddip/src/app/home/nav.js
deleted file mode 100644
index 6ec828c..0000000
--- a/ddip/src/app/home/nav.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Image from 'next/image';
-
-import {useEffect, useState} from "react";
-
-export function Navs() {
- const [moving,setMoved] = useState(false);
-
- useEffect(() => {
- const scrolled = () => {
- setMoved(window.scrollY > 50);
- };
- window.addEventListener('scroll', scrolled);
- return () => window.removeEventListener('scroll', scrolled);
- }, []);
-
- return (
- <>
-
- window.location.reload()}
- className="absolute w-32 left-3 text-center text-green-500 rounded-2xl cursor-pointer">
-
-
-
-
-
-
- 로그인/회원가입
-
-
-
- >
- )
-}
\ No newline at end of file
diff --git a/ddip/src/app/home/products.js b/ddip/src/app/home/products.js
deleted file mode 100644
index 842fbe1..0000000
--- a/ddip/src/app/home/products.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import Image from 'next/image'
-import {useState} from "react";
-
-export function Products({onSelect}) {
- const [activeIndex, setActiveIndex] = useState(null);
- const handleClick = (selected_index) => {
- setActiveIndex(selected_index);
- if (onSelect) onSelect(selected_index);
- };
- return (
-
-
- 상품 목록
-
- {Array.from({ length: 16 }).map((_, indexProduct) => (
-
handleClick(indexProduct+1)} >
-
-
-
-
- ))}
-
-
- )
-}
\ No newline at end of file
diff --git a/ddip/src/app/page.js b/ddip/src/app/page.js
deleted file mode 100644
index f4e5acd..0000000
--- a/ddip/src/app/page.js
+++ /dev/null
@@ -1,53 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import { Navs } from '@home/nav';
-import { Main } from '@home/main';
-import {Category} from '@home/category';
-import {Products} from '@home/products';
-import {Item} from '@home/item';
-
-
-
-export default function Home() {
- const [mode, setMode] = useState('home');
- const [selectedCategory, setSelectedCategory] = useState(null);
- const [selectedProduct, setSelectedProduct] = useState(null);
-
-
- return (
-
-
-
- {mode === 'home' && (
- <>
-
-
-
{
- setSelectedCategory(index);
- setMode('category');
- }} />
- >
- )}
-
- {mode === 'category' && (
- <>
-
-
-
- {
- setSelectedProduct(index);
- setMode('product');
- }} />
- >
- )}
-
- {mode === 'product' && (
- <>
-
- >
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/ddip/eslint.config.mjs b/eslint.config.mjs
similarity index 100%
rename from ddip/eslint.config.mjs
rename to eslint.config.mjs
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..e954206
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "baseUrl": "src",
+ "paths": {
+ "@home/*": ["app/feature/home/*"],
+ "@constants/*": ["constants/*"],
+ "@auth/*": ["app/feature/auth/*"],
+ "@components/*": ["app/components/*"]
+ }
+ }
+}
diff --git a/ddip/next.config.mjs b/next.config.mjs
similarity index 73%
rename from ddip/next.config.mjs
rename to next.config.mjs
index 4678774..8c1b65a 100644
--- a/ddip/next.config.mjs
+++ b/next.config.mjs
@@ -1,4 +1,6 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {};
+const nextConfig = {
+
+};
export default nextConfig;
diff --git a/ddip/package-lock.json b/package-lock.json
similarity index 91%
rename from ddip/package-lock.json
rename to package-lock.json
index 66ca4ad..6abf482 100644
--- a/ddip/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,16 @@
"name": "nexttest",
"version": "0.1.0",
"dependencies": {
+ "bcryptjs": "^3.0.2",
+ "cookie": "^1.0.2",
+ "iron-session": "^8.0.4",
+ "mongodb": "^6.16.0",
+ "mongoose": "^8.15.1",
"next": "15.3.2",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "socket.io": "^4.8.1",
+ "socket.io-client": "^4.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -749,6 +756,15 @@
"node": ">=18"
}
},
+ "node_modules/@mongodb-js/saslprep": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz",
+ "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==",
+ "license": "MIT",
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz",
@@ -968,6 +984,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -1270,6 +1292,15 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/cors": {
+ "version": "2.8.18",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz",
+ "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@@ -1291,6 +1322,30 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "22.15.21",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
+ "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.32.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz",
@@ -2083,6 +2138,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
+ "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
@@ -2128,6 +2201,15 @@
"node": ">=8"
}
},
+ "node_modules/bson": {
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz",
+ "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.20.1"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -2338,13 +2420,12 @@
}
},
"node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "dev": true,
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">=18"
}
},
"node_modules/cookie-signature": {
@@ -2361,7 +2442,6 @@
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"object-assign": "^4",
@@ -2451,7 +2531,6 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -2580,6 +2659,134 @@
"node": ">= 0.8"
}
},
+ "node_modules/engine.io": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+ "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.7.2",
+ "cors": "~2.8.5",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-client": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1",
+ "xmlhttprequest-ssl": "~2.1.1"
+ }
+ },
+ "node_modules/engine.io-client/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/engine.io/node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/engine.io/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/engine.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io/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/engine.io/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": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/engine.io/node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -3308,6 +3515,16 @@
"express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3861,6 +4078,39 @@
"node": ">= 0.10"
}
},
+ "node_modules/iron-session": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.4.tgz",
+ "integrity": "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA==",
+ "funding": [
+ "https://github.com/sponsors/vvo",
+ "https://github.com/sponsors/brc-dd"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^0.7.2",
+ "iron-webcrypto": "^1.2.1",
+ "uncrypto": "^0.1.3"
+ }
+ },
+ "node_modules/iron-session/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/iron-webcrypto": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
+ "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/brc-dd"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4375,6 +4625,15 @@
"node": ">=4.0"
}
},
+ "node_modules/kareem": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
+ "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4724,6 +4983,12 @@
"node": ">= 0.8"
}
},
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "license": "MIT"
+ },
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
@@ -4846,11 +5111,109 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/mongodb": {
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz",
+ "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.1.9",
+ "bson": "^6.10.3",
+ "mongodb-connection-string-url": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.188.0",
+ "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
+ "gcp-metadata": "^5.2.0",
+ "kerberos": "^2.0.1",
+ "mongodb-client-encryption": ">=6.0.0 <7",
+ "snappy": "^7.2.2",
+ "socks": "^2.7.1"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
+ "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/whatwg-url": "^11.0.2",
+ "whatwg-url": "^14.1.0 || ^13.0.0"
+ }
+ },
+ "node_modules/mongoose": {
+ "version": "8.15.1",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.1.tgz",
+ "integrity": "sha512-RhQ4DzmBi5BNGcS0w4u1vdMRIKcteXTCNzDt1j7XRcdWYBz1MjMjulBhPaeC5jBCHOD1yinuOFTTSOWLLGexWw==",
+ "license": "MIT",
+ "dependencies": {
+ "bson": "^6.10.3",
+ "kareem": "2.6.3",
+ "mongodb": "~6.16.0",
+ "mpath": "0.9.0",
+ "mquery": "5.0.0",
+ "ms": "2.1.3",
+ "sift": "17.1.3"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mongoose"
+ }
+ },
+ "node_modules/mpath": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mquery": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
+ "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4.x"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -4990,7 +5353,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -5368,7 +5730,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -5939,6 +6300,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sift": {
+ "version": "17.1.3",
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
+ "license": "MIT"
+ },
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -5949,6 +6316,173 @@
"is-arrayish": "^0.3.1"
}
},
+ "node_modules/socket.io": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+ "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.3.2",
+ "engine.io": "~6.6.0",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+ "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "~4.3.4",
+ "ws": "~8.17.1"
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-client": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-client/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io/node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/socket.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io/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/socket.io/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": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/socket.io/node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5958,6 +6492,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
"node_modules/stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -6271,6 +6814,18 @@
"node": ">=0.6"
}
},
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -6443,6 +6998,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/uncrypto": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
+ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -6500,12 +7067,33 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6628,6 +7216,35 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
diff --git a/ddip/package.json b/package.json
similarity index 60%
rename from ddip/package.json
rename to package.json
index 4462b71..d25744a 100644
--- a/ddip/package.json
+++ b/package.json
@@ -9,15 +9,20 @@
"lint": "next lint"
},
"dependencies": {
+ "bcryptjs": "^3.0.2",
+ "cookie": "^1.0.2",
+ "iron-session": "^8.0.4",
+ "mongodb": "^6.16.0",
+ "mongoose": "^8.15.1",
+ "next": "15.3.2",
"react": "^19.0.0",
- "react-dom": "^19.0.0",
- "next": "15.3.2"
+ "react-dom": "^19.0.0"
},
"devDependencies": {
+ "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
- "tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.3.2",
- "@eslint/eslintrc": "^3"
+ "tailwindcss": "^4"
}
}
diff --git a/ddip/postcss.config.mjs b/postcss.config.mjs
similarity index 100%
rename from ddip/postcss.config.mjs
rename to postcss.config.mjs
diff --git a/public/DDIP.png b/public/DDIP.png
new file mode 100644
index 0000000..a73e837
Binary files /dev/null and b/public/DDIP.png differ
diff --git a/public/DDIP_yellow.png b/public/DDIP_yellow.png
new file mode 100644
index 0000000..6563c59
Binary files /dev/null and b/public/DDIP_yellow.png differ
diff --git a/public/background.png b/public/background.png
new file mode 100644
index 0000000..08b903d
Binary files /dev/null and b/public/background.png differ
diff --git a/public/categoryImg/deliver.png b/public/categoryImg/deliver.png
new file mode 100644
index 0000000..83f0bbb
Binary files /dev/null and b/public/categoryImg/deliver.png differ
diff --git a/public/categoryImg/donate.png b/public/categoryImg/donate.png
new file mode 100644
index 0000000..3d45ab3
Binary files /dev/null and b/public/categoryImg/donate.png differ
diff --git a/public/categoryImg/ingredient.png b/public/categoryImg/ingredient.png
new file mode 100644
index 0000000..ba3490c
Binary files /dev/null and b/public/categoryImg/ingredient.png differ
diff --git a/public/categoryImg/instant.png b/public/categoryImg/instant.png
new file mode 100644
index 0000000..c07b2c6
Binary files /dev/null and b/public/categoryImg/instant.png differ
diff --git a/public/categoryImg/large.png b/public/categoryImg/large.png
new file mode 100644
index 0000000..18168cf
Binary files /dev/null and b/public/categoryImg/large.png differ
diff --git a/public/categoryImg/stuffs.png b/public/categoryImg/stuffs.png
new file mode 100644
index 0000000..33f5b5b
Binary files /dev/null and b/public/categoryImg/stuffs.png differ
diff --git a/public/chat.png b/public/chat.png
new file mode 100644
index 0000000..703228f
Binary files /dev/null and b/public/chat.png differ
diff --git a/ddip/public/file.svg b/public/file.svg
similarity index 100%
rename from ddip/public/file.svg
rename to public/file.svg
diff --git a/public/fonts/PretendardVariable.woff2 b/public/fonts/PretendardVariable.woff2
new file mode 100644
index 0000000..49c54b5
Binary files /dev/null and b/public/fonts/PretendardVariable.woff2 differ
diff --git a/ddip/public/globe.svg b/public/globe.svg
similarity index 100%
rename from ddip/public/globe.svg
rename to public/globe.svg
diff --git a/ddip/public/next.svg b/public/next.svg
similarity index 100%
rename from ddip/public/next.svg
rename to public/next.svg
diff --git a/public/search.png b/public/search.png
new file mode 100644
index 0000000..34d6d76
Binary files /dev/null and b/public/search.png differ
diff --git a/ddip/public/testimage.png b/public/testimage.png
similarity index 100%
rename from ddip/public/testimage.png
rename to public/testimage.png
diff --git a/ddip/public/textured-paper.png b/public/textured-paper.png
similarity index 100%
rename from ddip/public/textured-paper.png
rename to public/textured-paper.png
diff --git a/ddip/public/vercel.svg b/public/vercel.svg
similarity index 100%
rename from ddip/public/vercel.svg
rename to public/vercel.svg
diff --git a/ddip/public/window.svg b/public/window.svg
similarity index 100%
rename from ddip/public/window.svg
rename to public/window.svg
diff --git a/public/write.png b/public/write.png
new file mode 100644
index 0000000..379a59d
Binary files /dev/null and b/public/write.png differ
diff --git a/src/app/api/auth/check/route.js b/src/app/api/auth/check/route.js
new file mode 100644
index 0000000..50ff8f8
--- /dev/null
+++ b/src/app/api/auth/check/route.js
@@ -0,0 +1,15 @@
+// app/api/auth/check/route.js
+import { NextResponse } from 'next/server'
+import { decrypt } from '../../../../lib/session'
+
+export async function GET(request) {
+ try {
+ const session = request.cookies.get('session').value
+ if (!session) return NextResponse.json({ user: null })
+
+ const user = await decrypt(session)
+ return NextResponse.json({ user })
+ } catch (error) {
+ return NextResponse.json({ user: null })
+ }
+}
diff --git a/src/app/api/auth/login/route.js b/src/app/api/auth/login/route.js
new file mode 100644
index 0000000..683e8b4
--- /dev/null
+++ b/src/app/api/auth/login/route.js
@@ -0,0 +1,63 @@
+import { NextResponse } from 'next/server';
+import dbConnect from '../../../../lib/dbConnect';
+import bcrypt from 'bcryptjs';
+import User from '../../../../models/User';
+import { encrypt } from '../../../../lib/session';
+
+export async function POST(request) {
+ try {
+ const { userid, userpw } = await request.json();
+
+ if (!userid || !userpw) {
+ return NextResponse.json(
+ { success: false, message: '아이디와 비밀번호를 모두 입력해주세요.' },
+ { status: 400 }
+ );
+ }
+
+ await dbConnect();
+
+ const user = await User.findOne({ userid });
+ if (!user) {
+ return NextResponse.json(
+ { success: false, message: '존재하지 않는 아이디입니다.' },
+ { status: 404 }
+ );
+ }
+
+ const isMatch = await bcrypt.compare(userpw, user.password);
+
+ if (!isMatch) {
+ return NextResponse.json(
+ { success: false, message: '비밀번호가 일치하지 않습니다.' },
+ { status: 401 }
+ );
+ }
+
+
+ const session = await encrypt({ nickname: user.nickname });
+
+ // 응답 객체 생성
+ const response = NextResponse.json(
+ { success: true, nickname: user.nickname },
+ { status: 200 }
+ );
+
+ // 쿠키 설정
+ response.cookies.set('session', session, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: 'lax',
+ path: '/',
+ maxAge: 60 * 60 * 24 * 7,
+ });
+
+ return response;
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { success: false, message: '서버 에러 발생' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/auth/logout/route.js b/src/app/api/auth/logout/route.js
new file mode 100644
index 0000000..d6a037a
--- /dev/null
+++ b/src/app/api/auth/logout/route.js
@@ -0,0 +1,14 @@
+import { NextResponse } from 'next/server';
+
+export async function POST() {
+ // 세션 쿠키를 만료시켜서 삭제
+ const response = NextResponse.json({ success: true });
+ response.cookies.set('session', '', {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: 'lax',
+ path: '/',
+ maxAge: 0, // 즉시 만료!
+ });
+ return response;
+}
diff --git a/src/app/api/auth/signup/route.js b/src/app/api/auth/signup/route.js
new file mode 100644
index 0000000..c3c410a
--- /dev/null
+++ b/src/app/api/auth/signup/route.js
@@ -0,0 +1,65 @@
+import { NextResponse } from 'next/server';
+import dbConnect from '../../../../lib/dbConnect';
+import bcrypt from 'bcryptjs';
+import User from '../../../../models/User';
+
+export async function POST(request) {
+ try {
+ await dbConnect(); // DB 연결
+
+ const data = await request.json();
+ const { userid, userpw, checkpw, username, nickname, phone, address, email } = data;
+
+ // 필수값 체크 (기존 코드와 동일)
+ if (!userid || !userpw || !checkpw || !username || !nickname || !phone || !address || !email) {
+ return NextResponse.json(
+ { success: false, message: '모든 빈칸을 채워주세요!' },
+ { status: 400 }
+ );
+ }
+ if (userpw !== checkpw) {
+ return NextResponse.json(
+ { success: false, message: '비밀번호를 다시 확인해주세요!' },
+ { status: 400 }
+ );
+ }
+
+ // 중복 체크 (Mongoose 메서드 사용)
+ const existingUser = await User.findOne({
+ $or: [{ userid }, { email }]
+ });
+
+ if (existingUser) {
+ return NextResponse.json(
+ { success: false, message: '이미 존재하는 아이디 또는 이메일입니다.' },
+ { status: 409 }
+ );
+ }
+
+ // 비밀번호 암호화
+ const hashedPw = await bcrypt.hash(userpw, 10);
+
+ // 데이터 삽입 (Mongoose 메서드 사용)
+ const newUser = await User.create({
+ userid,
+ password: hashedPw,
+ username,
+ nickname,
+ phone,
+ address,
+ email
+ });
+
+ return NextResponse.json(
+ { success: true, message: '회원가입이 완료되었습니다!' },
+ { status: 201 }
+ );
+
+ } catch (error) {
+ console.error('회원가입 에러:', error);
+ return NextResponse.json(
+ { success: false, message: '서버 에러: ' + error.message },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/chat/messages.js b/src/app/api/chat/messages.js
new file mode 100644
index 0000000..8008583
--- /dev/null
+++ b/src/app/api/chat/messages.js
@@ -0,0 +1,16 @@
+// pages/api/chat/messages.js
+import dbConnect from '../../../lib/dbConnect';
+import Message from '../../../models/Message';
+
+export default async function handler(req, res) {
+ const { roomId } = req.query;
+
+ try {
+ await dbConnect();
+ const messages = await Message.find({ roomId }).sort({ createdAt: 1 });
+ res.status(200).json(messages);
+ } catch (error) {
+ console.error('메시지 조회 실패:', error);
+ res.status(500).json({ error: '메시지 조회 실패' });
+ }
+}
diff --git a/src/app/api/chat/send-message.js b/src/app/api/chat/send-message.js
new file mode 100644
index 0000000..5e8b24b
--- /dev/null
+++ b/src/app/api/chat/send-message.js
@@ -0,0 +1,19 @@
+// pages/api/chat/send-messages.js
+import dbConnect from '../../../lib/dbConnect';
+import Message from '../../../models/Message';
+
+export default async function handler(req, res) {
+ if (req.method !== 'POST') {
+ return res.status(405).json({ error: 'Method not allowed' });
+ }
+
+ try {
+ await dbConnect();
+ const newMessage = new Message(req.body);
+ await newMessage.save();
+ res.status(201).json({ success: true });
+ } catch (error) {
+ console.error('메시지 저장 실패:', error);
+ res.status(500).json({ error: '메시지 저장 실패' });
+ }
+}
diff --git a/src/app/api/post/item/[id]/route.js b/src/app/api/post/item/[id]/route.js
new file mode 100644
index 0000000..729d4a7
--- /dev/null
+++ b/src/app/api/post/item/[id]/route.js
@@ -0,0 +1,30 @@
+import { NextResponse } from 'next/server';
+import dbConnect from '../../../../../lib/dbConnect';
+import Item from '../../../../../models/item';
+
+// params를 함수 두 번째 인자로 받는다!
+export async function GET(request, { params }) {
+ try {
+ await dbConnect();
+ const { id } = await params;
+ const item = await Item.findById(id);
+
+ if (!item) {
+ return NextResponse.json(
+ { success: false, message: '해당 상품을 찾을 수 없습니다.' },
+ { status: 404 }
+ );
+ }
+
+ // 단일 아이템 반환
+ return NextResponse.json(
+ { success: true, data: item },
+ { status: 200 }
+ );
+ } catch (error) {
+ return NextResponse.json(
+ { success: false, message: '서버 에러 발생' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/post/item/route.js b/src/app/api/post/item/route.js
new file mode 100644
index 0000000..fbb4734
--- /dev/null
+++ b/src/app/api/post/item/route.js
@@ -0,0 +1,18 @@
+import { NextResponse } from 'next/server';
+import dbConnect from '../../../../lib/dbConnect';
+import Item from '../../../../models/item';
+
+export async function GET(request) {
+ try {
+ await dbConnect();
+ const items = await Item.find({});
+ console.log(items);
+ // 반드시 JSON 형태로 응답!
+ return NextResponse.json(items, { status: 200 });
+ } catch (error) {
+ return NextResponse.json(
+ { success: false, message: '서버 에러 발생' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/post/write/route.js b/src/app/api/post/write/route.js
new file mode 100644
index 0000000..7d57d44
--- /dev/null
+++ b/src/app/api/post/write/route.js
@@ -0,0 +1,117 @@
+import dbConnect from '../../../../lib/dbConnect';
+import Item from '../../../../models/item';
+
+export async function POST(request) {
+ try {
+ await dbConnect();
+
+ const contentType = request.headers.get('content-type') || '';
+ let data;
+
+ if (contentType.includes('application/json')) {
+ data = await request.json();
+ } else if (contentType.includes('multipart/form-data')) {
+ const formData = await request.formData();
+ data = Object.fromEntries(formData.entries());
+
+ // 이미지 파일을 Base64 문자열로 변환
+ if (formData.has('image')) {
+ const file = formData.get('image');
+ if (file && typeof file.arrayBuffer === 'function') {
+ const arrayBuffer = await file.arrayBuffer();
+ const base64String = Buffer.from(arrayBuffer).toString('base64');
+ // MIME 타입 추출
+ const mimeType = file.type || 'image/png';
+ // data:image/png;base64,xxxxxx 형태로 저장
+ data.image = `data:${mimeType};base64,${base64String}`;
+ }
+ }
+ } else {
+ return new Response(
+ JSON.stringify({ success: false, message: '지원하지 않는 Content-Type' }),
+ { status: 400, headers: { "Content-Type": "application/json" } }
+ );
+ }
+
+ // 필수 필드 검증
+ const requiredFields = [
+ 'title',
+ 'description',
+ 'writer',
+ 'itemCategory',
+ 'totalNumberOfRecruits',
+ 'totalPrice',
+ 'pricePerEachPerson',
+ 'tradeType',
+ ];
+
+ const missingFields = requiredFields.filter(field => !data[field]);
+ if (missingFields.length > 0) {
+ return new Response(
+ JSON.stringify({
+ success: false,
+ message: `필수 입력값 누락: ${missingFields.join(', ')}`
+ }),
+ { status: 400, headers: { "Content-Type": "application/json" } }
+ );
+ }
+
+ // 숫자 필드 변환
+ const numberFields = [
+ 'totalPrice',
+ 'totalNumberOfRecruits',
+ 'pricePerEachPerson',
+ 'numberOfRecruitedPersonnel'
+ ];
+ numberFields.forEach(field => {
+ if (data[field]) data[field] = Number(data[field]);
+ });
+
+ // 이미지 Base64 문자열 처리 (크기 및 타입 체크)
+ if (data.image) {
+ // Base64 크기 계산 (실제 파일 크기와 다를 수 있음)
+ const base64Length = data.image.length;
+ const maxSize = 5 * 1024 * 1024; // 5MB
+ if (base64Length > maxSize) {
+ return new Response(
+ JSON.stringify({
+ success: false,
+ message: '이미지 크기는 5MB를 초과할 수 없습니다.'
+ }),
+ { status: 400, headers: { "Content-Type": "application/json" } }
+ );
+ }
+
+ // MIME 타입 유효성 검사
+ const validMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
+ if (data.image.startsWith('data:image')) {
+ const mimeType = data.image.split(';')[0].split(':')[1];
+ if (!validMimeTypes.includes(mimeType)) {
+ return new Response(
+ JSON.stringify({
+ success: false,
+ message: 'JPEG, PNG, GIF 형식만 지원합니다.'
+ }),
+ { status: 400, headers: { "Content-Type": "application/json" } }
+ );
+ }
+ }
+ }
+
+ const newItem = await Item.create(data);
+ return new Response(
+ JSON.stringify({ success: true, item: newItem }),
+ { status: 201, headers: { "Content-Type": "application/json" } }
+ );
+
+ } catch (error) {
+ console.error('서버 에러:', error);
+ return new Response(
+ JSON.stringify({
+ success: false,
+ message: error.message || '서버 처리 중 오류 발생'
+ }),
+ { status: 500, headers: { "Content-Type": "application/json" } }
+ );
+ }
+}
diff --git a/src/app/api/user/update/route.js b/src/app/api/user/update/route.js
new file mode 100644
index 0000000..9a4feab
--- /dev/null
+++ b/src/app/api/user/update/route.js
@@ -0,0 +1,59 @@
+// app/api/user/update/route.js
+import { NextResponse } from 'next/server'
+import { cookies } from 'next/headers'
+import { decrypt } from '../../../../lib/session' // 사용자님이 제공한 decrypt 함수 임포트
+import User from '../../../../models/User' // 실제 사용자 모델 경로로 수정
+
+export async function POST(request) {
+ try {
+ // 1. 쿠키에서 암호화된 세션 추출
+ const cookieStore = cookies()
+ const encryptedSession = cookieStore.get('auth_session')?.value
+
+ // 2. 세션 복호화 (사용자님 코드 사용)
+ const session = await decrypt(encryptedSession)
+ if (!session?.user?.id) {
+ return NextResponse.json(
+ { success: false, message: '로그인이 필요해요!' },
+ { status: 401 }
+ )
+ }
+
+ // 3. 요청 데이터 파싱
+ const formData = await request.json()
+
+ // 4. 데이터베이스 업데이트
+ const updatedUser = await User.findByIdAndUpdate(
+ session.user.id,
+ { $set: formData },
+ { new: true }
+ )
+
+ // 5. 사용자 없음 처리
+ if (!updatedUser) {
+ return NextResponse.json(
+ { success: false, message: '사용자를 찾을 수 없어요!' },
+ { status: 404 }
+ )
+ }
+
+ // 6. 성공 응답
+ return NextResponse.json({
+ success: true,
+ user: {
+ nickname: updatedUser.nickname,
+ userid: updatedUser.userid,
+ description: updatedUser.description,
+ address: updatedUser.address,
+ image: updatedUser.image
+ }
+ })
+
+ } catch (error) {
+ console.error('업데이트 실패:', error)
+ return NextResponse.json(
+ { success: false, message: '서버 오류가 발생했어요!' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/src/app/components/auth/agreement.js b/src/app/components/auth/agreement.js
new file mode 100644
index 0000000..ff24ce2
--- /dev/null
+++ b/src/app/components/auth/agreement.js
@@ -0,0 +1,45 @@
+// [id]/AgreementBox.jsx
+export default function AgreementBox({ agreed, onChange }) {
+ return (
+
개인정보 제공 동의
+
+
+
[개인정보 수집 및 이용 동의서]
+ 회사는 개인정보 보호법 등 관련 법령에 따라 이용자의 개인정보를 보호하고,
+ 적법하게 수집·이용하고자 아래와 같이 동의를 받고자 합니다.
+
+
+
1. 수집하는 개인정보 항목
+ 필수 항목: 이름, 별명, 전화번호, 전화번호, 비밀번호
+ 선택 항목: 프로필 이미지, 이메일 주소
+
+
2. 개인정보 수집·이용 목적
+ 회원 가입 및 본인 확인
+ 서비스 제공 및 맞춤형 정보 제공
+ 고객 문의 응대 및 분석자료 정리
+
+
3. 보유 및 이용 기간
+ 수집일로부터 회원정보 수집 및 이용 목적 달성 시까지 보관
+ 단, 관련 법령에 따라 보존이 필요한 경우에는 해당 기간 동안 보존
+
+
4. 동의 거부 권리 및 불이익
+ 귀하는 개인정보 수집·이용에 동의하지 않을 수 있습니다.
+ 단, 필수 항목에 대한 동의를 거부할 경우 서비스 이용이 제한될 수 있습니다.
+
+
+
+ onChange(e.target.checked)}
+ className="mr-2"
+ />
+ 위 내용을 충분히 이해하였으며, 개인정보 수집 및 이용에 동의합니다.
+
+
+
+
+ );
+}
diff --git a/src/app/components/auth/input.js b/src/app/components/auth/input.js
new file mode 100644
index 0000000..0ccd48b
--- /dev/null
+++ b/src/app/components/auth/input.js
@@ -0,0 +1,22 @@
+export default function Input(
+ { label, type = "text", name, value, onChange, placeholder, className="", }) {
+
+ return (
+
+
+ {label}
+
+
+
+ );
+}
diff --git a/src/app/components/auth/login-form.js b/src/app/components/auth/login-form.js
new file mode 100644
index 0000000..6432b3f
--- /dev/null
+++ b/src/app/components/auth/login-form.js
@@ -0,0 +1,102 @@
+'use client'
+
+import { useState } from 'react';
+import { validatePassword } from './validate';
+import Input from './input';
+import Image from "next/image";
+import { useRouter } from 'next/navigation';
+import LoginModal from "./login-success-modal";
+
+export default function LoginForm() {
+
+ const [form, setForm] = useState({
+ userid: '',
+ userpw: '',
+ });
+
+ const [error, setError] = useState('');
+
+ const [isLoginSuccess, setIsLoginSuccess] = useState(false);
+
+ const handleChange = (field) => (e) => {
+ setForm((prev) => ({ ...prev, [field]: e.target.value }));
+ };
+
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+ // 비밀번호 유효성 검사
+ if (!validatePassword(form.userpw)) {
+ return setError('비밀번호는 8자 이상 입력해주세요');
+ }
+ const closeModal = () => {
+ setIsLoginSuccess(true);
+ };
+ try {
+ const response = await fetch('/api/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(form),
+ });
+ const result = await response.json();
+
+ if (result.success) {
+ alert('아주 심각한 에러입니다!');
+ alert('아주 심각한 에러입니다!');
+ alert('아주 심각한 에러입니다!');
+ alert('아주 심각한 에러입니다!');
+ alert('아주 심각한 에러입니다!');
+ alert('사실 에러 아니지롱 데헷😋');
+ alert(result.nickname + '님 환영합니다')
+ closeModal();
+ } else {
+ setError(result.message || '로그인에 실패했습니다.');
+ }
+ } catch (err) {
+ setError('로그인 요청 중 오류가 발생했습니다.');
+ }
+ };
+
+
+
+ return (
+
+
+
+
+
+ {isLoginSuccess &&
}
+
+ );
+}
diff --git a/src/app/components/auth/login-success-modal.js b/src/app/components/auth/login-success-modal.js
new file mode 100644
index 0000000..1e283b8
--- /dev/null
+++ b/src/app/components/auth/login-success-modal.js
@@ -0,0 +1,20 @@
+import {useRouter} from "next/navigation";
+
+export default function LoginSuccessModal({message}) {
+
+ const router = useRouter();
+ const goHome = () => {
+ router.push('/');
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/components/auth/logout-button.js b/src/app/components/auth/logout-button.js
new file mode 100644
index 0000000..a117c23
--- /dev/null
+++ b/src/app/components/auth/logout-button.js
@@ -0,0 +1,23 @@
+// 클라이언트 컴포넌트
+'use client'
+import { useRouter } from 'next/navigation'
+
+export default function LogoutButton() {
+ const router = useRouter();
+ const handleLogout = async () => {
+ await fetch('/api/auth/logout', { method: 'POST' });
+ setTimeout(() => {
+ window.location.reload()
+ router.push('/')
+ }, 50);
+ };
+
+ return (
+
+ 로그아웃
+
+ );
+}
diff --git a/src/app/components/auth/signup-form.js b/src/app/components/auth/signup-form.js
new file mode 100644
index 0000000..a8af75c
--- /dev/null
+++ b/src/app/components/auth/signup-form.js
@@ -0,0 +1,161 @@
+'use client'
+
+import { useState } from 'react';
+import { validateEmail, validatePassword, validatePhoneNumber } from './validate';
+import Input from './input';
+import AgreementBox from './agreement';
+import Image from "next/image";
+import SignupModal from "./login-success-modal";
+
+export default function SignupForm({setStatus}) {
+ const [agreed, setAgreed] = useState(false);
+ const [form, setForm] = useState({
+ userid: '',
+ userpw: '',
+ checkpw: '',
+ nickname: '',
+ username: '',
+ phone: '',
+ address: '',
+ email: '',
+ });
+
+
+ const [error, setError] = useState('');
+ const [isSuccess, setIsSuccess] = useState(false);
+
+ const handleChange = (field) => (e) => {
+ setForm((prev) => ({ ...prev, [field]: e.target.value }));
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!agreed) {
+ alert("개인정보 수집 및 이용에 동의해주세요.");
+ return;
+ }
+
+ if (!validateEmail(form.email)) return setError('유효한 이메일을 입력해주세요');
+ if (!validatePassword(form.userpw)) return setError('비밀번호는 8자 이상 입력해주세요');
+ if (!validatePhoneNumber(form.phone)) return setError('유효한 전화번호를 입력해주세요');
+
+ setError('');
+
+ if (form.userpw !== form.checkpw) {
+ return setError("비밀번호가 일치하지 않습니다.");
+ }
+
+ const response = await fetch("/api/auth/signup", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(form),
+ });
+
+ const result = await response.json();
+
+ // if (result.success) {
+ // alert(result.message);
+ // setStatus(true);
+ // if (typeof setStatus === 'function') {
+ // setStatus(true);
+ // }
+ // location.reload();
+ //
+ // } else {
+ // alert(result.message);
+ // }
+ // };
+
+ if (result.success) {
+ setIsSuccess(true); // 모달 띄우기
+ if (typeof setStatus === 'function') {
+ setStatus(true);
+ }
+ } else {
+ alert(result.message);
+ }
+ };
+
+ const closeModal = () => {
+ setIsSuccess(false);
+ };
+
+ return (
+
+
+
+
+
+ {isSuccess &&
}
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/components/auth/signup-success-modal.js b/src/app/components/auth/signup-success-modal.js
new file mode 100644
index 0000000..d744b05
--- /dev/null
+++ b/src/app/components/auth/signup-success-modal.js
@@ -0,0 +1,20 @@
+import {useRouter} from "next/navigation";
+
+export default function SignupSuccessModal({message}) {
+
+ const router = useRouter();
+ const goHome = () => {
+ router.push('/login');
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/components/auth/validate.js b/src/app/components/auth/validate.js
new file mode 100644
index 0000000..6219846
--- /dev/null
+++ b/src/app/components/auth/validate.js
@@ -0,0 +1,11 @@
+export function validateEmail(email) {
+ return /\S+@\S+\.\S+/.test(email);
+}
+
+export function validatePassword(pw) {
+ return pw.length >= 8;
+}
+
+export function validatePhoneNumber(phone) {
+ return /^010-\d{4}-\d{4}$/.test(phone);
+}
\ No newline at end of file
diff --git a/src/app/components/category/category-form.js b/src/app/components/category/category-form.js
new file mode 100644
index 0000000..c9bb0ae
--- /dev/null
+++ b/src/app/components/category/category-form.js
@@ -0,0 +1,37 @@
+'use client';
+
+import Image from 'next/image'
+import category_lists from '@constants/simpleDB';
+import {useRouter} from "next/navigation";
+
+export default function Category({onSelect}) {
+ const router = useRouter();
+
+ const handleClick = (selected_index) => {
+ if (onSelect) onSelect(selected_index);
+ router.push('/feature/category')
+ };
+
+ return (
+
+ 카테고리
+
+ {category_lists.map((item) => (
+
handleClick(item.name)}
+ className="relative w-[170px] h-[170px] p-4 flex flex-col justify-center text-center aspect-square group">
+
+
+
+
+ {item.name || '이름 없음'}
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/app/components/category/item-list.js b/src/app/components/category/item-list.js
new file mode 100644
index 0000000..39e4ad7
--- /dev/null
+++ b/src/app/components/category/item-list.js
@@ -0,0 +1,88 @@
+'use client';
+
+ import Image from 'next/image';
+import { useState, useEffect } from 'react';
+import {useRouter} from "next/navigation";
+import category_lists from '../../../constants/simpleDB';
+
+export default function ItemList({ category }) {
+ const [items, setItems] = useState([]);
+ const [filteredItems, setFilteredItems] = useState([]);
+ const router = useRouter();
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const res = await fetch('/api/post/item');
+ if (!res.ok) throw new Error('API 호출 실패');
+ const data = await res.json();
+ setItems(data);
+ } catch (error) {
+ console.error('데이터 불러오기 실패:', error);
+ }
+ };
+ fetchData();
+ }, []);
+
+ const handleClick = (id) => {
+ router.push(`/feature/product/${id}`);
+ };
+
+ useEffect(() => {
+ const filteredItem = category
+ ? items.filter(item => item.itemCategory === category)
+ : items;
+ setFilteredItems(filteredItem);
+ }, [category, items])
+
+ console.log(category);
+ function getValueByName(name) {
+ const found = category_lists.find(item => item.name.trim() === name.trim());
+ return found ? found.value : null;
+ }
+ const value = getValueByName(category);
+
+ if (!value) return null;
+ console.log(value);
+
+ return (
+
+
+ {filteredItems.map((item) => (
+
handleClick(item._id)}
+ >
+
+
+
+ 1 / {item.totalNumberOfRecruits}
+
+
+
+ {/* 이름 (한 줄 전체 차지) */}
+
{item.title}
+
+ {/* 나눈 가격 / '원' / 원래 가격 — 인라인 요소 */}
+
+ {item.pricePerEachPerson}
+ 원
+ {item.totalPrice}원
+
+
+
+ ))}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/components/chat/chat-message.js b/src/app/components/chat/chat-message.js
new file mode 100644
index 0000000..1cf8a64
--- /dev/null
+++ b/src/app/components/chat/chat-message.js
@@ -0,0 +1,22 @@
+// components/ChatMessage.js
+import Image from "next/image";
+
+export default function ChatMessage({ name, lastUser, lastMessage, time, profileImg, onClick }) {
+ return (
+
+ {/* 프로필 이미지 */}
+
+
+
+ {/* 채팅 내용 */}
+
+
+
{lastUser}: {lastMessage}
+
+
+ );
+}
diff --git a/src/app/components/chat/chat.js b/src/app/components/chat/chat.js
new file mode 100644
index 0000000..7b90d42
--- /dev/null
+++ b/src/app/components/chat/chat.js
@@ -0,0 +1,99 @@
+// components/Chat.js
+'use client'
+
+import { useEffect, useState, useRef } from "react";
+
+export default function Chat({ onClose, name, roomId, userId, userNickname }) {
+ const [chatLog, setChatLog] = useState([
+ {
+ name: "트럼프",
+ text: "안녕하세요!",
+ time: "오후 2:00",
+ sender: "other"
+ },
+ {
+ name: "나",
+ text: "안녕하세요! 반가워요~",
+ time: "오후 2:01",
+ sender: "me"
+ }
+ ]);
+ const [message, setMessage] = useState("");
+ const messagesEndRef = useRef(null);
+
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [chatLog]);
+
+ const sendMessage = () => {
+ if (!message.trim()) return;
+ const now = new Date();
+ setChatLog(prev => [
+ ...prev,
+ {
+ name: userNickname,
+ text: message,
+ time: now.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' }),
+ sender: "me"
+ }
+ ]);
+ setMessage("");
+ };
+
+ return (
+
+ {/* 상단 헤더 */}
+
+ {/* 채팅 메시지 영역 */}
+
+ {chatLog.map((msg, idx) => (
+
+
+ {msg.sender !== "me" && (
+
+ {msg.name}
+
+ )}
+
+
+ {msg.text}
+
+
+ {msg.time}
+
+
+
+
+ ))}
+
+
+ {/* 메시지 입력 영역 */}
+
+ setMessage(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && sendMessage()}
+ className="flex-1 px-2 text-sm focus:outline-none"
+ placeholder="메시지를 입력하세요."
+ />
+
+ →
+
+
+
+ );
+}
diff --git a/src/app/components/common/footer.js b/src/app/components/common/footer.js
new file mode 100644
index 0000000..6a201a2
--- /dev/null
+++ b/src/app/components/common/footer.js
@@ -0,0 +1,10 @@
+
+
+export default function Footer(){
+
+ return (
+
+ © webprogramming_team3
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/components/common/nav.js b/src/app/components/common/nav.js
new file mode 100644
index 0000000..cbe99a6
--- /dev/null
+++ b/src/app/components/common/nav.js
@@ -0,0 +1,55 @@
+// app/auth/nav (클라이언트 컴포넌트)
+'use client'
+
+import Image from 'next/image'
+import Link from 'next/link'
+import { useEffect, useState } from 'react'
+import LogoutButton from "@components/auth/logout-button";
+
+export default function Nav() {
+ const [user, setUser] = useState(null)
+ const [moving, setMoved] = useState(false)
+
+ // 1. 로그인 정보 가져오기
+ useEffect(() => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => setUser(data.user))
+ }, [])
+
+ useEffect(() => {
+ const scrolled = () => setMoved(window.scrollY > 50)
+ window.addEventListener('scroll', scrolled)
+ return () => window.removeEventListener('scroll', scrolled)
+ }, [])
+
+ return (
+
+
+
+
+
+
+
+ {user ? (
+ <>
+ 안녕하세요, {user.nickname}님
+
+
+
+
+ >
+
+ ) : (
+
+ 로그인 / 회원가입
+
+ )}
+
+
+
+ )
+}
diff --git a/src/app/components/post/post-big.js b/src/app/components/post/post-big.js
new file mode 100644
index 0000000..e408b47
--- /dev/null
+++ b/src/app/components/post/post-big.js
@@ -0,0 +1,124 @@
+'use client';
+
+import { useState } from 'react';
+import { useRef } from 'react';
+import ConfirmModal from '@components/post/post-confirm-modal';
+
+export default function WritingPage() {
+ const [showConfirm, setShowConfirm] = useState(false);
+
+ const handleSubmit = () => {
+ console.log('등록 처리 완료');
+ setShowConfirm(false);
+ onClose();
+ };
+
+ const fileInputRef = useRef(null);
+
+ const handleClick = () => {
+ fileInputRef.current?.click();
+ };
+
+ return (
+
+ {/*
상품 등록 */}
+
+
+ {/* 등록 확인 모달 */}
+ {showConfirm && (
+
setShowConfirm(false)}
+ onConfirm={handleSubmit}
+ />
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/components/post/post-box.js b/src/app/components/post/post-box.js
new file mode 100644
index 0000000..fe1156d
--- /dev/null
+++ b/src/app/components/post/post-box.js
@@ -0,0 +1,299 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import ConfirmModal from './post-confirm-modal';
+import WritingPage from './post-big';
+
+export default function PostBox({ onClose }) {
+ const [showConfirm, setShowConfirm] = useState(false);
+ const [showFullPage, setShowFullPage] = useState(false);
+ const [error, setError] = useState('');
+ const [user, setUser] = useState(null);
+ const [preview, setPreview] = useState(null); // 이미지 미리보기 URL
+ const [form, setForm] = useState({
+ title: '',
+ description: '',
+ writer: '',
+ itemCategory: '',
+ tradeType: '',
+ totalNumberOfRecruits: 1,
+ numberOfRecruitedPersonnel: 0,
+ totalPrice: 1,
+ pricePerEachPerson: 1,
+ image: null,
+ });
+
+ useEffect(() => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => {
+ if (data.user) setUser(data.user);
+ })
+ .catch(error => console.error('유저 정보 불러오기 실패:', error));
+ }, []);
+
+ useEffect(() => {
+ if (form.totalPrice > 0 && form.totalNumberOfRecruits > 0) {
+ const calculatedPrice = form.totalPrice / form.totalNumberOfRecruits;
+ const roundedPrice = Math.round(calculatedPrice / 10) * 10;
+
+ setForm(prev => ({
+ ...prev,
+ pricePerEachPerson: roundedPrice
+ }));
+ }
+ }, [form.totalPrice, form.totalNumberOfRecruits]);
+
+ const handleChange = (e) => {
+ const { name, value, type } = e.target;
+ let newValue = type === 'number' ? Number(value) : value;
+
+ if (name === 'totalNumberOfRecruits' && newValue > 8) {
+ newValue = 8;
+ }
+
+ setForm((prev) => ({
+ ...prev,
+ [name]: newValue,
+ }));
+ };
+
+ const handleImageChange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setForm(prev => ({
+ ...prev,
+ image: file
+ }));
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setPreview(reader.result);
+ };
+ reader.readAsDataURL(file);
+ } else {
+ setForm(prev => ({ ...prev, image: null }));
+ setPreview(null);
+ }
+ };
+
+ const handleSubmit = async () => {
+ setShowConfirm(false);
+
+ try {
+ if (!user?.nickname) throw new Error('로그인 정보가 없습니다');
+ if (!form.itemCategory || !form.tradeType) {
+ throw new Error('카테고리와 거래방식을 선택해주세요');
+ }
+
+ const payload = {
+ ...form,
+ writer: user.nickname,
+ };
+
+ let body, headers;
+
+ if (form.image) {
+ body = new FormData();
+ Object.entries(payload).forEach(([key, value]) => {
+ if (value !== null) {
+ if (key === 'image') {
+ body.append(key, value, value.name);
+ } else {
+ body.append(key, value.toString());
+ }
+ }
+ });
+ headers = undefined; // Let fetch set it automatically for FormData
+ } else {
+ body = JSON.stringify(payload);
+ headers = { 'Content-Type': 'application/json' };
+ }
+
+ const response = await fetch('/api/post/write', { method: 'POST', headers, body });
+
+ const contentType = response.headers.get('content-type') || '';
+ if (!contentType.includes('application/json')) {
+ const text = await response.text();
+ throw new Error(`서버 응답 오류: ${text.slice(0, 100)}`);
+ }
+
+ const result = await response.json();
+
+ if (result.success) {
+ alert('띱 올리기 완료!');
+ onClose();
+ } else {
+ setError(result.message || '서버 오류 발생');
+ }
+ } catch (err) {
+ setError(err.message);
+ console.error('등록 실패:', err);
+ }
+ };
+
+ const handleExpand = () => {
+ setShowFullPage(true);
+ };
+
+ if (showFullPage) return ;
+
+ return (
+
+
+
상품 등록
+
+ {/* 이미지 업로드 */}
+
+
+ {/* 제목 */}
+
+ 제목
+
+
+
+ {/* 금액 + 인원 */}
+
+
+ {/* 카테고리 + 거래방식 */}
+
+
+ 카테고리
+
+ 카테고리
+ 식재료
+ 간편식/냉동식품
+ 생활용품
+ 대용량
+ 배달음식
+ 나눔템
+
+
+
+ 거래방식
+
+ 거래 방식
+ 직거래
+ 택배
+ 기타
+
+
+
+
+ {/* 상세설명 */}
+
+ 상세설명
+
+
+
+ {/* 등록 버튼 */}
+ setShowConfirm(true)}
+ >
+ 등록하기
+
+
+
+ {/* 에러 메시지 */}
+ {error && (
+
{error}
+ )}
+
+ {/* 확인 모달 */}
+ {showConfirm && (
+
setShowConfirm(false)}
+ onConfirm={handleSubmit}
+ />
+ )}
+
+
+ ⇱
+
+
+
+ ✖
+
+
+
+ );
+}
diff --git a/src/app/components/post/post-confirm-modal.js b/src/app/components/post/post-confirm-modal.js
new file mode 100644
index 0000000..55dd875
--- /dev/null
+++ b/src/app/components/post/post-confirm-modal.js
@@ -0,0 +1,13 @@
+export default function ConfirmModal({ onConfirm, onCancel }) {
+ return (
+
+
+
상품을 등록하시겠습니까?
+
+ 닫기
+ 등록하기
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/components/product/category-sidebar.js b/src/app/components/product/category-sidebar.js
new file mode 100644
index 0000000..de1b017
--- /dev/null
+++ b/src/app/components/product/category-sidebar.js
@@ -0,0 +1,50 @@
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import { useRouter } from 'next/navigation';
+
+const categories = [
+ '식재료',
+ '간편식/냉동식품',
+ '생활용품',
+ '대용량',
+ '배달음식',
+ '나눔템',
+];
+
+export default function CategorySidebar({ category, onSelect }) {
+ const [selected, setSelected] = useState(category || null);
+ const router = useRouter();
+
+ useEffect(() => {
+ setSelected(category);
+ }, [category]);
+
+ const handleClick = (category) => {
+ setSelected(category);
+ if (onSelect) onSelect(category);
+
+ // 예: 선택한 카테고리에 따라 이동 (경로는 필요에 맞게 수정)
+ router.push(`/feature/category?selected=${encodeURIComponent(category)}`);
+ };
+
+ return (
+
+ 카테고리
+
+ {categories.map((cat) => (
+ handleClick(cat)}
+ className={`cursor-pointer hover:text-[#404040] font-medium transition-colors ${
+ selected === cat ? 'font-medium text-[#404040]' : ''
+ }`}
+ >
+ {cat}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/app/components/profile/info-sidebar.js b/src/app/components/profile/info-sidebar.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/profile/my-info-form.js b/src/app/components/profile/my-info-form.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/profile/my-sidebar.js b/src/app/components/profile/my-sidebar.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/profile/myproduct-managing-form.js b/src/app/components/profile/myproduct-managing-form.js
new file mode 100644
index 0000000..e69de29
diff --git a/ddip/src/app/favicon.ico b/src/app/favicon.ico
similarity index 100%
rename from ddip/src/app/favicon.ico
rename to src/app/favicon.ico
diff --git a/src/app/feature/auth/page.js b/src/app/feature/auth/page.js
new file mode 100644
index 0000000..1972475
--- /dev/null
+++ b/src/app/feature/auth/page.js
@@ -0,0 +1,40 @@
+'use client'
+
+import SignupForm from '@components/auth/signup-form';
+import LoginForm from "@components/auth/login-form";
+import { useState } from "react";
+import Nav from "@components/common/nav";
+
+
+export default function SignUp() {
+ const [status, setStatus] = useState(false);
+
+ return (
+
+
+
+
+
+ {status
+ ?
+ :
+ }
+
+ {/*
+ 비밀번호 찾기, 아이디 찾기 구현
+ 이메일로 코드 보내고 인증 같은 절차 구현
+ */}
+
+ setStatus(!status)}
+ className="mb-30 text-blue-500 underline" >
+ {status ? '로그인으로 돌아가기' : '회원가입'}
+
+
+
+ );
+}
+
diff --git a/src/app/feature/category/page.js b/src/app/feature/category/page.js
new file mode 100644
index 0000000..00938fb
--- /dev/null
+++ b/src/app/feature/category/page.js
@@ -0,0 +1,24 @@
+'use client'
+
+import ItemList from "@components/category/item-list";
+import {useState} from "react";
+import Category from "@components/category/category-form";
+import Nav from "@components/common/nav";
+
+
+export default function CategoryPage() {
+ const [selectedCategory, setSelectedCategory] = useState('식재료');
+
+ return(
+
+
+
+
+
+
+
+
{selectedCategory}
+
+
+ )
+}
diff --git a/src/app/feature/chat/page.js b/src/app/feature/chat/page.js
new file mode 100644
index 0000000..31e3293
--- /dev/null
+++ b/src/app/feature/chat/page.js
@@ -0,0 +1,77 @@
+// components/ChatList.js
+
+'use client'
+
+import { useState } from "react";
+import ChatMessage from "../../components/chat/chat-message";
+import Chat from "../../components/chat/chat";
+
+// 임의의 로그인 정보
+const user = {
+ id: "user123",
+ nickname: "갱수"
+};
+
+// 임의의 채팅방 데이터
+const chatData = [
+ {
+ chatId: "room1",
+ name: "갱수님의 띱",
+ lastUser: "홍길동",
+ message: "안녕하세요!",
+ time: "오후 2:00",
+ profileImg: "/profile1.png"
+ },
+ {
+ chatId: "room2",
+ name: "Next.js 개발자",
+ lastUser: "김철수",
+ message: "13버전에서 바뀐 점 공유해요!",
+ time: "오후 1:10",
+ profileImg: "/profile2.png"
+ }
+];
+
+export default function ChatList() {
+ const [selectedChat, setSelectedChat] = useState(null);
+
+ const handleCloseChat = () => {
+ setSelectedChat(null);
+ }
+
+ return (
+ <>
+
+ {/* 상단 */}
+
+ {/* 채팅 목록 */}
+
+ {chatData.map(chat => (
+ setSelectedChat(chat)}
+ />
+ ))}
+
+
+ {/* 선택된 채팅창 모달 띄우기 */}
+ {selectedChat && (
+
+ )}
+ >
+ );
+}
diff --git a/src/app/feature/home/page.js b/src/app/feature/home/page.js
new file mode 100644
index 0000000..51004ea
--- /dev/null
+++ b/src/app/feature/home/page.js
@@ -0,0 +1,28 @@
+import Image from "next/image";
+import Shortcut from "../shortcut/shortcut";
+
+
+export default function Main() {
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ SEARCH
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/feature/mypage/components/category-sidebar.js b/src/app/feature/mypage/components/category-sidebar.js
new file mode 100644
index 0000000..c604fb6
--- /dev/null
+++ b/src/app/feature/mypage/components/category-sidebar.js
@@ -0,0 +1,59 @@
+'use client';
+
+
+import React, { useState } from 'react';
+
+const QuestionCategories = [
+ '문의 사항'
+];
+
+const NoticeCategories = [
+ '공지사항',
+ '자주 묻는 질문',
+ '띱 소개',
+ '고객센터',
+];
+
+export default function CategorySidebar({ onSelect }) {
+ const [selected, setSelected] = useState(null);
+
+ const handleClick = (category) => {
+ setSelected(category);
+ onSelect(category);
+ };
+
+ return (
+
+ 문의
+
+ {QuestionCategories.map((question) => (
+ handleClick(question)}
+ className={`cursor-pointer hover:text-[#404040] font-medium ${
+ selected === question ? 'font-bold text-[#404040]' : ''
+ }`}
+ >
+ {question}
+
+ ))}
+
+
+ 공지
+
+ {NoticeCategories.map((notice) => (
+ handleClick(notice)}
+ className={`cursor-pointer hover:text-[#404040] font-medium ${
+ selected === notice ? 'font-bold text-[#404040]' : ''
+ }`}
+ >
+ {notice}
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/feature/mypage/components/manage.js b/src/app/feature/mypage/components/manage.js
new file mode 100644
index 0000000..dda5421
--- /dev/null
+++ b/src/app/feature/mypage/components/manage.js
@@ -0,0 +1,111 @@
+'use client';
+
+import { useState } from "react";
+
+export default function ProductManage() {
+ const tabs = ['내 상품 관리', '거래 중', '거래 완료'];
+ const [selected, setSelected] = useState('내 상품 관리');
+
+ // 예시 데이터
+ const allProducts = [
+ { name: '제품 A', price: 2300 },
+ { name: '제품 B', price: 4500 },
+ { name: '제품 C', price: 12000 },
+ { name: '제품 D', price: 9900 },
+ { name: '제품 E', price: 7000 },
+ { name: '제품 F', price: 1800 },
+ ];
+
+ // 탭에 따라 상품 필터링
+ let filteredProducts = allProducts;
+ if (selected === '거래 중') {
+ filteredProducts = allProducts.slice(0, 4);
+ } else if (selected === '거래 완료') {
+ filteredProducts = allProducts.slice(2, 6);
+ }
+
+
+ return (
+
+ {/* 상품 탭 */}
+
+ {tabs.map((tab) => (
+ setSelected(tab)}
+ className={`inline-block cursor-pointer hover:text-[#404040] font-medium
+ ${selected === tab ? 'mt-[-5px] font-semibold text-[20px] text-[#404040]' : 'mt-[0px] text-[16px] font-regular'}`}
+ >
+ {tab}
+
+ ))}
+
+ 상품 등록하기
+
+
+
+ {/* 상품 목록 */}
+
+
+ );
+}
+
+
+//카드 슬라이더
+function ProductSlider({ products, visibleCount = 4, slideCount = 2 }) {
+ const [start, setStart] = useState(0);
+
+ const maxStart = Math.max(0, products.length - visibleCount);
+
+ const handlePrev = () => setStart(prev => Math.max(0, prev - slideCount));
+ const handleNext = () => setStart(prev => {
+ // 마지막 이동 시 남은 상품이 slideCount 미만이어도 끝까지 보여주기
+ const next = prev + slideCount;
+ return next > maxStart ? maxStart : next;
+ });
+
+ // 마지막 슬라이드에서 더이상 이동 못하게 처리
+ const isNextDisabled = start >= maxStart || products.length <= visibleCount;
+ const isPrevDisabled = start === 0;
+
+ const slideWidth = 170;
+ const transitionStyle = {
+ display: 'flex',
+ transition: 'transform 0.4s cubic-bezier(0.4,0,0.2,1)',
+ transform: `translateX(-${start * slideWidth}px)`,
+ width: products.length * slideWidth
+ };
+
+ return (
+
+
{'<'}
+
+
+ {products.map((p, idx) => (
+
//여기를 기존 컴포넌트로 교체
+ ))}
+
+
+
{'>'}
+
+ );
+}
+
+// 상품 카드 컴포넌트 (다~~~~~ 삭제)
+function ProductCard({ price, name }) {
+ return (
+
+
+
+
{name}
+
{price.toLocaleString()}원
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/feature/mypage/components/mypage.js b/src/app/feature/mypage/components/mypage.js
new file mode 100644
index 0000000..b3b3674
--- /dev/null
+++ b/src/app/feature/mypage/components/mypage.js
@@ -0,0 +1,75 @@
+'use client';
+import Image from "next/image";
+import ProductManage from "./manage";
+import ProductWishList from "./wishlist";
+import ProfileEdit from "./profile-edit";
+import { useState, useEffect } from 'react';
+import Nav from "../../../components/common/nav";
+
+export default function MypageMain() {
+ const [selected, setSelected] = useState('내 상품 관리');
+ const [user, setUser] = useState('');
+ const [editMode, setEditMode] = useState(false);
+
+ useEffect(() => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => setUser(data.user))
+ }, [])
+
+ return (
+ <>
+
+
+ {/* 이용자 정보 */}
+ {editMode ? (
+ setEditMode(false)}
+ />
+ ) : (
+
+ {/* 좌측: 사진, 정보 수정 */}
+
+
+
+ setEditMode(true)}>정보 수정하기
+
+
+ {/* 우측: 상품 정보 */}
+
+ {/* 사용자 이름 */}
+
{user.nickname}
+ {/* 사용자 아이디 */}
+
{user.userid}
+ {/* */}
+
{user.description}
+ {/* */}
+
{user.address}
+
+
+ )}
+
+ {/* 상품 설명 */}
+ {editMode ? null
+ : (
+
+ )}
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/app/feature/mypage/components/profile-edit.js b/src/app/feature/mypage/components/profile-edit.js
new file mode 100644
index 0000000..b3ec35b
--- /dev/null
+++ b/src/app/feature/mypage/components/profile-edit.js
@@ -0,0 +1,182 @@
+'use client'
+import { useState, useRef } from 'react';
+import Image from 'next/image';
+
+export default function ProfileEdit({ user, setUser, onCancel }) {
+ const fileInputRef = useRef(null); // 파일 input 참조
+
+ const [form, setForm] = useState({
+ nickname: user.nickname,
+ userid: user.userid,
+ description: user.description,
+ address: user.address,
+ image: user.image,
+ });
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setForm({ ...form, [name]: value });
+ };
+
+ const handleImageChange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ const imageUrl = URL.createObjectURL(file);
+ setForm({ ...form, image: imageUrl }); // image로 저장
+ }
+ };
+
+ const [formError, setFormError] = useState({
+ nickname: '',
+ userid: '',
+ description: '',
+ address: '',
+ });
+
+ const handleSave = async () => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => setUser(data.user))
+
+ const errors = {
+ nickname: form.nickname.trim() ? '' : '별명을 입력해주세요.',
+ userid: form.userid.trim() ? '' : '아이디를 입력해주세요.',
+ description: form.description.trim() ? '' : '한줄 소개를 입력해주세요.',
+ address: form.address.trim() ? '' : '주소를 입력해주세요.',
+ };
+
+ await fetch('/api/user/update', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(form),
+ })
+ .then(res => res.json())
+ .then(data => {
+ setUser(data.user); // MypageMain의 user 상태 갱신
+ onCancel();
+ });
+
+ setFormError(errors);
+
+ const hasError = Object.values(errors).some(error => error);
+ if (hasError) return;
+
+ setUser(form);
+ onCancel();
+ };
+
+
+ return (
+
+ {/* 왼쪽: 프로필 이미지 */}
+
+ {form.image ? (
+
+ ) : (
+
+ 이미지 없음
+
+ )}
+
+
fileInputRef.current?.click()}
+ className="w-[185px] h-[22px] border-[1px] border-[#D9D9D9] rounded-[10px] mt-[10px]
+ text-[13px] text-[#888C85] text-regular"
+ >사진 변경하기
+
+ {/* 숨겨진 input */}
+
+
+
+
+ {/* 오른쪽: 이용자 정보 입력란 */}
+
+
+
+
+
+
+ {/* 버튼들 */}
+
+ 취소
+
+ 저장
+
+
+
+
+ );
+}
+
+function InputField({ label, name, value, onChange, placeholder, error }) {
+ return (
+
+
+ {label}
+
+
+ {error && (
+
* {error}
+ )}
+
+ );
+}
diff --git a/src/app/feature/mypage/components/wishlist.js b/src/app/feature/mypage/components/wishlist.js
new file mode 100644
index 0000000..2fcc5c0
--- /dev/null
+++ b/src/app/feature/mypage/components/wishlist.js
@@ -0,0 +1,81 @@
+import { useState } from "react";
+
+export default function ProductWishList() {
+ //예시 코드
+ const allProducts = [
+ { name: '제품 A', price: 2300 },
+ { name: '제품 B', price: 4500 },
+ { name: '제품 C', price: 12000 },
+ { name: '제품 D', price: 9900 },
+ { name: '제품 E', price: 7000 },
+ { name: '제품 F', price: 1800 },
+ ];
+
+ return (
+
+ );
+}
+
+
+//카드 슬라이더
+function ProductSlider({ products, visibleCount = 4, slideCount = 2 }) {
+ const [start, setStart] = useState(0);
+
+ const maxStart = Math.max(0, products.length - visibleCount);
+
+ const handlePrev = () => setStart(prev => Math.max(0, prev - slideCount));
+ const handleNext = () => setStart(prev => {
+ // 마지막 이동 시 남은 상품이 slideCount 미만이어도 끝까지 보여주기
+ const next = prev + slideCount;
+ return next > maxStart ? maxStart : next;
+ });
+
+ // 마지막 슬라이드에서 더이상 이동 못하게 처리
+ const isNextDisabled = start >= maxStart || products.length <= visibleCount;
+ const isPrevDisabled = start === 0;
+
+ const slideWidth = 170;
+ const transitionStyle = {
+ display: 'flex',
+ transition: 'transform 0.4s cubic-bezier(0.4,0,0.2,1)',
+ transform: `translateX(-${start * slideWidth}px)`,
+ width: products.length * slideWidth
+ };
+
+ return (
+
+
{'<'}
+
+
+ {products.map((p, idx) => (
+
//여기를 기존 컴포넌트로 교체
+ ))}
+
+
+
{'>'}
+
+ );
+}
+
+
+// 상품 카드 컴포넌트 (다~~~~~ 삭제)
+function ProductCard({ price, name }) {
+ return (
+
+
+
+
{name}
+
{price.toLocaleString()}원
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/feature/mypage/page.js b/src/app/feature/mypage/page.js
new file mode 100644
index 0000000..1a9a208
--- /dev/null
+++ b/src/app/feature/mypage/page.js
@@ -0,0 +1,18 @@
+'use client';
+
+import { useState } from "react";
+import MypageMain from "./components/mypage";
+import CategorySidebar from "./components/category-sidebar";
+
+export default function Mypage() {
+ return (
+
+ )
+}
diff --git a/src/app/feature/product/[id]/page.js b/src/app/feature/product/[id]/page.js
new file mode 100644
index 0000000..8129519
--- /dev/null
+++ b/src/app/feature/product/[id]/page.js
@@ -0,0 +1,147 @@
+'use client'
+
+import { useState, useEffect } from 'react';
+import CategorySidebar from '@components/product/category-sidebar';
+import Image from "next/image";
+import {useParams, useRouter} from "next/navigation";
+import Nav from "@components/common/nav";
+
+
+export default function Product({ onSelect }) {
+ const { id } = useParams(); // URL에서 받기
+ const [item, setItem] = useState(null);
+ const [error, setError] = useState(null);
+ const [user, setUser] = useState(null);
+ const router = useRouter();
+ const [selectedCategory, setSelectedCategory] = useState('식재료');
+
+
+ const pleaseLogin = ()=>{
+ alert('로그인창으로 이동합니다')
+ router.push('/feature/auth');
+ }
+
+ useEffect(() => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => setUser(data.user))
+ }, [])
+
+
+ useEffect(() => {
+ const fetchItem = async () => {
+ try {
+ const response = await fetch(`/api/post/item/${id}`);
+ const data = await response.json();
+
+ if (data.success) {
+ setItem(data.data);
+ } else {
+ setError(data.message);
+ }
+ } catch (err) {
+ setError('서버 에러가 발생했습니다.');
+ }
+ };
+
+ if (id) {
+ fetchItem();
+ }
+ }, [id]);
+
+ const getInChat = () => {
+ alert('채팅방 가입 성공!');
+
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {/* 좌측: 이미지 박스 */}
+
+ {item?.image && (
+
+
+
+ )}
+
+
+
+ {/* 우측: 상품 정보 */}
+
+
{item ? (item.title):(id)}
+
+ {item ? (item.pricePerEachPerson):("none")}
+ {item ? (item.totalPrice):("none")}{}
+
+
+
+
+
+
모집 인원 | {item ? (item.totalNumberOfRecruits):("none")} ({item ? (item.numberOfRecruitedPersonnel):("none")}/{item ? (item.totalNumberOfRecruits):("none")})
+
거래 방식 | {item ? (item.tradeType):("none")}
+
카테고리 | {item ? (item.itemCategory):("none")}
+
+
+ {user ? (
+ <>
+ {/* 버튼들 */}
+
+
+ 참여하기
+
+ >
+ )
+ :
+ (
+ <>
+ {/* 버튼들 */}
+
+ {pleaseLogin()}}
+ >
+ 톡 보내기
+ {pleaseLogin()}}
+ >
+ 참여하기
+
+ >
+ )}
+
+
+
+
+ {/* 상품 설명 */}
+
+
상품 설명
+
+ {item ? (item.description):("none")}
+
+
+
+
+
+
+ )
+};
\ No newline at end of file
diff --git a/src/app/feature/profile/page.js b/src/app/feature/profile/page.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/feature/shortcut/shortcut.js b/src/app/feature/shortcut/shortcut.js
new file mode 100644
index 0000000..afbd8e8
--- /dev/null
+++ b/src/app/feature/shortcut/shortcut.js
@@ -0,0 +1,64 @@
+'use client'
+
+import Image from 'next/image';
+import PostBox from '@components/post/post-box';
+
+import {useEffect, useState} from "react";
+import ChatList from "../chat/page";
+
+export default function Shortcut({}) {
+ const [isCOpen, setIsCOpen] = useState(false);
+ const [isPOpen, setIsPOpen] = useState(false);
+
+ const [user, setUser] = useState(null);
+ useEffect(() => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => setUser(data.user))
+ }, [])
+
+ const toggleChat = () => setIsCOpen(!isCOpen);
+ const togglePost = () => setIsPOpen(!isPOpen);
+
+ return (
+ <>
+ {user ? (
+ <>
+
+
+ {isCOpen && }
+
+
+
+
+ {isPOpen && }
+ >
+ ) : (
+ <>
+ alert('로그인을 해야 사용할 수 있는 기능입니다')}>
+
+
+
+
+ alert('로그인을 해야 사용할 수 있는 기능입니다')}/>
+
+ >
+ )
+ }
+ >
+
+ )
+}
\ No newline at end of file
diff --git a/ddip/src/app/globals.css b/src/app/globals.css
similarity index 51%
rename from ddip/src/app/globals.css
rename to src/app/globals.css
index 94f37cb..5609118 100644
--- a/ddip/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,14 +1,20 @@
@import "tailwindcss";
-
+@font-face {
+ font-family: 'Pretendardvariable';
+ src: url('../../public/fonts/PretendardVariable.woff2') format('woff2');
+ font-style: normal;
+ font-display: swap;
+}
body {
- font-family: Arial, Helvetica, sans-serif;
+ font-family: 'Pretendardvariable', Arial, Helvetica, sans-serif;
}
+
.searchslot{
@apply text-center rounded-full bg-[#FFFCED]
- shadow-green-500 hover:shadow-xl
+ shadow-[#aaaa88] hover:shadow-lg
transition-shadow duration-250 ease-out;
}
@@ -19,3 +25,11 @@ body {
.select-transition{
@apply transition-all duration-100 ease-linear cursor-pointer ;
}
+
+html,body {
+ height: 100%;
+}
+
+button {
+ cursor: pointer;
+}
diff --git a/ddip/src/app/layout.js b/src/app/layout.js
similarity index 51%
rename from ddip/src/app/layout.js
rename to src/app/layout.js
index 7bf337d..3811ee5 100644
--- a/ddip/src/app/layout.js
+++ b/src/app/layout.js
@@ -1,5 +1,7 @@
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import Footer from "@components/common/footer";
+import Shortcut from "./feature/shortcut/shortcut";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -19,11 +21,18 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
-
- {children}
-
+
+
+ {children}
+
+
+
+
+
+
);
}
+
diff --git a/src/app/page.js b/src/app/page.js
new file mode 100644
index 0000000..f007cfc
--- /dev/null
+++ b/src/app/page.js
@@ -0,0 +1,25 @@
+'use client'
+
+import Category from '@components/category/category-form';
+import Main from './feature/home/page'
+import Nav from "@components/common/nav";
+import {useEffect, useState} from "react";
+import Shortcut from "./feature/shortcut/shortcut";
+
+export default function Home() {
+ const [selectedCategory, setSelectedCategory] = useState('ingredient');
+ const [user, setUser] = useState(null);
+ useEffect(() => {
+ fetch('/api/auth/check')
+ .then(res => res.json())
+ .then(data => setUser(data.user))
+ }, [])
+
+ return (
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/util/chat.js b/src/app/util/chat.js
new file mode 100644
index 0000000..80a6f0c
--- /dev/null
+++ b/src/app/util/chat.js
@@ -0,0 +1,65 @@
+// pages/socket.js
+import { useEffect, useState } from 'react';
+import io from 'socket.io-client';
+
+let socket;
+
+const Chat = () => {
+ const [username, setUsername] = useState('');
+ const [message, setMessage] = useState('');
+ const [messages, setMessages] = useState([]);
+
+ useEffect(() => {
+ // 서버와 소켓 연결
+ socket = io({
+ path: '/api/socket_io',
+ });
+
+ socket.on('message', (data) => {
+ setMessages((prev) => [...prev, data]);
+ });
+
+ return () => {
+ socket.disconnect();
+ };
+ }, []);
+
+ const sendMessage = () => {
+ if (username && message) {
+ socket.emit('message', {
+ user: username,
+ text: message,
+ timestamp: new Date().toISOString(),
+ });
+ setMessage('');
+ }
+ };
+
+ return (
+
+
실시간 채팅
+
+ {messages.map((msg, idx) => (
+
+ {msg.user} ({new Date(msg.timestamp).toLocaleTimeString()}): {msg.text}
+
+ ))}
+
+
setUsername(e.target.value)}
+ />
+
setMessage(e.target.value)}
+ />
+
전송
+
+ );
+};
+
+export default Chat;
diff --git a/src/app/util/socket.js b/src/app/util/socket.js
new file mode 100644
index 0000000..7a1818c
--- /dev/null
+++ b/src/app/util/socket.js
@@ -0,0 +1,25 @@
+// pages/api/socket.js
+import { Server as HTTPServer } from 'http';
+import { Server as SocketIOServer } from 'socket.io';
+
+export const config = {
+ api: {
+ bodyParser: false,
+ },
+};
+
+export default function handler(req, res) {
+ if (!res.socket.server.io) {
+ const io = new SocketIOServer(res.socket.server, {
+ path: '/api/socket_io',
+ });
+ res.socket.server.io = io;
+
+ io.on('connection', (socket) => {
+ socket.on('message', (msg) => {
+ io.emit('message', msg);
+ });
+ });
+ }
+ res.end();
+}
diff --git a/src/constants/simpleDB.js b/src/constants/simpleDB.js
new file mode 100644
index 0000000..2c23e50
--- /dev/null
+++ b/src/constants/simpleDB.js
@@ -0,0 +1,11 @@
+const category_lists=
+ [
+ {key:'0', value:'ingredient', name:'식재료'},
+ {key:'1', value:'instant', name:'간편식/냉동식품'},
+ {key:'2', value:'stuffs', name:'생활용품'},
+ {key:'3', value:'large', name:'대용량'},
+ {key:'4', value:'deliver', name:'배달음식'},
+ {key:'5', value:'donate', name:'나눔템'},
+ ];
+
+export default category_lists;
diff --git a/src/lib/dbConnect.js b/src/lib/dbConnect.js
new file mode 100644
index 0000000..2a47cf0
--- /dev/null
+++ b/src/lib/dbConnect.js
@@ -0,0 +1,35 @@
+import mongoose from 'mongoose';
+
+const MONGODB_URI = process.env.MONGODB_URI;
+
+if (!MONGODB_URI) {
+ throw new Error('MONGODB_URI 환경 변수를 설정해주세요.');
+}
+
+let cached = global.mongoose;
+
+if (!cached) {
+ cached = global.mongoose = { conn: null, promise: null };
+}
+
+async function dbConnect() {
+ if (cached.conn) return cached.conn;
+
+ if (!cached.promise) {
+ cached.promise = mongoose.connect(process.env.MONGODB_URI).then(mongoose => {
+ mongoose.models = {};
+ return mongoose;
+ });
+ }
+
+ try {
+ cached.conn = await cached.promise;
+ } catch (e) {
+ cached.promise = null;
+ throw e;
+ }
+
+ return cached.conn;
+}
+
+export default dbConnect;
\ No newline at end of file
diff --git a/src/lib/session.js b/src/lib/session.js
new file mode 100644
index 0000000..b0c539a
--- /dev/null
+++ b/src/lib/session.js
@@ -0,0 +1,29 @@
+const { sealData, unsealData } = require('iron-session');
+
+const sessionSecret = process.env.SESSION_SECRET;
+
+if (!sessionSecret) {
+ throw new Error('SESSION_SECRET 환경변수가 필요해요!');
+}
+
+// 세션 암호화
+async function encrypt(data) {
+ return await sealData(data, {
+ password: sessionSecret,
+ ttl: 60 * 60 * 24 * 7, // 7일
+ });
+}
+
+// 세션 복호화
+async function decrypt(session) {
+ try {
+ return await unsealData(session, {
+ password: sessionSecret,
+ });
+ } catch (err) {
+ console.error('세션 복호화 실패:', err);
+ return null;
+ }
+}
+
+module.exports = { encrypt, decrypt };
diff --git a/src/models/Message.js b/src/models/Message.js
new file mode 100644
index 0000000..9d0826a
--- /dev/null
+++ b/src/models/Message.js
@@ -0,0 +1,12 @@
+// models/Message.js
+import mongoose from 'mongoose';
+
+const MessageSchema = new mongoose.Schema({
+ roomId: String,
+ senderId: String,
+ senderName: String,
+ message: String,
+ createdAt: { type: Date, default: Date.now }
+});
+
+export default mongoose.models.Message || mongoose.model('Message', MessageSchema);
diff --git a/src/models/User.js b/src/models/User.js
new file mode 100644
index 0000000..bd0b0f6
--- /dev/null
+++ b/src/models/User.js
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+
+const userSchema = new mongoose.Schema({
+ userid: { type: String, required: true, unique: true },
+ password: { type: String, required: true },
+ nickname: { type: String, required: true },
+ username: { type: String, required: true },
+ phone: { type: String, required: true },
+ address: { type: String, required: true },
+ email: { type: String, required: true, unique: true },
+ createdAt: { type: Date, default: Date.now }
+});
+
+// 연결된 DB가
+const User = mongoose.models.user || mongoose.model('user', userSchema, 'account');
+
+export default User;
+
diff --git a/src/models/UserDiscription.js b/src/models/UserDiscription.js
new file mode 100644
index 0000000..6cb3067
--- /dev/null
+++ b/src/models/UserDiscription.js
@@ -0,0 +1,14 @@
+import mongoose from 'mongoose';
+
+const userDescriptionSchema = new mongoose.Schema({
+
+ description: {type: String, required: true},
+ image: { type: String, required: true },
+ createdAt: { type: Date, default: Date.now }
+});
+
+// 연결된 DB가
+const User = mongoose.models.user || mongoose.model('UserDescription', userDiscriptionSchema);
+
+export default User;
+
diff --git a/src/models/item.js b/src/models/item.js
new file mode 100644
index 0000000..feea39a
--- /dev/null
+++ b/src/models/item.js
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+
+const itemSchema = new mongoose.Schema({
+ title: { type: String, required: true, unique: true },
+ description: { type: String, required: true },
+ writer: { type: String, required: true },
+ itemCategory: { type: String, required: true },
+ totalNumberOfRecruits: { type: Number, required: true },
+ numberOfRecruitedPersonnel: { type: Number, required: true },
+ totalPrice: { type: Number, required: true },
+ pricePerEachPerson: { type: Number, required: true },
+ tradeType: { type: String, required: true },
+ image: { type: String, required: true },
+ createdAt: { type: Date, default: Date.now }
+});
+
+
+const Item = mongoose.models.Item || mongoose.model('ddip', itemSchema,'item');
+
+export default Item;