Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4cc8f9c
style: Footer 반응형 수정
asksa1256 Jul 7, 2025
496a761
refactor: 메인 페이지 배너 컴포넌트화
asksa1256 Jul 7, 2025
a35a5da
refactor: 메인 페이지 섹션 컴포넌트화
asksa1256 Jul 7, 2025
0e9d350
refactor: 배너 컴포넌트 linkBtn -> linkTo로 속성 및 타입변경
asksa1256 Jul 7, 2025
5c50d71
refactor: 로그인, 회원가입 - 간편 로그인 컴포넌트화
asksa1256 Jul 7, 2025
b75e800
refactor: text, password input field 컴포넌트 생성 및 로그인 페이지에 적용
asksa1256 Jul 8, 2025
684affa
refactor: 폼 필드 타입 생성
asksa1256 Jul 8, 2025
b2961cf
refactor: 회원가입 페이지 InputField, passwordField 적용
asksa1256 Jul 8, 2025
724eb36
fix: 로그인, 회원가입 에러메시지 안 뜨는 현상 수정 (formRef를 필드가 각자 호출해서 발생한 문제)
asksa1256 Jul 8, 2025
6331a41
refactor: 로그인, 회원가입 공통 로직 커스텀 훅(useAuthForm)으로 분리
asksa1256 Jul 8, 2025
f7cb57d
style: 로그인, 회원가입 폼 로고 반응형 수정
asksa1256 Jul 8, 2025
7839168
refactor: BannerStyle 분리
asksa1256 Jul 8, 2025
04d105e
refactor(BannerStyle): css vars -> 스크립트 변수로 스타일 정의 및 적용
asksa1256 Jul 10, 2025
15b385b
refactor: Banner 합성 기반 구조로 변경
asksa1256 Jul 10, 2025
cdbb48e
fix: 배너 ariaLabel 속성 적용 위치 수정
asksa1256 Jul 10, 2025
e77643c
refactor: 로그인, 회원가입 버튼의 isSubmitting 조건에 따른 텍스트 렌더링 유틸 함수 생성
asksa1256 Jul 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/** @jsxImportSource @emotion/react */
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import Button from "@/components/ui/Button";
import BannerStyle from "./BannerStyle";

interface BannerProps {
title: string | ReactNode;
imgSrc: string;
imgAlt: string;
linkTo?: string;
ariaLabel?: string;
lazyLoading?: boolean;
}

const Banner = ({
title,
linkTo,
imgSrc,
imgAlt,
ariaLabel,
lazyLoading,
}: BannerProps) => {
const navigate = useNavigate();

return (
<div css={BannerStyle} aria-label={ariaLabel}>
<div className="banner-container">
<div className="banner-info">
<h2 className="banner-title">{title}</h2>
{linkTo && (
<Button
onClick={() => navigate(linkTo)}
aria-label="상품 페이지로 이동"
variant="bannerPrimary"
size="lg"
>
구경하러 가기
</Button>
)}
</div>
<img
className="banner-img"
loading={lazyLoading ? "lazy" : "eager"}
src={imgSrc}
alt={imgAlt}
/>
</div>
</div>
);
};

export default Banner;
128 changes: 128 additions & 0 deletions src/components/Banner/BannerStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { BREAKPOINTS } from "@/constants/responsive";

const BannerStyle = css`
min-height: 540px;
background: var(--background-blue);
color: var(--gray700);
text-align: center;

.banner-container {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
min-height: 540px;
}

.banner-hero {
background: var(--background-blue-light);
}

.banner-title {
font-size: var(--banner-font-size);
margin-bottom: 18px;
word-break: keep-all;

@media (min-width: ${BREAKPOINTS.tablet}px) {
margin-bottom: 24px;
}

@media (min-width: ${BREAKPOINTS.desktop}px) {
margin-bottom: 32px;
}
}

.banner-info {
padding-top: 120px;
}

.banner-hero .banner-info {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
padding-top: 48px;
max-width: 240px;
}

.banner-info .btn-lg {
display: block;
width: 100%;
font-size: var(--banner-btn-font-size);
line-height: 24px;
max-width: 356px;
}

.banner-img {
width: 100%;
max-width: 744px;
margin: 0 auto;
}

@media (min-width: 640px) {
.banner-hero .banner-info {
padding: 84px 0 210px;
max-width: none;
}
}

@media (min-width: ${BREAKPOINTS.tablet}px) {
:root {
--banner-btn-font-size: 20px;
}

.banner {
height: 926px;
}
.banner.banner-hero {
height: 770px;
}

.banner-info .btn-lg {
line-height: 32px;
}
}

@media (min-width: ${BREAKPOINTS.desktop}px) {
.banner-container {
width: var(--container-width);
margin: 0 auto;
}

.banner,
.banner.banner-hero {
height: 540px;
}

.banner-hero .banner-info {
align-items: flex-start;
}

.banner-container {
flex-direction: row;
justify-content: center;
align-items: flex-end;
}

.banner-info {
padding-bottom: 10.75rem;
}
.banner-hero .banner-info {
padding-bottom: 6.25rem;
}

.banner-title {
text-align: left;
}
}

@supports (font-size: clamp(1rem, 2vw, 3rem)) {
:root {
--banner-font-size: clamp(32px, 5vw, 40px);
}
}
`;

export default BannerStyle;
88 changes: 88 additions & 0 deletions src/components/Form/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/** @jsxImportSource @emotion/react */
import FormStyle from "./FormStyle";
import { Link } from "react-router-dom";
import Button from "@/components/ui/Button";
import SocialLogin from "@/components/SocialLogin/SocialLogin";
import logoImg from "@/assets/images/logo.svg";
import loginUser from "@/services/post/loginUser";
import InputField from "../ui/Form/InputField";
import PasswordField from "../ui/Form/PasswordField";
import useAuthForm from "@/hooks/useAuthForm";

const LoginForm = () => {
const {
formRef,
isSubmitting,
submitError,
handleBlur,
validateForm,
isFormValid,
fieldErrors,
handleSubmit,
} = useAuthForm({
onSubmit: async (userData) => {
await loginUser(userData);
},
});

return (
<form
className="form"
ref={formRef}
onSubmit={validateForm}
css={FormStyle}
>
<div className="form-logo">
<Link to="/" aria-label="새로고침">
<img src={logoImg} alt="판다마켓 로고" width="396" height="132" />
</Link>
</div>

<div className="form-contents">
<InputField
label="이메일"
inputId="userEmail"
type="email"
name="email"
placeholder="이메일"
required
onBlur={handleBlur}
fieldError={fieldErrors.email}
/>
<PasswordField
label="비밀번호"
inputId="userPassword"
name="password"
placeholder="비밀번호"
onBlur={handleBlur}
fieldError={fieldErrors.password}
/>

<Button
type="submit"
className="btn-lg btn-primary"
id="loginBtn"
disabled={!isFormValid}
variant="primary"
size="lg"
onClick={handleSubmit}
>
{isSubmitting ? "로그인중..." : "로그인"}
</Button>

{submitError && <p>{`${submitError}`}</p>}

<SocialLogin />

<div className="form-footer">
판다마켓이 처음이신가요?
<Link className="form-footer-link" to="/signUp">
회원가입
</Link>
</div>
</div>
</form>
);
};

export default LoginForm;
106 changes: 106 additions & 0 deletions src/components/Form/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/** @jsxImportSource @emotion/react */
import { Link } from "react-router-dom";
import logoImg from "@/assets/images/logo.svg";
import Button from "@/components/ui/Button";
import SocialLogin from "@/components/SocialLogin/SocialLogin";
import InputField from "@/components/ui/Form/InputField";
import PasswordField from "@/components/ui/Form/PasswordField";
import FormStyle from "./FormStyle";
import useAuthForm from "@/hooks/useAuthForm";
import createUser from "@/services/post/createUser";

const SignUpForm = () => {
const {
formRef,
isSubmitting,
submitError,
handleBlur,
validateForm,
isFormValid,
fieldErrors,
handleSubmit,
} = useAuthForm({
onSubmit: async (userData) => {
await createUser(userData);
},
});

return (
<form
className="form"
method="POST"
ref={formRef}
onSubmit={validateForm}
css={FormStyle}
>
<div className="form-logo">
<Link to="/" aria-label="새로고침">
<img src={logoImg} alt="판다마켓 로고" width="396" height="132" />
</Link>
</div>

<div className="form-contents">
<InputField
label="이메일"
inputId="userEmail"
type="email"
name="email"
placeholder="이메일"
required
onBlur={handleBlur}
fieldError={fieldErrors.email}
/>
<InputField
label="닉네임"
inputId="userNickname"
type="text"
name="nickname"
placeholder="닉네임"
required
onBlur={handleBlur}
fieldError={fieldErrors.nickname}
/>
<PasswordField
label="비밀번호"
inputId="userPassword"
name="password"
placeholder="비밀번호"
onBlur={handleBlur}
fieldError={fieldErrors.password}
/>
<PasswordField
label="비밀번호 확인"
inputId="userPasswordChk"
name="passwordCheck"
placeholder="비밀번호 확인"
onBlur={handleBlur}
fieldError={fieldErrors.passwordCheck}
/>

<Button
type="submit"
variant="primary"
size="lg"
id="signupBtn"
disabled={!isFormValid}
onClick={handleSubmit}
>
{isSubmitting ? "회원가입중..." : "회원가입"}
</Button>

{submitError && <p>{`${submitError}`}</p>}

<SocialLogin />

<div className="form-footer">
이미 회원이신가요?
<Link className="form-footer-link" to="/login">
로그인
</Link>
</div>
</div>
</form>
);
};

export default SignUpForm;
Loading