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
188 changes: 188 additions & 0 deletions components/AddItemForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { useState } from "react";
import ImgInput from "@/components/ImageInput";
import resetImg from "@/public/svgs/ic_X.svg";
import { useRouter } from "next/router";
import { AddItemFormProps } from "@/types/commontypes";
import Image from "next/image";
import styles from "@/styles/additem.module.css";

const INITIAL_VALUE = {
name: "",
favoriteCount: 0,
description: "",
price: 0,
images: null,
tags: [],
};

export default function AddItemForm({
initialValues = INITIAL_VALUE,
initialPreview,
onSubmit,
onSubmitSuccess,
}: AddItemFormProps) {
const router = useRouter();
const [values, setValues] = useState(initialValues);
const [submittingError, setSubmittingError] = useState<Error | null>(null);
const [tagInput, setTagInput] = useState("");

// 유효성 검사
const isValidForm =
values.name?.trim() !== "" &&
values.description?.trim() !== "" &&
values.price > 0 &&
values.tags.length > 0;

const handleChange = (name: string, value: any) => {
setValues((initialValues) => ({
Copy link
Collaborator

Choose a reason for hiding this comment

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

value를 업데이트 할때는 initialValues 보다는 prev나 prevState 같은 단어를 사용해주시는 것이 조금 더 직관적일 것 같습니다.

...initialValues,
[name]: value,
}));
};

const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
handleChange(name, value);
};

const handleFileChange = (name: string, file: File | null) => {
handleChange(name, file);
};

const handleTagInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTagInput(e.target.value);
};

const handleTagAdd = () => {
if (tagInput.trim() && !values.tags.includes(tagInput.trim())) {
handleChange("tags", [...values.tags, tagInput.trim()]);
setTagInput("");
}
};

const handleTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
handleTagAdd();
}
};

const handleTagRemove = (tagToRemove: string) => {
handleChange(
"tags",
values.tags.filter((tag) => tag !== tagToRemove)
);
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", values.name);
formData.append("favorite", values.favoriteCount.toString());
formData.append("description", values.description);
formData.append("price", values.price.toString());
if (values.images) {
values.images.forEach((image, index) => {
formData.append(`images[${index}]`, image);
});
}
formData.append("tags", JSON.stringify(values.tags));
Comment on lines +80 to +91
Copy link
Collaborator

Choose a reason for hiding this comment

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

로직 간에 개행으로 구분해주시면 가독성에 도움이 될 것 같습니다.


const result = await onSubmit(formData);
if (!result) return;

const { review } = result;
setValues(INITIAL_VALUE);
onSubmitSuccess(review);

router.push("/items");
};

return (
<form
onSubmit={handleSubmit}
className={styles.add_item_form_total_container}
>
<div className={styles.add_item_form_header}>
<div className={styles.add_item_form_title}>상품 등록하기</div>
<button
type="submit"
className={styles.add_item_submit_button}
disabled={!isValidForm || !!submittingError}
>
등록
</button>
</div>
<div className={styles.add_item_form_container}>
<div className={styles.add_item_img_container}>
<div className={styles.add_item_img_title}>상품 이미지</div>
<ImgInput
className={styles.add_item_img_preview}
name="imgFile"
value={values.images ? values.images[0] : null}
initialPreview={initialPreview}
onChange={handleFileChange}
/>
</div>
<div className={styles.add_item_name_container}>
<div className={styles.add_item_name_title}>상품명</div>
<input
className={styles.add_item_name_input}
name="name"
value={values.name}
onChange={handleInputChange}
placeholder="상품명을 입력해주세요"
/>
</div>
<div className={styles.add_item_content_container}>
<div className={styles.add_item_content_title}>상품 소개</div>
<textarea
className={styles.add_item_content_textarea}
name="description"
value={values.description}
onChange={handleInputChange}
placeholder="상품 소개를 입력해주세요"
/>
</div>
<div className={styles.add_item_price_container}>
<div className={styles.add_item_price_title}>판매가격</div>
<input
className={styles.add_item_price_input}
type="number"
name="price"
value={values.price}
onChange={handleInputChange}
placeholder="판매 가격을 입력해주세요"
/>
</div>
<div className={styles.add_item_tags_container}>
<div className={styles.add_item_tags_title}>태그</div>
<input
className={styles.add_item_tag_input}
type="text"
value={tagInput}
onChange={handleTagInputChange}
onKeyDown={handleTagKeyDown}
placeholder="태그를 입력해주세요"
/>
<div className={styles.add_item_tags_list}>
{values.tags.map((tag) => (
<div key={tag} className={styles.add_item_tag}>
#{tag}
<button
type="button"
className={styles.add_item_tag_remove_button}
onClick={() => handleTagRemove(tag)}
>
<Image src={resetImg} alt="제거" />
</button>
</div>
))}
</div>
</div>
</div>
</form>
);
}
35 changes: 17 additions & 18 deletions components/AllArticles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import heart from "@/public/svgs/ic_heart (1).svg";
import defaultImage from "@/public/pngs/noImage.png";
import searchIcon from "@/public/svgs/ic_search.svg";
import { getArticles } from "@/lib/api";
import debounce from "@/lib/utils/debounce";
Copy link
Collaborator

Choose a reason for hiding this comment

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

debounce 유틸 함수로 만들어주신거 좋네요.


export default function AllArticles() {
const [articles, setArticles] = useState<Article[]>([]);
Expand All @@ -25,9 +26,9 @@ export default function AllArticles() {

const data = await getArticles({
orderBy: sortOrder,
page: page,
page,
pageSize: 10,
keyword: keyword,
keyword,
});

if (page === 1) {
Expand Down Expand Up @@ -57,24 +58,22 @@ export default function AllArticles() {
}, [page]);

useEffect(() => {
let debounceTimer: NodeJS.Timeout;

const handleScroll = () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
if (
window.innerHeight + document.documentElement.scrollTop >=
document.documentElement.offsetHeight - 50 &&
!isFetching &&
hasMore
) {
setPage((prev) => prev + 1);
}
}, 200);
};
const handleScroll = debounce(() => {
if (
window.innerHeight + document.documentElement.scrollTop >=
document.documentElement.offsetHeight - 50 &&
!isFetching &&
hasMore
) {
setPage((prev) => prev + 1);
}
}, 200);

window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);

return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [isFetching, hasMore]);

const toggleDropdown = () => {
Expand Down
82 changes: 48 additions & 34 deletions components/AllItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,7 @@ import { Product } from "@/types/commontypes";
import styles from "@/styles/items.module.css";
import searchIcon from "@/public/svgs/ic_search.svg";
import Image from "next/image";

const getPageSize = () => {
if (typeof window === "undefined") return 10;
const width = window.innerWidth;
if (width < 768) {
return 4;
} else if (width < 1280) {
return 6;
} else {
return 10;
}
};
import getPageSize from "@/lib/utils/getPageSize";

function AllItems() {
const [orderBy, setOrderBy] = useState<string>("recent");
Copy link
Collaborator

Choose a reason for hiding this comment

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

string 타입 대신에 들어가는 문자열을 타입으로 사용해보는 것은 어떨까요?

Expand All @@ -26,34 +15,45 @@ function AllItems() {
const [items, setItems] = useState<Product[]>([]);
const [isDropdown, setIsDropdown] = useState<boolean>(false);
const [totalPageNum, setTotalPageNum] = useState<number>(0);
const [keyword, setKeyword] = useState<string>("");

useEffect(() => {
const handleFixSize = () => {
setPageSize(getPageSize());
};

const fetchProducts = async ({
const fetchProducts = async () => {
const products = await getProducts({
orderBy,
page,
pageSize,
}: {
orderBy: string;
page: number;
pageSize: number;
}) => {
const products = await getProducts({ orderBy, page, pageSize });
setItems(products.list);
setTotalPageNum(Math.ceil(products.totalCount / pageSize));
keyword: keyword.trim(),
});

setItems(products.list);
setTotalPageNum(Math.ceil(products.totalCount / pageSize));
};

useEffect(() => {
const handleFixSize = () => {
setPageSize(getPageSize());
};

window.addEventListener("resize", handleFixSize);
fetchProducts({ orderBy, page, pageSize });

fetchProducts();

return () => {
window.removeEventListener("resize", handleFixSize);
};
}, [orderBy, page, pageSize]);

const handleSearchSubmit = () => {
setPage(1);
fetchProducts();
};

const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
handleSearchSubmit();
}
};

const handleNextPage = (newPage: number) => {
setPage(newPage);
};
Expand All @@ -68,23 +68,37 @@ function AllItems() {
setIsDropdown(false);
};

const handleSearchInputChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setKeyword(event.target.value);
};

return (
<div className={styles.all_item_container}>
<div className={styles.all_item_content}>
<div className={styles.all_item_header}>
<div className={styles.all_item_header_front}>
<div className={styles.all_item_title}>전체 상품</div>
<div className={styles.all_item_search_container}>
<Image
className={styles.all_item_search_icon}
src={searchIcon}
alt="돋보기 아이콘"
width={24}
height={24}
/>
<button
onClick={handleSearchSubmit}
className={styles.search_button}
>
<Image
className={styles.all_item_search_icon}
src={searchIcon}
alt="돋보기 아이콘"
width={24}
height={24}
/>
Comment on lines +88 to +94
Copy link
Collaborator

Choose a reason for hiding this comment

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

icon은 next/image를 사용하지 않고 써보시는 것도 추천드릴게요.

</button>
<input
className={styles.all_item_search_input}
placeholder="검색할 상품을 입력해주세요"
value={keyword}
onChange={handleSearchInputChange}
onKeyDown={handleKeyDown}
/>
</div>
</div>
Expand Down
Loading
Loading