Skip to content

[이상달] refactor/sprint8 #221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all 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
107 changes: 107 additions & 0 deletions src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/** @jsxImportSource @emotion/react */
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import Button from "@/components/ui/Button";
import BannerStyle from "./BannerStyle";

interface BannerInfoProps {
align: string;
children: ReactNode;
}

const BannerInfo = ({ children, align }: BannerInfoProps) => {
return <div className="banner-info">{children}</div>;
};

interface BannerButtonProps {
linkTo: string;
children: ReactNode;
ariaLabel?: string;
}

const BannerButton = ({
linkTo,
children = "구경하러 가기",
ariaLabel,
}: BannerButtonProps) => {
const navigate = useNavigate();

return (
<Button
onClick={() => navigate(linkTo)}
aria-label={ariaLabel}
variant="bannerPrimary"
size="lg"
>
{children}
</Button>
);
};

interface BannerImageProps {
imgSrc: string;
imgAlt: string;
lazyLoading?: boolean;
align?: string;
}

const BannerImage = ({
imgSrc,
imgAlt,
lazyLoading,
align,
}: BannerImageProps) => {
return (
<img
className="banner-img"
loading={lazyLoading ? "lazy" : "eager"}
src={imgSrc}
alt={imgAlt}
/>
);
};

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

const Banner = ({
title,
linkTo,
imgSrc,
imgAlt,
ariaLabel,
lazyLoading,
infoAlign = "left",
imgAlign = "right",
}: BannerProps) => {
return (
<div css={BannerStyle} aria-label={ariaLabel}>
<div className="banner-container">
<BannerInfo align={infoAlign}>
<h2 className="banner-title">{title}</h2>
{linkTo && <BannerButton linkTo={linkTo}>구경하러 가기</BannerButton>}
</BannerInfo>
<BannerImage
imgSrc={imgSrc}
imgAlt={imgAlt}
lazyLoading={lazyLoading}
align={imgAlign}
/>
</div>
</div>
);
};

Banner.Info = BannerInfo;
Banner.Button = BannerButton;
Banner.Image = BannerImage;

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

const BannerStyle = css`
min-height: 540px;
background: ${COLORS.background.blue};
color: ${COLORS.gray[700]};
text-align: center;

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

.banner-hero {
background: ${COLORS.background.lightBlue};
}

.banner-title {
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: ${FONT_SIZES[18]};
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) {
.banner {
height: 926px;
}
.banner.banner-hero {
height: 770px;
}

.banner-info .btn-lg {
line-height: 32px;
font-size: ${FONT_SIZES[20]};
}
}

@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)) {
.banner-title {
font-size: clamp(32px, 5vw, 40px);
}
}
`;

export default BannerStyle;
89 changes: 89 additions & 0 deletions src/components/Form/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/** @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";
import { renderButtonTextByState } from "@/utils/renderButtonText";

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}
>
{renderButtonTextByState(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;
Loading
Loading