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..9213f77
--- /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/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/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 (
-
+ {/* 상단 */}
+
+
+ {/* 채팅 부분 */}
+
+ {messages.map((msg) => (
+
+
+
+ {/* 이름 (상대방 메시지일 때만 표시) */}
+ {msg.sender !== "me" && (
+
+ {msg.name}
+
+ )}
+ {/* 말풍선 + 시간*/}
+
+ {/* 말풍선 */}
+
+ {msg.text}
+
+ {/* 시간 */}
+
+ {msg.time}
+
+
+
+
+ ))}
+ {/* 스크롤 제일 밑에 위치한 빈 div */}
+
+
+
+ {/* 입력 부분 */}
+
+ setInput(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleSend()}
+ 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..e69de29
diff --git a/src/app/components/common/nav.js b/src/app/components/common/nav.js
new file mode 100644
index 0000000..c2bb3b3
--- /dev/null
+++ b/src/app/components/common/nav.js
@@ -0,0 +1,49 @@
+import Image from 'next/image';
+
+import {useEffect, useState} from "react";
+
+export default function Navs({onSelect}) {
+ const [moving,setMoved] = useState(false);
+ const [activeIndex, setActiveIndex] = useState(null);
+ const handleClick = (selected_index) => {
+ setActiveIndex(selected_index);
+ if (onSelect) onSelect(selected_index);
+ };
+
+ 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">
+
+
+
+
+
+
+ {
+ if (onSelect) onSelect('sign'); // 예: 'sign' 모드로 바꾸기
+ }}
+ className="absolute right-20 cursor-pointer text-black shadow-green-500"
+ >
+ 로그인/회원가입
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/app/components/common/post.js b/src/app/components/common/post.js
new file mode 100644
index 0000000..31b1243
--- /dev/null
+++ b/src/app/components/common/post.js
@@ -0,0 +1,71 @@
+import { useState } from "react";
+import Image from "next/image";
+import category_lists from "@constants/simpleDB";
+
+export default function PostBox({ onClose }) {
+ const [activeTags, setActiveTags] = useState([]);
+
+ const handleClick = (value) => {
+ setActiveTags((prev) =>
+ prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
+ );
+ };
+
+ return (
+
+ {/* 상단 */}
+
+
+ {/* 제목 */}
+
+ 제목:
+
+
+
+ {/* 이미지 첨부 */}
+
+
+ {/* 본문 입력 */}
+
+
+
+
+ {/* 카테고리 선택 */}
+
+ 제품 태그 선택
+
+
+
+ {category_lists.map((item) => {
+ const isActive = activeTags.includes(item.value);
+ return (
+
handleClick(item.value)}
+ className="w-full h-full flex-col justify-center text-center">
+
+ {item.name || '이름 없음'}
+
+ );
+ })}
+
+
+
글 게시
+
+ );
+}
diff --git a/src/app/components/common/shortcut.js b/src/app/components/common/shortcut.js
new file mode 100644
index 0000000..5a39701
--- /dev/null
+++ b/src/app/components/common/shortcut.js
@@ -0,0 +1,36 @@
+import Image from 'next/image';
+import ChatList from '../chat/chat-list';
+import ChatBox from '../chat/chatting';
+import PostBox from './post';
+
+import {useEffect, useState} from "react";
+
+export default function Shortcut({}) {
+ const [isCOpen, setIsCOpen] = useState(false);
+ const [isPOpen, setIsPOpen] = useState(false);
+
+
+ const toggleChat = () => setIsCOpen(!isCOpen);
+ const togglePost = () => setIsPOpen(!isPOpen);
+
+ return (
+ <>
+
+
+ {isCOpen &&
}
+
+
+
+
+ {isPOpen &&
}
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/app/components/footer.js b/src/app/components/footer.js
new file mode 100644
index 0000000..f094309
--- /dev/null
+++ b/src/app/components/footer.js
@@ -0,0 +1,10 @@
+
+
+export default function Footer(){
+
+ return (
+
+ 푸터입니다
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/components/item-list.js b/src/app/components/item-list.js
new file mode 100644
index 0000000..7358235
--- /dev/null
+++ b/src/app/components/item-list.js
@@ -0,0 +1,76 @@
+'use client';
+
+import Image from 'next/image';
+import { useState, useEffect } from 'react';
+import SimpleDB from "@constants/simpleDB";
+
+
+export default function ItemList({ selectedCategoryValue, onSelect }) {
+ const [items, setItems] = useState([]);
+ const [activeIndex, setActiveIndex] = useState(null);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const res = await fetch('/api'); // 전체 object 가져오기
+ if (!res.ok) throw new Error('API 호출 실패');
+ const data = await res.json();
+ setItems(data);
+ console.log('현재 API 데이터:', data); // 데이터 구조 확인
+ } catch (error) {
+ console.error('데이터 불러오기 실패:', error);
+ }
+ console.log('선택된 카테고리:', selectedCategoryValue);
+ };
+
+ fetchData();
+ }, []); // ← 빈 배열: 첫 로딩 시에만 fetch
+
+
+
+ const handleClick = (id) => {
+ setActiveIndex(id);
+ if (onSelect) onSelect(id);
+ };
+
+ const filteredItems = selectedCategoryValue
+ ? items.filter(item => item.value === selectedCategoryValue)
+ : items;
+ console.log('items:',items);
+ return (
+
+
+ {
+ selectedCategoryValue
+ ? (SimpleDB.find(cat => cat.value === selectedCategoryValue)?.name || '알 수 없음')
+ : '전체 항목'
+ }
+
+
+
+ {filteredItems.map((item) => (
+
handleClick(item._id)}
+ >
+
+ {item.name}
+
+ ))}
+
+
+ );
+}
+
+export async function GET(request) {
+ // ...로직
+ return new Response(JSON.stringify({ ok: true }), { status: 200 });
+}
\ No newline at end of file
diff --git a/ddip/src/app/home/nav.js b/src/app/components/nav.js
similarity index 63%
rename from ddip/src/app/home/nav.js
rename to src/app/components/nav.js
index 6ec828c..772bc05 100644
--- a/ddip/src/app/home/nav.js
+++ b/src/app/components/nav.js
@@ -1,8 +1,9 @@
import Image from 'next/image';
import {useEffect, useState} from "react";
+import Link from 'next/link';
-export function Navs() {
+export default function Navs() {
const [moving,setMoved] = useState(false);
useEffect(() => {
@@ -16,22 +17,25 @@ export function Navs() {
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/src/app/components/writing-form.js b/src/app/components/writing-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/api/login/route.js b/src/app/feature/auth/api/login/route.js
new file mode 100644
index 0000000..e8e1e48
--- /dev/null
+++ b/src/app/feature/auth/api/login/route.js
@@ -0,0 +1,48 @@
+import { NextResponse } from 'next/server';
+import dbConnect from '../../../../../lib/mongodb';
+import mongoose from 'mongoose';
+import bcrypt from 'bcryptjs';
+
+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 userDb = mongoose.connection.useDb('user');
+ const accountCollection = userDb.collection('account');
+
+ const user = await accountCollection.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 }
+ );
+ }
+
+ return NextResponse.json(
+ { success: true, username: user.username },
+ { status: 200 }
+ );
+ } catch (error) {
+ return NextResponse.json(
+ { success: false, message: '서버 에러 발생' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/app/feature/auth/api/signup/route.js b/src/app/feature/auth/api/signup/route.js
new file mode 100644
index 0000000..6d4c174
--- /dev/null
+++ b/src/app/feature/auth/api/signup/route.js
@@ -0,0 +1,70 @@
+import { NextResponse } from 'next/server';
+import {clientPromise} from '../../../../../lib/mongodb'; // clientPromise로 변경
+import bcrypt from 'bcryptjs';
+
+export async function POST(request) {
+ try {
+
+ const client = await clientPromise; // 클라이언트 연결
+
+ const userDb = client.db('user');
+ const accountCollection = userDb.collection('account');
+
+ const data = await request.json();
+ const { userid, userpw, checkpw, username, name, phone, address, email } = data;
+
+ // 필수값 체크 (기존 코드와 동일)
+ if (!userid || !userpw || !checkpw || !username || !name || !phone || !address || !email) {
+ return NextResponse.json(
+ { success: false, message: '모든 빈칸을 채워주세요!' },
+ { status: 400 }
+ );
+ }
+ if (userpw !== checkpw) {
+ return NextResponse.json(
+ { success: false, message: '비밀번호를 다시 확인해주세요!' },
+ { status: 400 }
+ );
+ }
+
+ // 중복 체크
+ const existing = await accountCollection.findOne({
+ $or: [{ userid }, { email }]
+ });
+
+ if (existing) {
+ return NextResponse.json(
+ { success: false, message: '이미 존재하는 아이디 또는 이메일입니다.' },
+ { status: 409 }
+ );
+ }
+
+ // 비밀번호 암호화
+ const hashedPw = await bcrypt.hash(userpw, 10);
+
+ // 데이터 삽입
+ await accountCollection.insertOne({
+ userid,
+ password: hashedPw,
+ username,
+ name,
+ phone,
+ address,
+ email,
+ createdAt: new Date()
+ });
+
+ 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/feature/auth/components/agreement.js b/src/app/feature/auth/components/agreement.js
new file mode 100644
index 0000000..ff24ce2
--- /dev/null
+++ b/src/app/feature/auth/components/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/feature/auth/components/input.js b/src/app/feature/auth/components/input.js
new file mode 100644
index 0000000..0ccd48b
--- /dev/null
+++ b/src/app/feature/auth/components/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/feature/auth/components/login-form.js b/src/app/feature/auth/components/login-form.js
new file mode 100644
index 0000000..d81fa0a
--- /dev/null
+++ b/src/app/feature/auth/components/login-form.js
@@ -0,0 +1,84 @@
+
+import { useState } from 'react';
+import { validatePassword } from './validate';
+import Input from './input';
+import Image from "next/image";
+import {redirect} from "next/navigation";
+
+export default function LoginForm() {
+ const [form, setForm] = useState({
+ userid: '',
+ userpw: '',
+ });
+ const [error, setError] = useState('');
+
+ 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자 이상 입력해주세요');
+ }
+
+ try {
+ const response = await fetch('/feature/auth/api/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('사실 에러 아니지롱 데헷😋');
+ } else {
+ setError(result.message || '로그인에 실패했습니다.');
+ }
+ } catch (err) {
+ setError('로그인 요청 중 오류가 발생했습니다.');
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/app/feature/auth/components/signup-form.js b/src/app/feature/auth/components/signup-form.js
new file mode 100644
index 0000000..aa38967
--- /dev/null
+++ b/src/app/feature/auth/components/signup-form.js
@@ -0,0 +1,138 @@
+import { useState } from 'react';
+import { validateEmail, validatePassword } from './validate';
+import Input from './input';
+import AgreementBox from './agreement';
+import Image from "next/image";
+
+export default function SignupForm({setStatus}) {
+ const [agreed, setAgreed] = useState(false);
+ const [form, setForm] = useState({
+ userid: '',
+ userpw: '',
+ checkpw: '',
+ username: '',
+ name: '',
+ phone: '',
+ address: '',
+ email: '',
+ });
+
+
+ const [error, setError] = useState('');
+
+ 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자 이상 입력해주세요');
+
+ setError('');
+
+ if (form.userpw !== form.checkpw) {
+ return setError("비밀번호가 일치하지 않습니다.");
+ }
+
+ const response = await fetch("/feature/auth/api/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);
+ }
+ };
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/feature/auth/components/validate.js b/src/app/feature/auth/components/validate.js
new file mode 100644
index 0000000..b332ab8
--- /dev/null
+++ b/src/app/feature/auth/components/validate.js
@@ -0,0 +1,7 @@
+export function validateEmail(email) {
+ return /\S+@\S+\.\S+/.test(email);
+}
+
+export function validatePassword(pw) {
+ return pw.length >= 8;
+}
\ No newline at end of file
diff --git a/src/app/feature/auth/page.js b/src/app/feature/auth/page.js
new file mode 100644
index 0000000..fbec47f
--- /dev/null
+++ b/src/app/feature/auth/page.js
@@ -0,0 +1,73 @@
+// 'use client';
+//
+// import SignupForm from './[id]/signup-form.js';
+// import LoginForm from './[id]/login-form.js';
+// import Image from 'next/image';
+//
+// /*
+// import { connectDB } from "../util/database";
+//
+// export async function getServerSideProps() {
+// const db = (await connectDB).db("forum");
+// const posts = await db.collection("post").find().toArray();
+// return {
+// props: { posts: JSON.parse(JSON.stringify(posts)) },
+// };
+// }
+// */
+//
+// export function Signup() {
+// return (<>
+//
+//
+//
+//
+//
+//
+// 회원가입
+//
+//
+//
+// >
+// );
+// }
+
+'use client';
+
+import SignupForm from './components/signup-form';
+import Navs from '@components/nav.js';
+import LoginForm from "./components/login-form";
+import { useState } from "react";
+
+export default function SignUp() {
+ const [status, setStatus] = useState(false);
+
+ return (
+
+
+
+
+
+ {status
+ ?
+ :
+ }
+
+ {/*
+ 비밀번호 찾기, 아이디 찾기 구현
+ 이메일로 코드 보내고 인증 같은 절차 구현
+ */}
+
+ setStatus(!status)}
+ className="mb-3 text-blue-500 underline"
+ >
+ {status ? '로그인으로 돌아가기' : '회원가입'}
+
+
+
+ );
+}
+
diff --git a/src/app/feature/category/components/category-form.js b/src/app/feature/category/components/category-form.js
new file mode 100644
index 0000000..8fb51d5
--- /dev/null
+++ b/src/app/feature/category/components/category-form.js
@@ -0,0 +1,31 @@
+import Image from 'next/image'
+import category_lists from '@constants/simpleDB';
+
+export default function Category({onSelect}) {
+
+ const handleClick = (category) => {
+ onSelect(category);
+ };
+
+ return (
+
+ 카테고리
+
+ {category_lists.map((item) => (
+
handleClick(item.value)} className="relative w-full h-full p-4
+ flex flex-col justify-center text-center">
+
+
+
+
+ {item.name || '이름 없음'}
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/app/feature/category/page.js b/src/app/feature/category/page.js
new file mode 100644
index 0000000..baa1191
--- /dev/null
+++ b/src/app/feature/category/page.js
@@ -0,0 +1,25 @@
+import Navs from "@components/nav";
+import ItemList from "@components/item-list";
+import {useState} from "react";
+import Category from "./components/category-form";
+
+
+export default function CategoryPage() {
+ const [selectedCategory, setSelectedCategory] = useState('');
+ const [selectedProduct, setSelectedProduct] = useState(null);
+
+ return(
+
+
+
+
+
+ {/* 迭 ʰ 迭 */}
+
+
+
+
{selectedCategory}
+
+
+ )
+}
diff --git a/src/app/feature/profile/components/info-sidebar.js b/src/app/feature/profile/components/info-sidebar.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/feature/profile/components/my-info-form.js b/src/app/feature/profile/components/my-info-form.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/feature/profile/components/my-sidebar.js b/src/app/feature/profile/components/my-sidebar.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/feature/profile/components/myproduct-managing-form.js b/src/app/feature/profile/components/myproduct-managing-form.js
new file mode 100644
index 0000000..e69de29
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/ddip/src/app/globals.css b/src/app/globals.css
similarity index 59%
rename from ddip/src/app/globals.css
rename to src/app/globals.css
index 94f37cb..a52b56c 100644
--- a/ddip/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,11 +1,17 @@
@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
@@ -19,3 +25,7 @@ body {
.select-transition{
@apply transition-all duration-100 ease-linear cursor-pointer ;
}
+
+html,body {
+ height: 100%;
+}
diff --git a/ddip/src/app/home/main.js b/src/app/home/page.js
similarity index 57%
rename from ddip/src/app/home/main.js
rename to src/app/home/page.js
index 269c3be..3b6290e 100644
--- a/ddip/src/app/home/main.js
+++ b/src/app/home/page.js
@@ -1,9 +1,13 @@
-import Image from 'next/image'
+import Navs from "@components/nav";
+import Category from "../feature/category/components/category-form";
+import Image from "next/image";
-export function Main() {
+
+export default function Main() {
return (
-
+
+
@@ -16,18 +20,8 @@ export function Main() {
SEARCH
-
-
- {Array.from({ length: 10 }).map((_, index) => (
-
- ))}
-
-
+
-
)
}
\ No newline at end of file
diff --git a/ddip/src/app/layout.js b/src/app/layout.js
similarity index 76%
rename from ddip/src/app/layout.js
rename to src/app/layout.js
index 7bf337d..59c7af6 100644
--- a/ddip/src/app/layout.js
+++ b/src/app/layout.js
@@ -1,5 +1,6 @@
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import Footer from "@components/footer";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -20,10 +21,15 @@ export default function RootLayout({ children }) {
return (
{children}
+
+
);
}
+
diff --git a/src/app/page.js b/src/app/page.js
new file mode 100644
index 0000000..a9912b4
--- /dev/null
+++ b/src/app/page.js
@@ -0,0 +1,59 @@
+'use client';
+
+import Category from './feature/category/components/category-form';
+import Main from './home/page'
+import Shortcut from './components/common/shortcut';
+import { useState } from 'react';
+import ItemList from './components/item-list';
+import ItemExplain from './pruduct/page';
+import Navs from "@components/nav";
+import Footer from "@components/footer";
+
+
+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(productID);
+ setMode('product');
+ }} />
+ >
+ )}
+
+ {mode === 'product' && (
+ <>
+
+ >
+ )}
+
+
+ );
+
+}
+
diff --git a/src/app/pruduct/component/category-sidebar.js b/src/app/pruduct/component/category-sidebar.js
new file mode 100644
index 0000000..9f8b44e
--- /dev/null
+++ b/src/app/pruduct/component/category-sidebar.js
@@ -0,0 +1,38 @@
+import React, { useState } from 'react';
+
+const categories = [
+ '식재료',
+ '간편식/냉동식품',
+ '생활용품',
+ '대용량',
+ '배달음식',
+ '나눔템',
+];
+
+export default function CategorySidebar({ onSelect }) {
+ const [selected, setSelected] = useState(null);
+
+ const handleClick = (category) => {
+ setSelected(category);
+ onSelect(category);
+ };
+
+ return (
+
+ 카테고리
+
+ {categories.map((category) => (
+ handleClick(category)}
+ className={`cursor-pointer hover:text-[#404040] font-medium transition-colors ${
+ selected === category ? 'font-medium text-[#404040]' : ''
+ }`}>
+ {category}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/app/pruduct/component/item.js b/src/app/pruduct/component/item.js
new file mode 100644
index 0000000..7b1d2da
--- /dev/null
+++ b/src/app/pruduct/component/item.js
@@ -0,0 +1,62 @@
+import Image from 'next/image'
+import { useState, useEffect } from 'react';
+
+
+export default function ItemMain({ selectedProduct, onSelect }) {
+ return (
+
+
+ {/* 좌측: 이미지 박스 */}
+
+
+
+
+ {/* 우측: 상품 정보 */}
+
+
뿌잉뿌잉
+
+ 2,500원
+ 30,000원
+
+
+
+
+
+
+
모집 인원 | 8명 (2/8)
+
거래 방식 | 직거래
+
카테고리 | 식재료
+
+
+ {/* 버튼들 */}
+
+
+ 톡 보내기
+
+ 참여하기
+
+
+
+
+ {/* 상품 설명 */}
+
+
상품 설명
+
+ 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명
+ 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명
+ 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명
+ 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명 상품 설명
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/pruduct/page.js b/src/app/pruduct/page.js
new file mode 100644
index 0000000..6c41eca
--- /dev/null
+++ b/src/app/pruduct/page.js
@@ -0,0 +1,21 @@
+import Image
+ from 'next/image'
+import { useState, useEffect } from 'react';
+import Item from './component/item';
+import CategorySidebar from './component/category-sidebar';
+
+//페이지 연결 이름 바꿔야함니두 아마?
+export default function pruduct({ onSelect }) {
+ const [selectedCategory, setSelectedCategory] = useState(null);
+
+ return (
+
+ )
+};
diff --git a/src/app/user/model/user.js b/src/app/user/model/user.js
new file mode 100644
index 0000000..7f1b522
--- /dev/null
+++ b/src/app/user/model/user.js
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+
+const userSchema = new mongoose.Schema({
+ userid: { type: String, required: true, unique: true },
+ password: { type: String, required: true },
+ username: { type: String, required: true },
+ name: { 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 }
+});
+
+const User = mongoose.models.User || mongoose.model('User', userSchema);
+
+export default User;
diff --git a/src/app/util/chat.js b/src/app/util/chat.js
new file mode 100644
index 0000000..db57672
--- /dev/null
+++ b/src/app/util/chat.js
@@ -0,0 +1,65 @@
+// pages/chat.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/legacy(no_use)/item-list.js b/src/legacy(no_use)/item-list.js
new file mode 100644
index 0000000..641c5d4
--- /dev/null
+++ b/src/legacy(no_use)/item-list.js
@@ -0,0 +1,66 @@
+'use client';
+
+import Image from 'next/image';
+import { useState, useEffect } from 'react';
+import SimpleDB from "@constants/simpleDB";
+
+export default function ItemList({ selectedCategoryValue, onSelect }) {
+ const [items, setItems] = useState([]);
+ const [activeIndex, setActiveIndex] = useState(null);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const res = await fetch('/api'); // API 경로 수정
+ if (!res.ok) throw new Error('API 호출 실패');
+ const data = await res.json();
+ setItems(data);
+ } catch (error) {
+ console.error('데이터 불러오기 실패:', error);
+ }
+ };
+
+ fetchData();
+ }, []);
+
+ const handleClick = (id) => {
+ setActiveIndex(id);
+ if (onSelect) onSelect(id);
+ };
+
+ const filteredItems = selectedCategoryValue
+ ? items.filter(item => item.value === selectedCategoryValue)
+ : items;
+
+ return (
+
+
+ {
+ selectedCategoryValue
+ ? (SimpleDB.find(cat => cat.value === selectedCategoryValue)?.name || '알 수 없음')
+ : '전체 항목'
+ }
+
+
+
+ {filteredItems.map((item) => (
+
handleClick(item._id)}
+ >
+
+ {item.name}
+
+ ))}
+
+
+ );
+}
diff --git a/src/legacy(no_use)/mongodb.js b/src/legacy(no_use)/mongodb.js
new file mode 100644
index 0000000..91b435f
--- /dev/null
+++ b/src/legacy(no_use)/mongodb.js
@@ -0,0 +1,19 @@
+import { MongoClient } from 'mongodb';
+
+const uri = process.env.MONGODB_URI;
+const options = {};
+
+let client;
+let clientPromise;
+
+if (!process.env.MONGODB_URI) {
+ throw new Error('MONGODB_URI 환경 변수가 설정되지 않았습니다.');
+}
+
+if (!global._mongoClientPromise) {
+ client = new MongoClient(uri, options);
+ global._mongoClientPromise = client.connect();
+}
+clientPromise = global._mongoClientPromise;
+
+export default clientPromise;
diff --git a/src/legacy(no_use)/route.js b/src/legacy(no_use)/route.js
new file mode 100644
index 0000000..233fca9
--- /dev/null
+++ b/src/legacy(no_use)/route.js
@@ -0,0 +1,24 @@
+import clientPromise from '@/lib/mongodb';
+
+export async function GET() {
+ try {
+ const client = await clientPromise;
+ const db = client.db('ddip'); // ddip DB 선택
+ const collection = db.collection('object'); // object 컬렉션 선택
+ const data = await collection.find({}).toArray();
+
+ return new Response(JSON.stringify(data), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ } catch (error) {
+ console.error('MongoDB 조회 에러:', error);
+ return new Response(
+ JSON.stringify({ message: '서버 에러 발생' }),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ }
+ );
+ }
+}
diff --git a/src/lib/dbConnect.js b/src/lib/dbConnect.js
new file mode 100644
index 0000000..6cc04a8
--- /dev/null
+++ b/src/lib/dbConnect.js
@@ -0,0 +1,34 @@
+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(MONGODB_URI, {
+ bufferCommands: false,
+ }).then(mongoose => 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/mongodb.js b/src/lib/mongodb.js
new file mode 100644
index 0000000..862276a
--- /dev/null
+++ b/src/lib/mongodb.js
@@ -0,0 +1,43 @@
+import mongoose from 'mongoose';
+import { MongoClient } from 'mongodb';
+
+const uri = process.env.MONGODB_URI;
+const options = {};
+
+let client;
+
+if (!uri) {
+ throw new Error('MONGODB_URI 환경 변수가 설정되지 않았습니다.');
+}
+//mongoose 범위
+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(uri).then((mongoose) => {
+ return mongoose;
+ });
+ }
+ cached.conn = await cached.promise;
+ return cached.conn;
+}
+//mongodb부분
+let clientPromise;
+
+if (!global._mongoClientPromise) {
+ client = new MongoClient(uri, options);
+ global._mongoClientPromise = client.connect();
+}
+clientPromise = global._mongoClientPromise;
+
+export default dbConnect;
+
+export {clientPromise};
+
+