diff --git a/package-lock.json b/package-lock.json index 4e6cc9e..eee04dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "next": "14.0.3", "react": "^18", "react-dom": "^18", + "recoil": "^0.7.7", "sass": "^1.69.5" }, "devDependencies": { @@ -2904,6 +2905,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -4285,6 +4291,25 @@ "node": ">=8.10.0" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", diff --git a/package.json b/package.json index 9259bd2..373a9e2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "next": "14.0.3", "react": "^18", "react-dom": "^18", + "recoil": "^0.7.7", "sass": "^1.69.5" }, "devDependencies": { diff --git a/src/app/feed/page.tsx b/src/app/feed/page.tsx index 3a76205..7fe9b9e 100644 --- a/src/app/feed/page.tsx +++ b/src/app/feed/page.tsx @@ -1,20 +1,30 @@ "use client"; -import Menubar from "@/component/MenuBar" -import WeatherBar from "@/component/WeatherBar" + +import FeedSearch from "@/component/FeedSearch"; +import WeatherBar from "@/component/WeatherBar"; +import FeedCategory from "@/component/FeedCategory"; +import FeedContenets from "@/component/FeedContents"; +import Menubar from "@/component/MenuBar"; + +import { RecoilRoot } from "recoil"; + + export default function Feed(){ return(<> -
- + +
+ +
-
-
- -
+ + +
+
) } \ No newline at end of file diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 4601f2c..9118b94 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -5,12 +5,29 @@ import { useState } from "react"; import InputBar from "@/component/InputBar"; import Menubar from "@/component/MenuBar"; +import axios from "axios"; + +///////////////////////////해야하는 작업///////////////////////////// + +// 희성이형하고 같이해야함 +//1. 비밀번호 확인 기능 +//2. 이메일 인증 전에는 데이터 전송을 해주지 않고, 이메일 전송을 통해 토큰을 받았을때만 +// 회원가입 데이터를 전송해주는 기능 +//3. 소셜데이터 로그인 버튼과 소셜 로그인 기능 + +/////////////////////////////////////////////////////////////////// export default function Register(): JSX.Element { - const [email, setEmail] = useState(""); - const [name, setName] = useState(""); - const [nickname, setNickname] = useState(""); - const [password, setPassword] = useState(""); - const [repassword, setRepassword] = useState(""); + const [email, setEmail] = useState(""); + const [name, setName] = useState(""); + const [nickname, setNickname] = useState(""); + const [password, setPassword] = useState(""); + const [repassword, setRepassword] = useState(""); + console.log("========================================"); + console.log("이메일", email); + console.log("이름", name); + console.log("닉네임", nickname); + console.log("비번", password); + console.log("비번 확인", repassword); const validateEmail = (inputValue: string) => { const emailFormat = @@ -27,7 +44,39 @@ export default function Register(): JSX.Element { return inputValue === password; }; - const caster_register = () => {}; + const verify_btn = async() => { + console.log("Verify 버튼이 클릭되었습니다."); + + const verify_email = await axios({ + method : "POST", + url : "https://www.jerneithe.site/user/profile", + data : email + }); + + + } + + const caster_register = async() => { + const req_regdata = { + email, + name, + nickname, + password, + repassword, + } + + console.log(req_regdata); + + const register_data = await axios({ + method : "POST", + url : "https://www.jerneithe.site/user/signup", + data : req_regdata + }); + + + + }; + return (
@@ -49,6 +98,7 @@ export default function Register(): JSX.Element { button // 버튼을 사용한다고 명시 buttonId="btn_verify" buttonText="인증" + onButtonClick={verify_btn} autoFocus /> @@ -93,7 +143,7 @@ export default function Register(): JSX.Element { onChange={(value: string) => setRepassword(value)} />
- diff --git a/src/component/FeedCateDetail.tsx b/src/component/FeedCateDetail.tsx new file mode 100644 index 0000000..ce90b85 --- /dev/null +++ b/src/component/FeedCateDetail.tsx @@ -0,0 +1,215 @@ +import { useRecoilState, useRecoilValue } from "recoil"; +import { Dispatch, SetStateAction, useState } from "react"; + +import { FeedAtom } from "@/recoilAtom/FeedAtom"; + +import { FeedSearchData } from "@/recoilAtom/FseahData"; + +import { FeedTop } from "@/recoilAtom/Category_top"; +import { FeedOuter } from "@/recoilAtom/Category_out"; +import { FeedPants } from "@/recoilAtom/Category_pants"; +import { FeedShoe } from "@/recoilAtom/Category_shoe"; +import { FeedBack } from "@/recoilAtom/Category_back"; +import { FeedHat } from "@/recoilAtom/Category_hat"; + +interface PROPS { + categorytitle: string | undefined; + setControl: Dispatch>; + tab_control: boolean; +} + +interface DATA { + id: number; + Ctitle: string; + arr: string[]; +} + +interface SEARCH{ + categoryT:string; + serch_data:string[]; +} + +export default function FeedcateDetail( { categorytitle, setControl } : PROPS) { + + //▷ 리코일 + // 만약 Value나 Set만 사용할 경우 + // value >> const state = useRecoilValue(`값`); + // set >> const setState = useSetRecoilState(`값`); + // seState에서 이전 값을 사용하던 방식 사용 가능 setState((prev)=>[...prev, data]); + const category_data = useRecoilValue(FeedAtom); + + const [search, setSearch] = useRecoilState(FeedSearchData); + + const [top_data, setTop] = useRecoilState(FeedTop); + const [outer_data, setOuter] = useRecoilState(FeedOuter); + const [pants_data, setPants] = useRecoilState(FeedPants); + const [shoes_data, setShoe] = useRecoilState(FeedShoe); + const [back_data, setBack] = useRecoilState(FeedBack); + const [hat_data, setHAt] = useRecoilState(FeedHat); + //아톰에서 미리 형식을 지정해주지 않으면 never로 떠서 값을 못넣는다. + + + + //▷ useState + const [delData, setDel] = useState([]); + let chooseCa:string[] = []; + + //▷ 데이터 확인하기 + console.log(category_data); + // console.log(Object.keys(category_data));//키만 가지고 오기 + console.log(categorytitle); + + if(categorytitle == "top") { + chooseCa = [...top_data]; + } else if(categorytitle == "outer"){ + chooseCa = [...outer_data]; + } else if(categorytitle == "pants"){ + chooseCa = [...pants_data]; + } else if(categorytitle == "shoes"){ + chooseCa = [...shoes_data]; + } else if(categorytitle == "back"){ + chooseCa = [...back_data]; + } else if(categorytitle == "hat"){ + chooseCa = [...hat_data]; + } + + + //▷ 가져온 Value 값을 Ctitle로 가진 배열 가지고오기 + const caArr:DATA | undefined = category_data.find(arr => arr.Ctitle == categorytitle); + const view_arr:string[] | undefined = caArr?.arr; + console.log(view_arr); + + //▷ 이벤트 리스너 + + //선택한 카테고리 useRecoilState 배열에 넣기 + const cate_btn = ( btnval: string ) => { + console.log(btnval); + console.log("현재 카테고리", categorytitle) + + //btnval값이 배열에 존재하지 않을때만 push + if(categorytitle == "top") { + if (!top_data.includes(btnval)) { + setTop([...top_data, btnval]); + } + } else if(categorytitle == "outer"){ + if (!outer_data.includes(btnval)) { + setOuter([...outer_data, btnval]); + } + } else if(categorytitle == "pants"){ + if (!pants_data.includes(btnval)) { + setPants([...pants_data, btnval]); + } + } else if(categorytitle == "shoes"){ + if (!shoes_data.includes(btnval)) { + setShoe([...shoes_data, btnval]); + } + } else if(categorytitle == "back"){ + if (!back_data.includes(btnval)) { + setBack([...back_data, btnval]); + } + } else if(categorytitle == "hat"){ + if (!hat_data.includes(btnval)) { + setHAt([...hat_data, btnval]); + } + } + + // if (!chooseCa.includes(btnval)) { + // setChoose([...chooseCa, btnval]); + // } + // console.log(chooseCa); + + } + + // 선택한 카테고리 선택 취소 + const del_choose = (delval:string) => { + if(categorytitle == "top") { + setTop((prevArray) => prevArray.filter((item) => item !== delval)); + //선택한 카테고리를 제외했을때 제외한 데이터를 배열에 넣어서 검색 아톰에서 삭제하기위한 delDat 배열 + setDel((prev) => [...new Set([...prev, delval])]); + } else if(categorytitle == "outer"){ + setOuter((prevArray) => prevArray.filter((item) => item !== delval)); + setDel((prev) => [...new Set([...prev, delval])]); + } else if(categorytitle == "pants"){ + setPants((prevArray) => prevArray.filter((item) => item !== delval)); + setDel((prev) => [...new Set([...prev, delval])]); + } else if(categorytitle == "shoes"){ + setShoe((prevArray) => prevArray.filter((item) => item !== delval)); + setDel((prev) => [...new Set([...prev, delval])]); + } else if(categorytitle == "back"){ + setBack((prevArray) => prevArray.filter((item) => item !== delval)); + setDel((prev) => [...new Set([...prev, delval])]); + } else if(categorytitle == "hat"){ + setHAt((prevArray) => prevArray.filter((item) => item !== delval)); + setDel((prev) => [...new Set([...prev, delval])]); + } + // setChoose((prevArray) => prevArray.filter((item) => item !== delval)); + } + + //카테고리 선택완료시 Atom으로 데이터 전송 + const startSearch = () => { + // setSearch((prev) => [...new Set([...prev, ...chooseCa])]); + + //카테고리에 맞는 검색 배열에 검색 데이터 넣기 + //중복을 제어하기 위해 Set 사용 + setSearch((prevSearch) => + + //중복된 값이 없도록 추가하는 방법 + prevSearch.map((item) => + item.categoryT === categorytitle + ? { ...item, serch_data: Array.from(new Set([...item.serch_data, ...chooseCa])) } + : item + )); + + setControl(false); + + //특정 배열과 아톰이 가지고 있는 모든 배열을 비교하고, + //동일한 값을 가진 경우 해당 값을 삭제하는 코드 + setSearch((prevSearch) => + prevSearch.map((item) => ({ + ...item, + serch_data: item.serch_data.filter((data) => !delData.includes(data)), + })) + ); + + //근데 이렇게 삭제할때마다 삭제 데이터 배열(delData)의 값들이 사라짐 + //원하는 방식이기는 하나 이유를 모르겠음... + + + } + + console.log("검색 데이터 배열",search); + console.log("삭제할 카테고리", delData); + + + return(<> +
+
{/* 카테고리 목록 */} + {view_arr && view_arr.map((myarr, index)=>{ + return(<> + +
+ ) + } + )} +
+
+ {/* + --선택한 카테고리 띄워주기-- + 기억해야하는것 + - 앞에 chooseCa && 없이 그냥 쓴다면 chooseCa 배열에 값이 + 존재하지 않을때에 대해 오류가 뜨기 때문에 앞에 단축평가로 + 오류 예방 + */} + {chooseCa && chooseCa.map((myarr, index)=>{ + return(<> +
+

{myarr}

+ +
+ ) + })} + +
+
+ ) +} \ No newline at end of file diff --git a/src/component/FeedCategory.tsx b/src/component/FeedCategory.tsx new file mode 100644 index 0000000..7cf12c1 --- /dev/null +++ b/src/component/FeedCategory.tsx @@ -0,0 +1,82 @@ +import { useEffect, useState } from "react"; +import "../style/feedCategory.scss"; +import FeedcateDetail from "./FeedCateDetail"; + +interface TabMenu { + id: number; + title?: string; + val: string; + } + +export default function FeedCategory(){ + + //useState + const [usetab, setTab] = useState([ + { + id: 1, + title : "상의", + val : "top" + }, + { + id: 2, + title : "아우터", + val : "outer" + }, + { + id: 3, + title : "하의", + val : "pants" + }, + { + id: 4, + title : "신발", + val : "shoes" + }, + { + id: 5, + title : "가방", + val : "back" + }, + { + id: 6, + title : "모자", + val : "hat" + } + ]); + + const [tab_control, setControl] = useState(false); + const [send_val, setVal] = useState(); + + //useEffect + // useEffect(()=>{ + // console.log("탭 메뉴",usetab); + // },[]); + + + //js + + const tab_title = (title?:string) => { + if(tab_control == false) { + setControl(true); + setVal(title); + } else { + setControl(false); + } + } + + console.log(tab_control); + + + return(<> +
+
    + { + usetab.map((val) => { + return
  • tab_title(val.val)}> || {val.title}
  • + }) + } +
+
+ {tab_control && } + ) +} \ No newline at end of file diff --git a/src/component/FeedContents.tsx b/src/component/FeedContents.tsx new file mode 100644 index 0000000..274cd7b --- /dev/null +++ b/src/component/FeedContents.tsx @@ -0,0 +1,20 @@ +import axios from "axios"; +import { useEffect } from "react"; + + +export default function FeedContenets(){ + useEffect(()=>{ + const feeddata = async() => { + const req = axios({ + method: "GET", + url: "https://www.jerneithe.site/", + }); + + console.log("받아온 피드 데이터", req); + } + }, []); + + return(<> + + ) +} \ No newline at end of file diff --git a/src/component/FeedSearch.tsx b/src/component/FeedSearch.tsx new file mode 100644 index 0000000..dc9a1e6 --- /dev/null +++ b/src/component/FeedSearch.tsx @@ -0,0 +1,41 @@ +import Image from "next/image"; +import "../style/feedSearch.scss"; + +import { FeedSearchData } from "@/recoilAtom/FseahData"; +import { useRecoilState } from "recoil"; +import { useState } from "react"; + +export default function FeedSearch(){ + + const [catesearch, setCateSearch] = useRecoilState(FeedSearchData); + console.log("검색할 카테고리 데이터", catesearch); + + const [hash_value, setHval] = useState(""); + + //검색 아이콘 누르면 선택한 카테고리 데이터가 있는 아톰에서 배열들을 가져오고 + //해시태그에 있는 스트링을 # 기준으로 나누어 배열에 넣은 뒤에 + //모두 합쳐서 백엔드로 보내면 됨 + const search_final = () => { + + } + + + return(<> +
+
+ + +
+ +
+ ) +} \ No newline at end of file diff --git a/src/component/WeatherBar.tsx b/src/component/WeatherBar.tsx index 83cce9e..7783809 100644 --- a/src/component/WeatherBar.tsx +++ b/src/component/WeatherBar.tsx @@ -3,25 +3,6 @@ import Image from "next/image"; import "../style/weatherBar.scss"; import React from "react"; -// interface WeatherData { -// main:{ -// temp:number; -// temp_max: -// }; -// weather:{ -// main: string; -// }[]; -// } - -// interface Address { -// documents: { -// address: { -// region_1depth_name: string; -// region_2depth_name: string; -// }; -// }[]; -// } - const WeatherBar:React.FC = () => { const API_KEY = "fa3eba61f243af3e8e69086462763172"; @@ -45,7 +26,9 @@ const WeatherBar:React.FC = () => { ); const latitude = position.coords.latitude; + console.log("위도", latitude); const longitude = position.coords.longitude; + console.log("경도", longitude); const weatherResponse = await fetch( `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric` @@ -57,6 +40,7 @@ const WeatherBar:React.FC = () => { setMax(weatherData.main.temp_max.toFixed(1)); setMin(weatherData.main.temp_min.toFixed(1)); setWeat(weatherData.weather[0].main); + console.log(weatherData) // console.log(`온도 : ${temp} ,최고온도 ${max},최저온도 ${min}, 날씨 : ${weat}`); const addressResponse = await fetch( diff --git a/src/recoilAtom/Category_back.ts b/src/recoilAtom/Category_back.ts new file mode 100644 index 0000000..0c06531 --- /dev/null +++ b/src/recoilAtom/Category_back.ts @@ -0,0 +1,7 @@ +import { atom } from "recoil" + + +export const FeedBack = atom({ + key:'back_key',//전역적으로 유일한 값 + default:[] +}); \ No newline at end of file diff --git a/src/recoilAtom/Category_hat.ts b/src/recoilAtom/Category_hat.ts new file mode 100644 index 0000000..72a0a6c --- /dev/null +++ b/src/recoilAtom/Category_hat.ts @@ -0,0 +1,7 @@ +import { atom } from "recoil" + + +export const FeedHat = atom({ + key:'hat_key',//전역적으로 유일한 값 + default:[] +}); \ No newline at end of file diff --git a/src/recoilAtom/Category_out.ts b/src/recoilAtom/Category_out.ts new file mode 100644 index 0000000..79016f6 --- /dev/null +++ b/src/recoilAtom/Category_out.ts @@ -0,0 +1,7 @@ +import { atom } from "recoil" + + +export const FeedOuter = atom({ + key:'outer_key',//전역적으로 유일한 값 + default:[] +}); \ No newline at end of file diff --git a/src/recoilAtom/Category_pants.ts b/src/recoilAtom/Category_pants.ts new file mode 100644 index 0000000..0699a7e --- /dev/null +++ b/src/recoilAtom/Category_pants.ts @@ -0,0 +1,7 @@ +import { atom } from "recoil" + + +export const FeedPants = atom({ + key:'pants_key',//전역적으로 유일한 값 + default:[] +}); \ No newline at end of file diff --git a/src/recoilAtom/Category_shoe.ts b/src/recoilAtom/Category_shoe.ts new file mode 100644 index 0000000..336548c --- /dev/null +++ b/src/recoilAtom/Category_shoe.ts @@ -0,0 +1,7 @@ +import { atom } from "recoil" + + +export const FeedShoe = atom({ + key:'shoes_key',//전역적으로 유일한 값 + default:[] +}); \ No newline at end of file diff --git a/src/recoilAtom/Category_top.ts b/src/recoilAtom/Category_top.ts new file mode 100644 index 0000000..c275dd3 --- /dev/null +++ b/src/recoilAtom/Category_top.ts @@ -0,0 +1,7 @@ +import { atom } from "recoil" + + +export const FeedTop = atom({ + key:'top_key',//전역적으로 유일한 값 + default:[] +}); \ No newline at end of file diff --git a/src/recoilAtom/FeedAtom.ts b/src/recoilAtom/FeedAtom.ts new file mode 100644 index 0000000..49f86b3 --- /dev/null +++ b/src/recoilAtom/FeedAtom.ts @@ -0,0 +1,104 @@ +import { atom } from "recoil" + + +export const FeedAtom = atom({ + key:'category_key',//전역적으로 유일한 값 + default:[ + { + id : 1, + Ctitle : "top", + arr : [ + "맨투맨", + "셔츠/블라우스", + "후드티", + "니트/스웨터", + "반팔티", + "카라티", + "긴팔티", + "민소매", + "스포츠", + "기타 상의" + ]}, + { + id : 2, + Ctitle : "outer", + arr : [ + "후드 집업", + "가디건", + "베스트", + "플리스", + "아노락", + "블루종", + "라이더 자켓", + "트러커 자켓", + "무스탕", + "블레이저", + "싱글 코트", + "더블 코트", + "더플 코트", + "롱패딩", + "숏패딩", + "기타" + ]}, + { + id : 3, + Ctitle : "pants", + arr : [ + "데님 팬츠", + "코튼 팬츠", + "슬랙스", + "트레이닝 팬츠", + "조거 팬츠", + "숏 팬츠", + "레깅스", + "점프 슈트", + "스포츠 하의", + "기타" + ]}, + { + id : 4, + Ctitle : "shoes", + arr : [ + "스니커즈", + "컨버스", + "워커", + "로퍼", + "보트화", + "슬립온", + "운동화", + "구두", + "부츠", + "플랫 슈즈", + "블로퍼", + "샌들", + "슬리퍼", + "기타" + ]}, + { + id : 5, + Ctitle : 'back', + arr : [ + "백팩", + "메신저백", + "크로스백", + "숄더백", + "토트백", + "에코백", + "더플백", + "클러치백", + "이스트백", + "기타" + ]}, + { + id : 6, + Ctitle : "hat", + arr : [ + "베레모", + "페도라", + "버킷/사파리햇", + "비니", + "트루퍼", + "기타" + ]}, + ] +}); \ No newline at end of file diff --git a/src/recoilAtom/FseahData.ts b/src/recoilAtom/FseahData.ts new file mode 100644 index 0000000..83cdf9b --- /dev/null +++ b/src/recoilAtom/FseahData.ts @@ -0,0 +1,40 @@ +import { atom } from "recoil" + +interface SEARCH{ + categoryT:string; + serch_data:string[]; +} + +export const FeedSearchData = atom({ + key:'seach_key_dj',//전역적으로 유일한 값 + default:[ + { + categoryT: "top", + serch_data: [] + }, + { + categoryT: "shoes", + serch_data: [] + }, + { + categoryT: "outer", + serch_data: [] + }, + { + categoryT: "pants", + serch_data: [] + }, + { + categoryT: "back", + serch_data: [] + }, + { + categoryT: "hat", + serch_data: [] + }, + { + categoryT: "hash_tag", + serch_data: [] + }, + ] +}); \ No newline at end of file diff --git a/src/style/feedCategory.scss b/src/style/feedCategory.scss new file mode 100644 index 0000000..31c4649 --- /dev/null +++ b/src/style/feedCategory.scss @@ -0,0 +1,3 @@ +#category_dj{ + display: flex; +} diff --git a/src/style/feedSearch.scss b/src/style/feedSearch.scss new file mode 100644 index 0000000..985a8e1 --- /dev/null +++ b/src/style/feedSearch.scss @@ -0,0 +1,35 @@ +.search_dj{ + width: 100%; + display: flex; +} + +.search_contain_dj{ + width: 75%; + height: 30px; + margin: 10px 5px 10px 10%; + background-color: #E5EBFF; + border-radius: 8px; +} + +#search_input_dj{ + width: 80%; + margin: 7px 0px 0px 3%; + + background-color: #E5EBFF; + + font-size: 11px; + +} + +#search_btn_dj{ + margin-left: 8%; + background-color: #E5EBFF; +} + +.search_img_dj{ + display: block; +} + +#cancle_btn_dj{ + font-size: 11px; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3869c9c..6106fb6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,9 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "downlevelIteration" : true, + // 배열에서 사용하는 Set은 원래 반복 사용이 안됨 위의 코드를 추가해줌으로써 + // 반복을 허용 "plugins": [ { "name": "next"