+ );
+}
diff --git a/src/app/components/common/footer.js b/src/app/components/common/footer.js
new file mode 100644
index 0000000..2ba3ca4
--- /dev/null
+++ b/src/app/components/common/footer.js
@@ -0,0 +1,10 @@
+
+
+export default function Footer(){
+
+ return (
+
+ )
+}
\ 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..68fddf1
--- /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 (
+
+ )
+}
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 (
+
+ );
+}
\ 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..27316fc
--- /dev/null
+++ b/src/app/components/post/post-box.js
@@ -0,0 +1,280 @@
+'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 [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;
+ setForm(prev => ({
+ ...prev,
+ pricePerEachPerson: calculatedPrice
+ }));
+ }
+ }, [form.totalPrice, form.totalNumberOfRecruits]);
+
+ // input 값이 바뀔 때마다 form state 업데이트
+ const handleChange = (e) => {
+ const { name, value, type } = e.target;
+ setForm((prev) => ({
+ ...prev,
+ [name]: type === 'number' ? Number(value) : value,
+ }));
+ };
+
+ // 이미지 파일 따로 처리
+ const handleImageChange = (e) => {
+ setForm((prev) => ({
+ ...prev,
+ image: e.target.files[0], // 실제 파일 객체 저장
+ }));
+ };
+
+ // 등록 처리 함수 수정본
+ 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;
+ } else {
+ body = JSON.stringify(payload);
+ headers = { 'Content-Type': 'application/json' };
+ }
+
+ const response = await fetch('/api/post/write', { method: 'POST', headers, body });
+
+ // 응답이 JSON인지 확인
+ 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
+ );
+}
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..3fa61d0
--- /dev/null
+++ b/src/app/components/product/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 (
+
+ );
+}
\ No newline at end of file
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..8b57b2a
--- /dev/null
+++ b/src/app/feature/auth/page.js
@@ -0,0 +1,39 @@
+'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 (
+
+ );
+}
+
diff --git a/src/app/feature/category/page.js b/src/app/feature/category/page.js
new file mode 100644
index 0000000..151105a
--- /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(
+
+ )
+}
diff --git a/ddip/src/app/home/main.js b/src/app/feature/home/page.js
similarity index 57%
rename from ddip/src/app/home/main.js
rename to src/app/feature/home/page.js
index 269c3be..d803da6 100644
--- a/ddip/src/app/home/main.js
+++ b/src/app/feature/home/page.js
@@ -1,9 +1,11 @@
-import Image from 'next/image'
+import Image from "next/image";
-export function Main() {
+
+export default function Main() {
return (
-
+
+
@@ -16,18 +18,8 @@ export function Main() {
SEARCH
-
-
- {Array.from({ length: 10 }).map((_, index) => (
-
- ))}
-
-
+
-
)
}
\ 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..339efd8
--- /dev/null
+++ b/src/app/feature/mypage/components/category-sidebar.js
@@ -0,0 +1,56 @@
+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}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/app/feature/mypage/components/manage.js b/src/app/feature/mypage/components/manage.js
new file mode 100644
index 0000000..773ac20
--- /dev/null
+++ b/src/app/feature/mypage/components/manage.js
@@ -0,0 +1,109 @@
+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()}원
+
+
+ );
+}
diff --git a/src/app/feature/mypage/components/mypage.js b/src/app/feature/mypage/components/mypage.js
new file mode 100644
index 0000000..80fad80
--- /dev/null
+++ b/src/app/feature/mypage/components/mypage.js
@@ -0,0 +1,73 @@
+import Image from 'next/image';
+import ProductManage from "./manage";
+import ProductWishList from "./wishlist";
+import ProfileEdit from "./profile-edit";
+import { useState, useEffect } from 'react';
+
+export default function MypageMain() {
+ const [selected, setSelected] = useState('내 상품 관리');
+
+ const [editMode, setEditMode] = useState(false);
+ // 간이 사용자 정보 상태값
+ const [userInfo, setUserInfo] = useState({
+ name: '독고다히',
+ username: 'dokkk0dahee',
+ bio: '한줄 소개 쏼라쏼라 어쩌르티비 홍홍홍~',
+ address: '주소에옹 주소주소주소~',
+ picture: '/hoho.png',
+ });
+
+
+ return (
+
+ {/* 이용자 정보 */}
+ {editMode ? (
+ setEditMode(false)}
+ />
+ ) : (
+
+ {/* 좌측: 사진, 정보 수정 */}
+
+
+
+ setEditMode(true)}>정보 수정하기
+
+
+ {/* 우측: 상품 정보 */}
+
+ {/* 사용자 이름 */}
+
{userInfo.name}
+ {/* 사용자 아이디 */}
+
@{userInfo.username}
+ {/* */}
+
{userInfo.bio}
+ {/* */}
+
{userInfo.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..d6e8b68
--- /dev/null
+++ b/src/app/feature/mypage/components/profile-edit.js
@@ -0,0 +1,169 @@
+'use client'
+import { useState, useRef } from 'react';
+import Image from 'next/image';
+
+export default function ProfileEdit({ userInfo, setUserInfo, onCancel }) {
+ const fileInputRef = useRef(null); // 파일 input 참조
+
+ const [form, setForm] = useState({
+ name: userInfo.name,
+ username: userInfo.username,
+ bio: userInfo.bio,
+ address: userInfo.address,
+ picture: userInfo.picture,
+ });
+
+ 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, picture: imageUrl });
+ }
+ };
+
+ const [formError, setFormError] = useState({
+ name: '',
+ username: '',
+ bio: '',
+ address: '',
+ });
+
+ const handleSave = () => {
+ //여기에 api 추가하래유
+
+ const errors = {
+ name: form.name.trim() ? '' : '별명을 입력해주세요.',
+ username: form.username.trim() ? '' : '아이디를 입력해주세요.',
+ bio: form.bio.trim() ? '' : '한줄 소개를 입력해주세요.',
+ address: form.address.trim() ? '' : '주소를 입력해주세요.',
+ };
+
+ setFormError(errors);
+
+ const hasError = Object.values(errors).some(error => error);
+ if (hasError) return;
+
+ setUserInfo(form); // 부모 컴포넌트에 변경 반영
+ onCancel(); // 편집 종료
+ };
+
+ return (
+
+ {/* 왼쪽: 프로필 이미지 */}
+
+ {form.picture ? (
+
) : (
+
+ 이미지 없음
+
+ )}
+
+
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..1eb41c8
--- /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()}원
+
+
+ );
+}
diff --git a/src/app/feature/mypage/page.js b/src/app/feature/mypage/page.js
new file mode 100644
index 0000000..20200af
--- /dev/null
+++ b/src/app/feature/mypage/page.js
@@ -0,0 +1,16 @@
+import { useState } from "react";
+import MypageMain from "./components/mypage";
+import CategorySidebar from "./components/category-sidebar";
+
+export function Mypage() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/feature/product/[id]/page.js b/src/app/feature/product/[id]/page.js
new file mode 100644
index 0000000..d45cbdc
--- /dev/null
+++ b/src/app/feature/product/[id]/page.js
@@ -0,0 +1,130 @@
+'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";
+
+
+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 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]);
+
+ return (
+
+
+
+
+
+
+ {/* 좌측: 이미지 박스 */}
+
+
+
+
+ {/* 우측: 상품 정보 */}
+
+
{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..58ef609
--- /dev/null
+++ b/src/app/feature/shortcut/shortcut.js
@@ -0,0 +1,62 @@
+import Image from 'next/image';
+import ChatBox from '@components/chat/chatting';
+import PostBox from '@components/post/post-box';
+
+import {useEffect, useState} from "react";
+
+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 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/layout.js b/src/app/layout.js
similarity index 75%
rename from ddip/src/app/layout.js
rename to src/app/layout.js
index 7bf337d..bf34568 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/common/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..f5938f0
--- /dev/null
+++ b/src/app/page.js
@@ -0,0 +1,28 @@
+'use client'
+
+import Category from '@components/category/category-form';
+import Main from './feature/home/page'
+import Footer from "@components/common/footer";
+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..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/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/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/item.js b/src/models/item.js
new file mode 100644
index 0000000..9e87d02
--- /dev/null
+++ b/src/models/item.js
@@ -0,0 +1,19 @@
+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 },
+ createdAt: { type: Date, default: Date.now }
+});
+
+
+const Item = mongoose.models.Item || mongoose.model('ddip', itemSchema,'item');
+
+export default Item;