diff --git a/my-app/public/Img_home_01.png b/my-app/public/Img_home_01.png new file mode 100644 index 000000000..249652e34 Binary files /dev/null and b/my-app/public/Img_home_01.png differ diff --git a/my-app/public/Img_home_02.png b/my-app/public/Img_home_02.png new file mode 100644 index 000000000..84d8629f1 Binary files /dev/null and b/my-app/public/Img_home_02.png differ diff --git a/my-app/public/Img_home_03.png b/my-app/public/Img_home_03.png new file mode 100644 index 000000000..eb0d6cd26 Binary files /dev/null and b/my-app/public/Img_home_03.png differ diff --git a/my-app/public/Img_home_bottom.png b/my-app/public/Img_home_bottom.png new file mode 100644 index 000000000..58b29043e Binary files /dev/null and b/my-app/public/Img_home_bottom.png differ diff --git a/my-app/public/Img_home_top.png b/my-app/public/Img_home_top.png new file mode 100644 index 000000000..7ce56caaa Binary files /dev/null and b/my-app/public/Img_home_top.png differ diff --git a/my-app/public/icon/google.png b/my-app/public/icon/google.png new file mode 100644 index 000000000..0c4b3886c Binary files /dev/null and b/my-app/public/icon/google.png differ diff --git a/my-app/public/icon/ic_facebook.png b/my-app/public/icon/ic_facebook.png new file mode 100644 index 000000000..a3e343c77 Binary files /dev/null and b/my-app/public/icon/ic_facebook.png differ diff --git a/my-app/public/icon/ic_instagram.png b/my-app/public/icon/ic_instagram.png new file mode 100644 index 000000000..c47e044d1 Binary files /dev/null and b/my-app/public/icon/ic_instagram.png differ diff --git a/my-app/public/icon/ic_twitter.png b/my-app/public/icon/ic_twitter.png new file mode 100644 index 000000000..3f74b730a Binary files /dev/null and b/my-app/public/icon/ic_twitter.png differ diff --git a/my-app/public/icon/ic_youtube.png b/my-app/public/icon/ic_youtube.png new file mode 100644 index 000000000..874150b0b Binary files /dev/null and b/my-app/public/icon/ic_youtube.png differ diff --git a/my-app/public/icon/kakao.png b/my-app/public/icon/kakao.png new file mode 100644 index 000000000..f1a52f459 Binary files /dev/null and b/my-app/public/icon/kakao.png differ diff --git a/my-app/public/icon/visibility.png b/my-app/public/icon/visibility.png new file mode 100644 index 000000000..69a949ba9 Binary files /dev/null and b/my-app/public/icon/visibility.png differ diff --git a/my-app/public/icon/visible.png b/my-app/public/icon/visible.png new file mode 100644 index 000000000..e8a0fba8a Binary files /dev/null and b/my-app/public/icon/visible.png differ diff --git a/my-app/public/og_img.png b/my-app/public/og_img.png new file mode 100644 index 000000000..158e4f5f5 Binary files /dev/null and b/my-app/public/og_img.png differ diff --git a/my-app/src/app/api/api.ts b/my-app/src/app/api/api.ts new file mode 100644 index 000000000..a2a8492e6 --- /dev/null +++ b/my-app/src/app/api/api.ts @@ -0,0 +1,34 @@ +const url = "https://panda-market-api.vercel.app/auth"; +export const signup = async (data: { + email: string; + nickname: string; + password: string; + passwordConfirmation?: string; +}) => { + const response = await fetch(`${url}/signUp`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error("회원가입 실패"); + } + + return response.json(); +}; + +export const login = async (data: { email: string; password: string }) => { + const response = await fetch(`${url}/signIn`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error("로그인 실패"); + } + + const responseData = await response.json(); + return responseData; +}; diff --git a/my-app/src/app/components/CommonForm.tsx b/my-app/src/app/components/CommonForm.tsx new file mode 100644 index 000000000..491b60e8d --- /dev/null +++ b/my-app/src/app/components/CommonForm.tsx @@ -0,0 +1,319 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { AuthFormProps } from "../type/type"; +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { signup, login } from "../api/api"; + +export default function CommonForm({ type }: AuthFormProps) { + const isLogin = type === "login"; + const [email, setEmail] = useState(""); + const [emailError, setEmailError] = useState(""); + const [password, setPassword] = useState(""); + const [passwordError, setPasswordError] = useState(""); + const [repassword, setRePassword] = useState(""); + const [repasswordError, setRePasswordError] = useState(""); + const [nickname, setNickname] = useState(""); + const [nicknameError, setNicknameError] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [showRePassword, setShowRePassword] = useState(false); + const router = useRouter(); + + useEffect(() => {}, [email, nickname, password, repassword]); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + try { + if (type === "signup") { + const signupData = { + email, + nickname, + password, + passwordConfirmation: repassword, + }; + await signup(signupData); // 회원가입 API 호출 + router.push("/login"); + alert("회원가입 성공!"); + } else if (type === "login") { + const loginData = { + email, + password, + }; + const data = await login(loginData); // 로그인 API 호출 + if (data && data.accessToken) { + localStorage.setItem("token", data.accessToken); // 토큰 저장 + router.push("/"); + alert("로그인 성공!"); + } else { + alert("로그인 실패: 토큰이 없습니다."); + } + } + } catch (error: any) { + const errorMessage = + error.response?.data?.message || + error.message || + "알 수 없는 오류가 발생했습니다."; + alert(errorMessage); + } + }; + const validateEmail = (email: string) => { + if (!email) { + setEmailError("이메일을 입력해주세요"); + } else if (!/\S+@\S+\.\S+/.test(email)) { + setEmailError("올바른 이메일 형식을 입력해주세요"); + } else { + setEmailError(""); + } + }; + + const validatePassword = (password: string) => { + if (!password) { + setPasswordError("비밀번호를 입력해주세요"); + } else if (password.length < 8) { + setPasswordError("비밀번호는 8자리 이상이어야 합니다"); + } else { + setPasswordError(""); + } + }; + const validateRePassword = (repassword: string, password: string) => { + if (!repassword) { + setRePasswordError("비밀번호를 다시 한 번 입력해주세요"); + } else if (repassword !== password) { + setRePasswordError("비밀번호가 일치하지 않습니다."); + } else { + setRePasswordError(""); + } + }; + + // 이메일 입력 변경 시 에러 메시지 제거 + const handleEmailChange = (e: React.ChangeEvent) => { + setEmail(e.target.value); + setEmailError(""); // 실시간으로 에러 메시지 제거 + validateEmail(e.target.value); // 실시간 이메일 유효성 검사 + }; + + // 비밀번호 입력 변경 시 에러 메시지 제거 + const handlePasswordChange = (e: React.ChangeEvent) => { + setPassword(e.target.value); + setPasswordError(""); // 실시간으로 에러 메시지 제거 + validatePassword(e.target.value); // 실시간 비밀번호 유효성 검사 + }; + + // 비밀번호 입력 변경 시 에러 메시지 제거 + const handleRePasswordChange = (e: React.ChangeEvent) => { + setRePassword(e.target.value); + setRePasswordError(""); // 실시간으로 에러 메시지 제거 + validateRePassword(e.target.value, password); // 실시간 비밀번호 확인 유효성 검사 + }; + + // 닉네임 입력 변경 시 + const handleNicknameChange = (e: React.ChangeEvent) => { + setNickname(e.target.value); + setNicknameError(""); + }; + + const handlePasswordVisibilityToggle = () => { + setShowPassword((prevState) => !prevState); // 비밀번호 보이기/숨기기 상태 토글 + }; + + const handleRePasswordVisibilityToggle = () => { + setShowRePassword((prevState) => !prevState); // 비밀번호 확인 보이기/숨기기 상태 토글 + }; + return ( + <> +
+
+
+ +
+ 로고 +
+
+ 로고 +
+ +
+
+
+
+ + +
{emailError}
+
+ {!isLogin && ( +
+ + +
+ )} +
+ +
+ +
+
+ 클릭시비밀번호보기 +
+
+
+
{passwordError}
+
+ {!isLogin && ( +
+ +
+ +
+ 클릭시비밀번호보기 +
+
+
{repasswordError}
+
+ )} + + +
+

간편 로그인하기

+
+
+ + 구글로고 + +
+
+ + 카카오톡로고 + +
+
+
+
+ + {isLogin ? "판다마켓이 처음이신가요?" : "이미 회원이신가요?"} + + + {isLogin ? "회원가입" : "로그인"} + +
+
+
+
+
+ + ); +} diff --git a/my-app/src/app/components/Header.tsx b/my-app/src/app/components/Header.tsx index 38716610d..1f1b1979b 100644 --- a/my-app/src/app/components/Header.tsx +++ b/my-app/src/app/components/Header.tsx @@ -1,7 +1,42 @@ +"use client"; import Image from "next/image"; import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; export default function HeaderContainer() { + const [token, setToken] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const router = useRouter(); + + useEffect(() => { + const storedToken = localStorage.getItem("token"); + setToken(storedToken); + }, []); + + const handleLogout = () => { + localStorage.removeItem("token"); + setToken(null); + router.push("/login"); + alert("로그아웃이 되었습니다."); + }; + + const handleToggle = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsOpen(!isOpen); + }; + + useEffect(() => { + if (isOpen) { + const handleClickOutside = () => setIsOpen(false); + document.addEventListener("click", handleClickOutside); + + return () => { + document.removeEventListener("click", handleClickOutside); + }; + } + }, [isOpen]); + return (
@@ -11,10 +46,22 @@ export default function HeaderContainer() { href={"/"} >
- logo_face + logo_face
- logo_text + logo_text
@@ -26,11 +73,39 @@ export default function HeaderContainer() { 중고마켓 -
- - myPageIcon - -
+ {token ? ( +
+ myPageIcon + +
+ ) : ( + + )}
); diff --git a/my-app/src/app/components/MainCard.tsx b/my-app/src/app/components/MainCard.tsx new file mode 100644 index 000000000..6865119e3 --- /dev/null +++ b/my-app/src/app/components/MainCard.tsx @@ -0,0 +1,38 @@ +import Image from "next/image"; +import { Mcard } from "../type/type"; + +export default function MainCard({ + imgSrc, + alt, + title, + description1, + description2, + isSpecial, +}: Mcard) { + return ( + <> +
+
+ {alt} +
+
+

{title}

+

+ {description1} +

+

{description2}

+
+
+ + ); +} diff --git a/my-app/src/app/globals.css b/my-app/src/app/globals.css index 4b749c478..62b7c0481 100644 --- a/my-app/src/app/globals.css +++ b/my-app/src/app/globals.css @@ -12,3 +12,18 @@ body { background: var(--background); @apply text-base; } + +.t-pc-br { + display: none; +} +.pc-br { + display: none; +} +@media (min-width: 768px) and (max-width: 1199px) { + .pc-br { + display: block; + } + .t-pc-br { + display: block; + } +} diff --git a/my-app/src/app/item/page.tsx b/my-app/src/app/item/page.tsx new file mode 100644 index 000000000..387976e4a --- /dev/null +++ b/my-app/src/app/item/page.tsx @@ -0,0 +1,3 @@ +export default function Item() { + return
아이템
; +} diff --git a/my-app/src/app/layout.tsx b/my-app/src/app/layout.tsx index 15a643786..61c760255 100644 --- a/my-app/src/app/layout.tsx +++ b/my-app/src/app/layout.tsx @@ -1,3 +1,6 @@ +"use client"; + +import { usePathname } from "next/navigation"; import localFont from "next/font/local"; import "./globals.css"; import Header from "./components/Header"; @@ -13,10 +16,13 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { + const pathname = usePathname(); + const hiddenPaths = ["/login", "/signup"]; + const showHeader = !hiddenPaths.includes(pathname); return ( -
+ {showHeader &&
}
{children}
diff --git a/my-app/src/app/login/page.tsx b/my-app/src/app/login/page.tsx new file mode 100644 index 000000000..c656d48d4 --- /dev/null +++ b/my-app/src/app/login/page.tsx @@ -0,0 +1,8 @@ +import CommonForm from "../components/CommonForm"; +export default function login() { + return ( + <> + + + ); +} diff --git a/my-app/src/app/page.tsx b/my-app/src/app/page.tsx index 039ddbf24..f131382ce 100644 --- a/my-app/src/app/page.tsx +++ b/my-app/src/app/page.tsx @@ -1,7 +1,139 @@ +import Image from "next/image"; +import Link from "next/link"; +import MainCard from "./components/MainCard"; +import { Mcard } from "./type/type"; + export default function Home() { + const items: Mcard[] = [ + { + imgSrc: "/Img_home_01.png", + alt: "이미지 1", + title: "Hot item", + description1: "인기 상품을 확인해 보세요", + description2: "가장 HOT한 중고거래 물품을 판다 마켓에서 확인해 보세요", + isSpecial: false, // 특정 스타일 적용 여부 + }, + { + imgSrc: "/Img_home_02.png", + alt: "이미지 2", + title: "Search", + description1: "새로운 거래를 찾아보세요", + description2: "다양한 물품과 거래를 경험해 보세요", + isSpecial: true, // 다른 CSS 적용 + }, + { + imgSrc: "/Img_home_03.png", + alt: "이미지 3", + title: "register", + description1: "최저가 물품을 찾으세요", + description2: "합리적인 가격으로 물건을 구매하세요", + isSpecial: false, + }, + ]; return ( <> -
Home
+
+
+
+
+

+ 일상의 모든 물건을 +
거래해 보세요 +

+ +
+
+ 탑이미지 +
+
+
+
+
+
+ {items.map((item, index) => ( + + ))} +
+
+
+
+

+ 믿을 수 있는 +
판다마켓 중고 거래 +

+
+ 바텀이미지 +
+
+
+
+
+
+

©codeit - 2024

+
+

Privacy Policy

+

FAQ

+
+
+
+ + 페이스북로고 + + + 페이스북로고 + + + 페이스북로고 + + + 페이스북로고 + +
+
+
); } diff --git a/my-app/src/app/signup/page.tsx b/my-app/src/app/signup/page.tsx new file mode 100644 index 000000000..e5170f0a7 --- /dev/null +++ b/my-app/src/app/signup/page.tsx @@ -0,0 +1,5 @@ +import CommonForm from "../components/CommonForm"; + +export default function Signup() { + return ; +} diff --git a/my-app/src/app/type/type.tsx b/my-app/src/app/type/type.tsx index 9174b7b04..e16bc69e5 100644 --- a/my-app/src/app/type/type.tsx +++ b/my-app/src/app/type/type.tsx @@ -1,3 +1,5 @@ +import { ReactNode } from "react"; + export interface BestItemData { id: number; title: string; @@ -29,3 +31,17 @@ export interface Comment { nickname: string; }; } + +export interface Mcard { + imgSrc: string; + alt: string; + title: string; + description1: string; + description2: string; + isSpecial: boolean; +} + +export type AuthFormProps = { + type: "login" | "signup"; + nickName?: ReactNode; +}; diff --git a/my-app/tailwind.config.ts b/my-app/tailwind.config.ts index 22ad7b421..e0122001d 100644 --- a/my-app/tailwind.config.ts +++ b/my-app/tailwind.config.ts @@ -12,12 +12,17 @@ export default { background: "var(--background)", foreground: "var(--foreground)", gray50: "#f9fafb", + gray70: "#fcfcfc", gray100: "#f3f4f6", gray200: "#e5e7eb", gray400: "#9ca3af", + gray500: "#374151", gray600: "#4b5563", bordergray: "#DFDFDF", skyblue: "#3692ff", + mainbg: "#cfe5ff", + ftbg: "#111827", + red: "#f74747", }, }, },