-
Notifications
You must be signed in to change notification settings - Fork 39
[문주영] Sprint8 #226
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
The head ref may contain hidden characters: "React-\uBB38\uC8FC\uC601-sprint8"
[문주영] Sprint8 #226
Changes from all commits
9e8689d
6e8f0e2
ef6c045
55cc07a
fedb887
256e81c
4852f5c
c13d5cc
aa7a4b1
232d126
6145429
69d3a32
d029caa
d74651a
285ef71
8cd8d53
24a3734
4ae723b
88ee07f
64f9003
8824a67
2e342bd
f434aa2
ec8e128
f5db528
cdb81ed
71bd156
3b5984e
1988541
df91800
2f706ea
c85b125
1345370
4349be0
8f01942
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Comments, Product, Products } from "./apiTypes"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const BASE_URL = "https://panda-market-api.vercel.app"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function getProducts({ page=1, pageSize=10, orderBy="recent", keyword='' }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| page?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pageSize?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| orderBy?: "recent" | "favorite"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keyword?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): Promise<Products> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${BASE_URL}/products?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}${keyword && "&keyword="+keyword}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { throw Error("Request Error"); } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (await response.json()) as Products; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+14
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 여담
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function getProduct({ productId }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| productId: number | string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): Promise<Product> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${BASE_URL}/products/${productId}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { throw Error("Request Error"); } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (await response.json()) as Product; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function getProductComments({ productId, limit }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| productId: number | string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): Promise<Comments> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${BASE_URL}/products/${productId}/comments?limit=${limit}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { throw Error("Request Error"); } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (await response.json()) as Comments; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| export interface Product { | ||
| createdAt: string; | ||
| favoriteCount: number; | ||
| ownerNickname: string; | ||
| ownerId: number; | ||
| images: string[]; | ||
| tags: string[]; | ||
| price: number; | ||
| description: string; | ||
| name: string, | ||
| id: number; | ||
| isFavorite: boolean; | ||
| } | ||
|
|
||
| export interface Products { | ||
| totalCount: number; | ||
| list: Product[]; | ||
| } | ||
|
Comment on lines
+15
to
+18
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 칭찬 |
||
|
|
||
| export interface Comment { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 제안 |
||
| writer: { | ||
| image: string; | ||
| nickname: string; | ||
| id: number; | ||
| }; | ||
|
Comment on lines
+21
to
+25
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 여담 export interface CommentWriterProps {
image: string;
nickname: string;
id: number;
}
export interface CommentProps {
writer: WriterProps;
}위에처럼 분리하시면 아래처럼 작성이 가능합니다. // 분리된 타입을 사용하는 경우
export const CommentWriter = ({ image, nickname, id }: CommentWriterProps) => { ... }
// 또는 유틸리티 타입을 사용하는 방법도 있습니다
export const CommentWriter = ({ image, nickname, id }: Pick<Comment, 'writer'>) => { ... } |
||
| updatedAt: string; | ||
| createdAt: string; | ||
| content: string; | ||
| id: number; | ||
| } | ||
|
|
||
| export interface Comments { | ||
| nextCursor: number; | ||
| list: Comment[] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,53 +1,61 @@ | ||
| import { useState } from "react"; | ||
| import { useState, type FocusEvent, type FocusEventHandler, type InputHTMLAttributes } from "react"; | ||
| import Input from "./Input"; | ||
| import icEyeVisible from "../assets/ic_eye_visible.svg"; | ||
| import icEyeInvisible from "../assets/ic_eye_invisible.svg"; | ||
| import "./AuthInput.css"; | ||
|
|
||
| function PasswordInput ({ name, className, onFocusout, valid, wrongMessage, ...props }) { | ||
| interface PasswordProps extends InputHTMLAttributes<HTMLInputElement> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💊 제안 interface PasswordProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
wrongMessage: string;
onFocusout?: FocusEventHandler;
valid?: string;
}
interface PasswordProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'name' | 'className'> {
name: string;
label: string;
className?: string;
onFocusout?: FocusEventHandler;
valid?: string;
wrongMessage: string;
} |
||
| name: string; | ||
| label: string; | ||
| className?: string; | ||
| onFocusout?: FocusEventHandler; | ||
| valid?: string; | ||
| wrongMessage: string; | ||
Moon-ju-young marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| function PasswordInput ({ name, label, className='', onFocusout, valid, wrongMessage, ...props }: PasswordProps) { | ||
| const [isVisible, setIsVisible] = useState(false); | ||
| const onClick = () => { | ||
| setIsVisible((prev) => !prev); | ||
| } | ||
| const passwordMatch = () => { | ||
| try { | ||
| const password = document.querySelector("input#password"); | ||
| const passwordCheck = document.querySelector("input#password-check"); | ||
| const password = document.querySelector("input#password") as HTMLInputElement; | ||
| const passwordCheck = document.querySelector("input#password-check") as HTMLInputElement; | ||
Moon-ju-young marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (password.value === passwordCheck.value) { | ||
| passwordCheck.classList.add("correct"); | ||
| passwordCheck.classList.remove("wrong"); | ||
| passwordCheck.nextElementSibling.nextElementSibling.textContent = null; | ||
| (passwordCheck.nextElementSibling as HTMLElement).textContent = null; | ||
| } else if (passwordCheck.value) { | ||
| passwordCheck.classList.add("wrong"); | ||
| passwordCheck.classList.remove("correct"); | ||
| passwordCheck.nextElementSibling.nextElementSibling.textContent = "비밀번호가 일치하지 않습니다."; | ||
| (passwordCheck.nextElementSibling as HTMLElement).textContent = "비밀번호가 일치하지 않습니다."; | ||
| } | ||
| } catch (e) {} | ||
| } | ||
|
|
||
| return ( | ||
| <Input required name={name} | ||
| className={"password "+className} | ||
| type={isVisible ? "text" : "password"} | ||
| onChange={passwordMatch} | ||
| {...(name === "password-check" | ||
| ? { onChange: passwordMatch } | ||
| : { inputClassName: valid, minLength: 8, onBlur: onFocusout })} | ||
| {...props} | ||
| > | ||
| <button className="eye-btn" type="button" onClick={onClick}> | ||
| <img src={isVisible ? icEyeVisible : icEyeInvisible} /> | ||
| </button> | ||
| <div className="wrong-message">{wrongMessage}</div> | ||
| </Input> | ||
| <Input required name={name} label={label} | ||
| className={"password "+className} | ||
| type={isVisible ? "text" : "password"} | ||
| onChange={passwordMatch} | ||
| {...(name === "password-check" ? {} | ||
| : { inputClassName: valid, minLength: 8, onBlur: onFocusout })} | ||
| {...props} | ||
| > | ||
| <div className="wrong-message">{wrongMessage}</div> | ||
| <button className="eye-btn" type="button" onClick={onClick}> | ||
| <img src={isVisible ? icEyeVisible : icEyeInvisible} /> | ||
| </button> | ||
| </Input> | ||
| ); | ||
| } | ||
|
|
||
| export default function AuthInput ({ label, name, type="text", placeholder='', emptyWrongMessage='', invalidWrongMessage='' }) { | ||
| export default function AuthInput ({ label='', name='', type="text", placeholder='', emptyWrongMessage='', invalidWrongMessage='' }) { | ||
| const [valid, setValid] = useState(''); | ||
| const [wrongMessage, setWrongMessage] = useState(null); | ||
| const [wrongMessage, setWrongMessage] = useState(''); | ||
|
|
||
| const onFocusout = (e) => { | ||
| const onFocusout = (e: FocusEvent<HTMLInputElement>) => { | ||
| if (!e.target.value) { | ||
| setValid("wrong"); | ||
| setWrongMessage(emptyWrongMessage); | ||
|
|
@@ -56,7 +64,7 @@ export default function AuthInput ({ label, name, type="text", placeholder='', e | |
| setWrongMessage(invalidWrongMessage); | ||
| } else { | ||
| setValid("correct"); | ||
| setWrongMessage(null); | ||
| setWrongMessage(''); | ||
| } | ||
| } | ||
|
|
||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import type { ButtonHTMLAttributes, ReactNode } from "react"; | ||
| import icHeartLargeActive from "../assets/heart/ic_heart_large_active.svg"; | ||
| import icHeartLargeInactive from "../assets/heart/ic_heart_large_inactive.svg"; | ||
| import icHeartMediumActive from "../assets/heart/ic_heart_medium_active.svg"; | ||
| import icHeartMediumInactive from "../assets/heart/ic_heart_medium_inactive.svg"; | ||
| import styles from "./ButtonHeart.module.css"; | ||
|
|
||
| interface Props extends ButtonHTMLAttributes<HTMLButtonElement> { | ||
| isActive?: boolean; | ||
| className?: string; | ||
| children?: ReactNode; | ||
| } | ||
|
|
||
| function ButtonHeart({ isActive=false, className='', children, ...props }: Props) { | ||
| return (<button className={styles.btn+' '+className} {...props}> | ||
| <img className={styles.large+' '+styles[String(isActive)]} src={icHeartLargeActive} /> | ||
| <img className={styles.large+' '+styles[String(!isActive)]} src={icHeartLargeInactive} /> | ||
| <img className={styles.medium+' '+styles[String(isActive)]} src={icHeartMediumActive} /> | ||
| <img className={styles.medium+' '+styles[String(!isActive)]} src={icHeartMediumInactive} /> | ||
| {children} | ||
| </button>); | ||
| } | ||
|
|
||
| export default ButtonHeart; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 여담
keyword는 옵셔널 파람인데 기본값으로 빈 문자열을 줄 필요가 있을까요?
위의 코드도 동작상 문제가 없을 것 같지만 가독성 측면에서 기본값이 없는것이 더 좋을 것 같아요~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기본값을 주지 않으면 query parameter에 그대로 undefined가 뒤에 붙어서 기본값을 줬습니다~