Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 70 additions & 0 deletions react/my-react-app/src/components/auth/AuthFormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { ChangeEvent, FocusEvent } from "react";

interface AuthFormFieldProps {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

html 기본 속성의 경우 아래처럼 표현할 수 있습니다.

interface AuthFormFieldProps extends InputHTMLAttributes<HTMLInputElement> {
  error?: string;
  //... html 속성 제외 커스텀 프랍들
}

id: string;
label: string;
name: string;
type: "email" | "text" | "password";
placeholder: string;
value: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 onChange: ChangeEventHandler<HTMLInputElement>;

이렇게 쓰실 수 있어요!

onBlur?: (e: FocusEvent<HTMLInputElement>) => void; // onBlur는 선택적
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위와 마찬가지 입니다!

  onBlur?: FocusEventHandler<HTMLInputElement>;

error?: string; // 에러 메시지도 선택적
showPasswordToggle?: boolean; // 비밀번호 표시/숨김 토글 여부
showPassword?: boolean; // 현재 비밀번호 표시 상태
onToggleShowPassword?: () => void; // 토글 함수
}

const AuthFormField: React.FC<AuthFormFieldProps> = ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크게 상관은 없는데..
암시적인 children 포함, 제네릭 활용 어려움 등의 이유로 FC 타입은 지양되는 분위기가 있습니다. 실제로 CRA에서는 제거 되기도 했죠. 참고만 해주세요 :)

facebook/create-react-app#8177

id,
label,
name,
type,
placeholder,
value,
onChange,
onBlur,
error,
showPasswordToggle = false,
showPassword = false,
onToggleShowPassword,
}) => {
return (
<div className={`input-item ${error ? "input-error-active" : ""}`}>
<label htmlFor={id}>{label}</label>
<div className="input-wrapper">
{" "}
{/* 비밀번호 토글 아이콘을 위해 wrapper 추가 */}
<input
id={id}
name={name}
type={
showPasswordToggle && type === "password" && showPassword
? "text"
: type
}
placeholder={placeholder}
value={value}
onChange={onChange}
onBlur={onBlur}
className={error ? "input-error" : ""}
/>
{showPasswordToggle && type === "password" && onToggleShowPassword && (
<img
src={
showPassword
? "/images/icons/eye-visible.svg"
: "/images/icons/eye-invisible.svg"
}
alt={showPassword ? "비밀번호 보임" : "비밀번호 숨김"}
className="toggle-password"
onClick={onToggleShowPassword}
/>
)}
</div>
{error && <p className={`error-message ${name}-error`}>{error}</p>}
</div>
);
};

export default AuthFormField;
15 changes: 15 additions & 0 deletions react/my-react-app/src/components/auth/AuthLogoLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import { Link as RouterLink } from "react-router-dom";

// HomePage.tsx, SigninPage.tsx, SignupPage.tsx 에서 사용된 Link 타입 문제 임시 해결
const Link: any = RouterLink;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as RouterLink만 지워도 타입 문제는 딱히 없지 않나요!? 🤔

import { Link } from "react-router-dom";


const AuthLogoLink: React.FC = () => {
return (
<Link to="/" className="logo-home-button">
<img src="/images/logo/logo.svg" alt="판다마켓 홈" />
</Link>
);
};

export default AuthLogoLink;
25 changes: 25 additions & 0 deletions react/my-react-app/src/components/auth/AuthRedirectLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import { Link as RouterLink } from "react-router-dom";

// HomePage.tsx, SigninPage.tsx, SignupPage.tsx 에서 사용된 Link 타입 문제 임시 해결
const Link: any = RouterLink;

interface AuthRedirectLinkProps {
text: string;
linkText: string;
to: string;
}

const AuthRedirectLink: React.FC<AuthRedirectLinkProps> = ({
text,
linkText,
to,
}) => {
return (
<div className="auth-switch">
{text} <Link to={to}>{linkText}</Link>
</div>
);
};

export default AuthRedirectLink;
35 changes: 35 additions & 0 deletions react/my-react-app/src/components/auth/SocialLoginButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

const SocialLoginButtons: React.FC = () => {
return (
<div className="social-login-container">
<h3>간편 로그인하기</h3>
<div className="social-login-buttons-container">
<a
href="https://www.google.com/"
target="_blank"
rel="noopener noreferrer"
>
<img
src="/images/social/google-logo.png"
alt="구글 로그인"
width="42"
/>
</a>
<a
href="https://www.kakaocorp.com/page/"
target="_blank"
rel="noopener noreferrer"
>
<img
src="/images/social/kakao-logo.png"
alt="카카오톡 로그인"
width="42"
/>
</a>
</div>
</div>
);
};

export default SocialLoginButtons;
134 changes: 41 additions & 93 deletions react/my-react-app/src/components/comment/CommentSection.jsx
Original file line number Diff line number Diff line change
@@ -1,126 +1,74 @@
import React from "react";
import styled from "styled-components";
import React, { useState } from "react";
import CommentItem from "./CommentItem";

const CommentsContainer = styled.div`
margin-top: 40px;
width: 100%;
`;

const SectionTitle = styled.h3`
font-size: 18px;
font-weight: 600;
color: #222;
margin-bottom: 16px;
`;

const CommentForm = styled.form`
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 24px;
padding: 0;
border-radius: 8px;
`;

const CommentInput = styled.textarea`
width: 100%;
height: 104px;
border-radius: 8px;
border: 1px solid #e5e8ec;
background-color: #f4f6fa;
padding: 16px;
font-size: 14px;
resize: none;
box-sizing: border-box;
color: #333333;
font-family: inherit;

&:focus {
border-color: #007aff;
outline: none;
}

&::placeholder {
color: #666;
opacity: 1;
}
`;

const SubmitButton = styled.button`
align-self: flex-end;
padding: 8px 16px;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;

&:hover {
background-color: #5a6268;
}
`;

const CommentList = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
`;

const MessageParagraph = styled.p`
text-align: center;
color: #8b95a1;
padding: 20px;
font-size: 14px;
`;
import {
CommentsContainer,
SectionTitle,
CommentForm,
CommentInput,
SubmitButton,
CommentList,
MessageParagraph,
} from "../../styles/components/comment/CommentSection.styled";

// 디폴트 문구 정의
const DEFAULT_COMMENT =
"개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다.";

function CommentSection({
comments,
newComment,
loadingComments,
commentError,
onCommentChange,
onCommentSubmit,
onCommentFocus,
onCommentBlur,
onToggleMenu
onToggleMenu,
handleCommentSubmit,
submittingComment,
}) {
const [newComment, setNewComment] = useState("");

const handleCommentChange = (e) => {
setNewComment(e.target.value);
};

const handleSubmit = async (e) => {
e.preventDefault();
if (!newComment.trim() || submittingComment) return;
const success = await handleCommentSubmit(newComment);
if (success) {
setNewComment("");
}
};

return (
<CommentsContainer>
<SectionTitle>문의하기</SectionTitle>
<CommentForm onSubmit={onCommentSubmit}>
<CommentForm onSubmit={handleSubmit}>
<CommentInput
placeholder={DEFAULT_COMMENT}
value={newComment}
onChange={onCommentChange}
onFocus={onCommentFocus}
onBlur={onCommentBlur}
onChange={handleCommentChange}
disabled={submittingComment}
/>
<SubmitButton type="submit">등록</SubmitButton>
<SubmitButton
type="submit"
disabled={submittingComment || !newComment.trim()}
>
{submittingComment ? "등록 중..." : "등록"}
</SubmitButton>
</CommentForm>

<CommentList>
{loadingComments ? (
<MessageParagraph>댓글을 불러오는 중...</MessageParagraph>
) : commentError ? (
<MessageParagraph>
댓글을 불러오는데 오류가 발생했습니다.
</MessageParagraph>
<MessageParagraph>{commentError}</MessageParagraph>
) : comments.length === 0 ? (
<MessageParagraph>
아직 댓글이 없습니다. 처음으로 댓글을 남겨보세요!
</MessageParagraph>
) : (
comments.map((comment) => (
<CommentItem
key={comment.id}
comment={comment}
<CommentItem
key={comment.id}
comment={comment}
onToggleMenu={onToggleMenu}
/>
))
Expand Down
8 changes: 1 addition & 7 deletions react/my-react-app/src/components/product/ProductDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import React from "react";
import styled from "styled-components";
import ProductInfo from "./ProductInfo";
import ProductTags from "./ProductTags";
import SellerInfo from "./SellerInfo";

const ProductDetailsContainer = styled.div`
display: flex;
flex-direction: column;
gap: 24px;
`;
import { ProductDetailsContainer } from "../../styles/components/product/ProductDetails.styled";

function ProductDetails({ product, onFavoriteClick }) {
return (
Expand Down
Loading
Loading