diff --git a/components/_styled/mainStyled.tsx b/components/_styled/mainStyled.tsx index 1167ff2f..b31632b8 100644 --- a/components/_styled/mainStyled.tsx +++ b/components/_styled/mainStyled.tsx @@ -1,11 +1,183 @@ import styled from "styled-components"; +import Image from 'next/image'; export const MainWrapper = styled.div` width: 100%; height: 100%; display: flex; + flex-direction: column; + justify-content: center; align-items: center; `; + export const MainContainer = styled.div` + max-width: 1200px; + margin-top: 138px; +`; + +export const SectionContainer = styled.div` + display: flex; + flex-direction: column; + gap: 138px; + @media (max-width: 1199px){ + max-width:696px; + } +`; + +export const Banner = styled.section` + display: flex; + width: 100%; + height: 540px; + background: #cfe5ff; + align-items: flex-end; + justify-content: center; + flex-shrink: 0; + + @media (max-width: 1199px) { + height: 771px; + } +`; + +export const BannerWrap = styled.div` + display: inline-flex; + align-items: center; + gap: 7px; + + @media (max-width: 1199px) { + flex-direction: column; + gap: 100px; + width: 744px; + height: 100%; + } +`; + +export const BannerImg = styled(Image)` + width: 746px; + height: 340px; + + @media (max-width: 1199px) { + width: 100%; + height: 340px; + } +`; + +export const BannerTextBox = styled.div` + height: 100%; + display: flex; + padding-bottom: 60px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 32px; + + @media (max-width: 1199px) { + align-items: center; + text-align: center; + padding-bottom: 40px; + } +`; + +export const BannerText = styled.h1` + color: #374151; + margin: 0; + font-size: 40px; + font-weight: 700; + line-height: 140%; + + @media (max-width: 767px) { + font-size: 32px; + } +`; + +export const ItemsButton = styled.a` + display: flex; + height: 56px; + padding: 16px 124px; + justify-content: center; + align-items: center; + border-radius: 40px; + background: #3692ff; + color: #f9fafb; + font-size: 20px; + font-weight: 600; + text-decoration: none; + + @media (max-width: 767px) { + padding: 12px 71px; + font-size: 18px; + } +`; + +export const Section = styled.section` width: 100%; + display: flex; + align-items: center; + justify-content: center; +`; + +export const SectionWrap = styled.div` + max-width: 1200px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + gap: 64px; + + @media (max-width: 1199px) { + max-width: 696px; + flex-direction: column; + gap: 24px; + } `; + +export const SectionTextBox = styled.div` + display: flex; + flex-direction: column; + gap: 32px; + justify-content: center; + align-items: flex-start; + + @media (max-width: 1199px) { + align-items: center; + margin: 0; + text-align: center; + } +`; + +export const SectionText = styled.h3` + color: #374151; + font-size: 40px; + font-weight: 700; + line-height: 140%; + + @media (max-width: 767px) { + font-size: 24px; + } +`; + +export const SectionTextDetail = styled.p` + color: #374151; + font-size: 24px; + font-weight: 500; + line-height: 32px; + + @media (max-width: 1199px) { + font-size: 19px; + line-height: 26px; + } + + @media (max-width: 767px) { + font-size: 16px; + } +`; + +export const Tag = styled.p` + color: #3692ff; + font-size: 18px; + font-weight: 700; + line-height: 26px; + + @media (max-width: 767px) { + font-size: 16px; + } +`; \ No newline at end of file diff --git a/components/_styled/signStyled.tsx b/components/_styled/signStyled.tsx new file mode 100644 index 00000000..38d80dc3 --- /dev/null +++ b/components/_styled/signStyled.tsx @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +export const AuthContainer = styled.div` + width: 100%; + display: flex; + justify-content: center; +`; + + +export const HomeButton = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const PandaLogo = styled.img` + width: 396px; +`; \ No newline at end of file diff --git a/components/api/api.tsx b/components/api/api.tsx index 9c4eca88..3dfba7f3 100644 --- a/components/api/api.tsx +++ b/components/api/api.tsx @@ -2,6 +2,8 @@ import axios from 'axios'; import { ItemList } from '@/components/boards/PostList'; import { Article } from '@/components/board/Article'; import { CommentList } from '@/components/board/Comment'; +import { SignUpFormInputs } from '../sign/SignUpForm'; +import { SignInFormInputs } from '../sign/SignInForm'; const BASE_URL = 'https://panda-market-api.vercel.app'; export async function getArticles(params = {}) { @@ -48,3 +50,31 @@ export async function getArticleComment(articleId: number, params = { limit: 10, throw error; } } +export async function postSignUp(signUpData : SignUpFormInputs) { + try { + const response = await axios.post(`${BASE_URL}/auth/signUp`, { + email:signUpData.email, + nickname:signUpData.nickname, + password:signUpData.password, + passwordConfirmation:signUpData.passwordConfirm, + }); + console.log(signUpData); + return response.data; + } catch (error) { + console.error("Failed to sign up:", error); + throw error; + } +} +export async function postSignIn(signInData : SignInFormInputs) { + try { + const response = await axios.post(`${BASE_URL}/auth/signIn`, { + email:signInData.email, + password:signInData.password, + }); + console.log(signInData); + return response.data; + } catch (error) { + console.error("Failed to sign in:", error); + throw error; + } +} \ No newline at end of file diff --git a/components/boards/BestPostCard.tsx b/components/boards/BestPostCard.tsx index 3e98cfe8..b843ad7f 100644 --- a/components/boards/BestPostCard.tsx +++ b/components/boards/BestPostCard.tsx @@ -18,7 +18,6 @@ const BestPostCard:React.FC = ({ item }) => { }; return ( - Best diff --git a/components/common/images/Img_home_01.png b/components/common/images/Img_home_01.png new file mode 100644 index 00000000..408d6270 Binary files /dev/null and b/components/common/images/Img_home_01.png differ diff --git a/components/common/images/Img_home_02.png b/components/common/images/Img_home_02.png new file mode 100644 index 00000000..fd68492b Binary files /dev/null and b/components/common/images/Img_home_02.png differ diff --git a/components/common/images/Img_home_03.png b/components/common/images/Img_home_03.png new file mode 100644 index 00000000..abe080ea Binary files /dev/null and b/components/common/images/Img_home_03.png differ diff --git a/components/common/images/Img_home_bottom.png b/components/common/images/Img_home_bottom.png new file mode 100644 index 00000000..7ae14006 Binary files /dev/null and b/components/common/images/Img_home_bottom.png differ diff --git a/components/common/images/Img_home_top.png b/components/common/images/Img_home_top.png new file mode 100644 index 00000000..282abe23 Binary files /dev/null and b/components/common/images/Img_home_top.png differ diff --git a/components/common/images/ic_facebook.png b/components/common/images/ic_facebook.png new file mode 100644 index 00000000..8764b313 Binary files /dev/null and b/components/common/images/ic_facebook.png differ diff --git a/components/common/images/ic_google.png b/components/common/images/ic_google.png new file mode 100644 index 00000000..914c0dbb Binary files /dev/null and b/components/common/images/ic_google.png differ diff --git a/components/common/images/ic_instagram.png b/components/common/images/ic_instagram.png new file mode 100644 index 00000000..98e24ea6 Binary files /dev/null and b/components/common/images/ic_instagram.png differ diff --git a/components/common/images/ic_kakao.png b/components/common/images/ic_kakao.png new file mode 100644 index 00000000..61120754 Binary files /dev/null and b/components/common/images/ic_kakao.png differ diff --git a/components/common/images/ic_twitter.png b/components/common/images/ic_twitter.png new file mode 100644 index 00000000..5df0852d Binary files /dev/null and b/components/common/images/ic_twitter.png differ diff --git a/components/common/images/ic_youtube.png b/components/common/images/ic_youtube.png new file mode 100644 index 00000000..7c8f37b9 Binary files /dev/null and b/components/common/images/ic_youtube.png differ diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx new file mode 100644 index 00000000..9608a160 --- /dev/null +++ b/components/layout/Footer.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import * as S from './Styled'; +import Link from 'next/link'; +import FacebookIcon from '@/components/common/images/ic_facebook.png'; +import TwitterIcon from '@/components/common/images/ic_twitter.png'; +import InstagramIcon from '@/components/common/images/ic_instagram.png'; +import YoutubeIcon from '@/components/common/images/ic_youtube.png'; +export default function Footer() { + return ( + + + ©codeit - 2024 + + Privacy Policy + FAQ + + + + + + + + + + + + + + + + + + ); +} + diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index a4033102..e9aa2195 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React , { useEffect, useState }from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; import PandaLogo from '../common/images/pandalogo.png'; @@ -7,8 +7,18 @@ import * as HS from './Styled'; export default function Header() { + const [isLogin, setIsLogin] = useState(false); const router = useRouter(); + const handleOnClick = () => { + router.push('/sign/in') + } + useEffect(()=>{ + const accessToken = localStorage.getItem('accessToken') + setIsLogin(!accessToken); + }) + + return ( @@ -29,7 +39,8 @@ export default function Header() { - + {isLogin ? (로그인): + ()} ); diff --git a/components/layout/Styled.tsx b/components/layout/Styled.tsx index 7cdf57d3..93909123 100644 --- a/components/layout/Styled.tsx +++ b/components/layout/Styled.tsx @@ -22,7 +22,7 @@ export const Header = styled.header` z-index: 10; height: 70px; justify-content: center; - align-items: center; + align-items: centr; flex-shrink: 0; border-bottom: 1px solid #DFDFDF; background: #FFF; @@ -90,4 +90,70 @@ export const PandaLogo = styled(Image)` export const ProfileImg = styled(Image)` width: 40px; border-radius: 50%; +`; + +//Footer +export const Footer = styled.footer` + display: flex; + justify-content: center; + width: 100%; + height: 160px; + gap: 10px; + flex-shrink: 0; + background: #111827; + position: relative; + top: 138px; +`; + +export const FooterWrap = styled.div` + padding: 34px 400px; + width: 100%; + height: 100%; + position: absolute; + display: flex; + justify-content: space-between; +`; + +export const FooterLink = styled.a` + color: #9ca3af; + font-size: 16px; + font-weight: 400; + text-decoration: none; + cursor: pointer; +`; + +export const FooterCenter = styled.div` + display: flex; + gap: 30px; + justify-content: center; +`; + +export const SnsWrap = styled.div` + display: flex; + width: 116px; + gap: 12px; + justify-content: space-between; + cursor: pointer; +`; + +export const SnsIcon = styled(Image)` + width: 20px; + height: 20px; +`; + +export const LoginButton = styled.button` + display: flex; + width: 128px; + height: 48px; + padding: 12px 23px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 8px; + background: #3692FF; + border: none; + color: #F3F4F6; + font-size: 16px; + font-weight: 600; + line-height: 26px; `; \ No newline at end of file diff --git a/components/sign/SignInForm.tsx b/components/sign/SignInForm.tsx new file mode 100644 index 00000000..66679c38 --- /dev/null +++ b/components/sign/SignInForm.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import * as S from './Styled'; +import Image from 'next/image'; +import PandaLogo from '@/components/common/images/pandalogo.png'; +import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; +import { useRouter } from 'next/router'; +import { postSignIn } from '../api/api'; +import Link from 'next/link'; + +export interface SignInFormInputs { + email: string; + password: string; +} + +export default function SignInForm() { + const { register, handleSubmit, formState: { errors } } = useForm(); + const [passwordVisible, setPasswordVisible] = useState(false); + const router = useRouter(); + + const onSubmit: SubmitHandler = async (data) => { + try { + const response = await postSignIn(data); + localStorage.setItem('accessToken', response.accessToken); + router.push('/'); + } catch (error) { + console.error('로그인 실패:', error); + } + }; + + return ( + + + + 홈버튼 + + + + 이메일 + + {errors.email && {errors.email.message}} + + + + 비밀번호 + + setPasswordVisible(!passwordVisible)} + /> + {errors.password && {errors.password.message}} + + + + 로그인 + + + + + 계정이 없으신가요? + + 회원가입 + + + + ); +} \ No newline at end of file diff --git a/components/sign/SignUpForm.tsx b/components/sign/SignUpForm.tsx new file mode 100644 index 00000000..18e60829 --- /dev/null +++ b/components/sign/SignUpForm.tsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import * as S from './Styled'; +import Image from 'next/image'; +import PandaLogo from '@/components/common/images/pandalogo.png'; +import KakaoIcon from '@/components/common/images/ic_kakao.png'; +import GoogleIcon from '@/components/common/images/ic_google.png'; +import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; +import Link from 'next/link'; +import { postSignUp } from '../api/api'; +import { useRouter } from 'next/router'; + +export interface SignUpFormInputs { + email: string; + nickname: string; + password: string; + passwordConfirm: string; +} + +export default function SignUpForm() { + const { register, handleSubmit, watch, trigger, formState: { errors } } = useForm(); + const [passwordVisible, setPasswordVisible] = useState(false); + const [passwordConfirmVisible, setPasswordConfirmVisible] = useState(false); + const password = watch("password"); + const router = useRouter(); + + const onSubmit: SubmitHandler = async (data) => { + try { + const response = await postSignUp(data); + if (response && response.accessToken) { + localStorage.setItem('accessToken', response.accessToken); + } + router.push('/sign/in'); + } catch (error) { + console.log('회원가입 실패: ', error); + } + }; + + return ( + + + 홈버튼 + + + + 이메일 + trigger("email")} + /> + {errors.email && {errors.email.message}} + + + + 닉네임 + trigger("nickname")} + /> + {errors.nickname && {errors.nickname.message}} + + + + 비밀번호 + trigger("password")} + /> + setPasswordVisible(!passwordVisible)} + /> + {errors.password && {errors.password.message}} + + + + 비밀번호 확인 + value === password || "비밀번호가 일치하지 않습니다", + })} + placeholder="비밀번호를 입력해주세요" + onBlur={() => trigger("passwordConfirm")} + /> + setPasswordConfirmVisible(!passwordConfirmVisible)} + /> + {errors.passwordConfirm && {errors.passwordConfirm.message}} + + + + 회원가입 + + + + + 간편 로그인하기 + + + 구글 로그인 + + + 카카오톡 로그인 + + + + + + + 이미 회원이신가요? + 로그인 + + + ); +} \ No newline at end of file diff --git a/components/sign/Styled.tsx b/components/sign/Styled.tsx new file mode 100644 index 00000000..ebaf19b4 --- /dev/null +++ b/components/sign/Styled.tsx @@ -0,0 +1,168 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React from 'react'; +import styled from 'styled-components'; + + +export const AuthWrap = styled.div` + width: 100%; + max-width: 640px; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + gap: 40px; + margin-top: 60px; +`; + + +export const HomeButton = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const PandaLogo = styled.img` + width: 396px; +`; + +export const Form = styled.form` + width:100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +`; + +export const InputBox = styled.div` + width: 100%; + display: flex; + position:relative; + flex-direction: column; + align-items: flex-start; + gap: 16px; +`; + +export const Label = styled.label` + color: #1f2937; + font-size: 18px; + font-weight: 700; + line-height: 26px; +`; + +export const Input = styled.input` + display: flex; + width: 100%; + height: 56px; + padding: 16px 24px; + border: none; + border-radius: 12px; + background: #f3f4f6; + font-size: 16px; + line-height: 24px; + color: #1f2937; + + &::placeholder { + color: #9ca3af; + } + + &:focus { + outline-color: #3692ff; + } +`; + +export const VisibilityButton = styled(FontAwesomeIcon)` + width: 24px; + height: 24px; + color: #4B5563; + position: absolute; + right: 24px; + top: 58px; + cursor: pointer; +`; + +export const SignupButton = styled.button` + width: 100%; + height: 56px; + padding: 16px 124px; + border: none; + border-radius: 40px; + background: #9ca3af; + color: #f3f4f6; + font-size: 20px; + font-weight: 600; + cursor: pointer; + + &:hover { + background-color: #1967d6; + } + + &:focus { + background-color: #1251aa; + } + + &:disabled { + background-color: #9ca3af; + cursor: default; + pointer-events: none; + } +`; + +export const SimpleLoginBox = styled.div` + display: flex; + width: 100%; + height: 74px; + padding: 16px 23px; + justify-content: space-between; + align-items: center; + gap: 10px; + border-radius: 8px; + background: #e6f2ff; +`; + +export const SimpleLoginText = styled.p` + color: #1f2937; + font-size: 16px; + font-weight: 500; + line-height: 26px; +`; + +export const SimpleLoginSnsBox = styled.div` + display: flex; + gap: 16px; +`; + +export const SimpleLoginSnsLogo = styled.img` + width: 42px; + cursor: pointer; +`; + +export const AuthSwitchBox = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + gap: 4px; +`; + +export const AuthSwitchText = styled.p` + color: #1f2937; + font-size: 14px; + font-weight: 500; +`; + +export const SwitchToLogin = styled.a` + color: #3692ff; + font-size: 14px; + font-weight: 500; + text-decoration: underline; + cursor: pointer; +`; + +export const ErrorMessage = styled.span` + color: #f74747; + font-weight: 600; + font-size: 15px; + line-height: 18px; + margin-top: 8px; + padding-left: 16px; +`; diff --git a/next.config.js b/next.config.js index 7a79441e..a754e3fa 100644 --- a/next.config.js +++ b/next.config.js @@ -1,8 +1,12 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + compiler: { + styledComponents: true, + }, reactStrictMode: true, images: { remotePatterns: [ + { protocol: 'https', hostname: 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com', @@ -27,9 +31,7 @@ const nextConfig = { port: '', pathname: '/...', }, - ], }, }; - module.exports = nextConfig; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 54f01d52..3450fcc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "next": "13.5.6", "react": "^18", "react-dom": "^18", + "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "styled-components": "^6.1.13" }, @@ -3797,43 +3798,27 @@ "react": "^18.2.0" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "node_modules/react-hook-form": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.1.tgz", + "integrity": "sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==", "license": "MIT", - "dependencies": { - "@remix-run/router": "1.20.0" - }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" - }, - "engines": { - "node": ">=14.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-router": { "version": "6.27.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", diff --git a/package.json b/package.json index 1bf7185d..b18ff0a2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "next": "13.5.6", "react": "^18", "react-dom": "^18", + "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "styled-components": "^6.1.13" }, diff --git a/pages/index.tsx b/pages/index.tsx index 1cb55c19..c4abbc89 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,9 +1,62 @@ import * as MS from "@/components/_styled/mainStyled"; - +import Footer from "@/components/layout/Footer"; +import Header from "@/components/layout/Header"; +import Image from 'next/image'; +import BannerImg from '@/components/common/images/Img_home_top.png' +import FirstImg from '@/components/common/images/Img_home_01.png'; +import SecondImg from '@/components/common/images/Img_home_02.png'; +import ThirdImg from '@/components/common/images/Img_home_03.png'; export default function Home() { return ( - Main +
+ + + + 일상의 모든 물건을
거래해 보세요
+ 구경하러 가기 +
+ +
+
+ + + + + Register + + Hot Item + 인기 상품을
확인해 보세요
+ 가장 HOT한 중고거래 물품을
판다 마켓에서 확인해 보세요
+
+
+
+ + + + + Search + 구매를 원하는
상품을 검색하세요
+ 구매하고 싶은 물품은 검색해서
쉽게 찾아보세요
+
+ Search +
+
+ + + + Register + + Register + 판매를 원하는
상품을 등록하세요
+ 어떤 물건이든 판매하고 싶은 상품을
쉽게 등록하세요
+
+
+
+
+ +
+