+ {isError && (
+
+ )}
{preview && (
diff --git a/src/ui/FormComponents/Input.tsx b/src/ui/FormComponents/Input.tsx
index 952f77ee7..658fdc6d8 100644
--- a/src/ui/FormComponents/Input.tsx
+++ b/src/ui/FormComponents/Input.tsx
@@ -1,56 +1,30 @@
-import { useState } from "react";
-import { InputProps } from "../@types/Input";
-import styles from "./Input.module.css";
-import hideIcon from "../../assets/images/hide_icon.png";
-import showIcon from "../../assets/images/show_icon.png";
-
-export default function Input({
- label,
- id,
- className,
- name,
- changeValue,
- hideBtn,
- type,
- register,
- rules,
- errorMsg,
- ...props
-}: InputProps) {
- const [passwordType, setPasswordType] = useState(type);
-
- const togglePasswordVisibility = () => {
- setPasswordType((prevType) =>
- prevType === "password" ? "text" : "password"
- );
- };
-
- const isPassword = type === "password";
-
- return (
-
-
-
-
-
- {isPassword && hideBtn ? (
-
- ) : null}
-
-
- {errorMsg &&
{errorMsg}}
-
- );
-}
+import { ChangeEvent } from "react";
+import { InputProps } from "../@types/Input";
+import styles from "./Input.module.css";
+
+export default function Input({
+ label,
+ id,
+ className,
+ changeValue,
+ ...props
+}: InputProps) {
+ const handleChangeInput = (e: ChangeEvent
) => {
+ let { name, value } = e.target;
+ changeValue(name, value);
+ };
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/ui/FormComponents/TagInput.tsx b/src/ui/FormComponents/TagInput.tsx
index d7d136288..41a4d8d48 100644
--- a/src/ui/FormComponents/TagInput.tsx
+++ b/src/ui/FormComponents/TagInput.tsx
@@ -1,3 +1,4 @@
+import { useEffect } from "react";
import { ChangeEvent, KeyboardEvent, useState } from "react";
import { TagInputProps, Tag } from "../@types/Input";
import styles from "./TagInput.module.css";
@@ -31,13 +32,17 @@ export default function TagInput({
name: tagValue.trim(),
};
const newTags = [...tags, newTag];
- changeValue("tag", newTags);
+
+ if (e.key === "Enter") {
+ e.preventDefault();
+ changeValue("tags", newTags);
+ }
setTagValue("");
};
const handleRemoveTag = (list: Tag) => {
const updateTag = tags.filter((item) => item.id !== list.id);
- changeValue("tag", updateTag);
+ changeValue("tags", updateTag);
};
return (
@@ -54,15 +59,18 @@ export default function TagInput({
{tags &&
- tags.map((tag) => (
- -
- {tag.name}
- handleRemoveTag(tag)}
- className={styles.tagRemoveBtn}
- >
-
- ))}
+ tags.map((tag) => {
+ const tagName = typeof tag === "string" ? tag : tag.name;
+ return (
+ -
+ {tagName}
+ handleRemoveTag(tag)}
+ className={styles.tagRemoveBtn}
+ >
+
+ );
+ })}
diff --git a/src/ui/FormComponents/ValidateInput.tsx b/src/ui/FormComponents/ValidateInput.tsx
new file mode 100644
index 000000000..ab81a0187
--- /dev/null
+++ b/src/ui/FormComponents/ValidateInput.tsx
@@ -0,0 +1,56 @@
+import { useState } from "react";
+import { InputProps } from "../@types/Input";
+import styles from "./Input.module.css";
+import hideIcon from "../../assets/images/hide_icon.png";
+import showIcon from "../../assets/images/show_icon.png";
+
+export default function ValidateInput({
+ label,
+ id,
+ className,
+ name,
+ changeValue,
+ hideBtn,
+ type,
+ register,
+ rules,
+ errorMsg,
+ ...props
+}: InputProps) {
+ const [passwordType, setPasswordType] = useState(type);
+
+ const togglePasswordVisibility = () => {
+ setPasswordType((prevType) =>
+ prevType === "password" ? "text" : "password"
+ );
+ };
+
+ const isPassword = type === "password";
+
+ return (
+
+
+
+
+
+ {isPassword && hideBtn ? (
+
+ ) : null}
+
+
+ {errorMsg &&
{errorMsg}}
+
+ );
+}
diff --git a/src/ui/Header/Header.module.css b/src/ui/Header/Header.module.css
index 969dc87de..f2c61d9ad 100644
--- a/src/ui/Header/Header.module.css
+++ b/src/ui/Header/Header.module.css
@@ -3,7 +3,7 @@
top: 0;
width: 100%;
padding: 0 20rem;
- z-index: 999;
+ z-index: 10;
background: #fff;
border-bottom: 1px solid #dfdfdf;
}
diff --git a/src/utils/http.ts b/src/utils/http.ts
index 09211793e..3b7128e06 100644
--- a/src/utils/http.ts
+++ b/src/utils/http.ts
@@ -1,19 +1,49 @@
+import { QueryClient } from "@tanstack/react-query";
+
+export const queryClient = new QueryClient();
+
+interface Tag {
+ id: string;
+ name?: string;
+}
+
interface Query {
- query: {
- productId?: number;
- currentPage?: number;
- order?: string;
- size?: number;
- keyword?: string;
- };
+ productId?: number;
+ currentPage?: number;
+ order?: string;
+ size?: number;
+ keyword?: string;
}
-const BASE_URL = "https://panda-market-api.vercel.app";
+interface ProductType {
+ images: string | string[];
+ tags: Tag[];
+ price: string;
+ description: string;
+ name: string;
+}
+
+interface PostCommentType {
+ id: string;
+ data: string;
+}
+
+interface LoginType {
+ email: string;
+ password: string;
+}
-export async function getAllProduct({ query }: Query) {
+interface RegisterType {
+ email: string;
+ nickname: string;
+ password: string;
+ passwordCheck: string;
+}
+
+export async function getAllProduct(query: Query) {
const { currentPage, order, size, keyword } = query;
const response = await fetch(
- `${BASE_URL}/products?page=${currentPage}&orderBy=${order}&pageSize=${size}&keyword=${keyword}`
+ `${process.env.REACT_APP_API_BASE_URL}/products?page=${currentPage}&orderBy=${order}&pageSize=${size}&keyword=${keyword}`
);
if (!response.ok) {
throw new Error("데이터를 불러오는 중 에러가 발생했습니다.");
@@ -22,10 +52,10 @@ export async function getAllProduct({ query }: Query) {
return data;
}
-export async function getFavoriteProduct({ query }: Query) {
+export async function getFavoriteProduct(query: Query) {
const { size } = query;
const response = await fetch(
- `${BASE_URL}/products?&orderBy=favorite&pageSize=${size}`
+ `${process.env.REACT_APP_API_BASE_URL}/products?&orderBy=favorite&pageSize=${size}`
);
const data = await response.json();
if (!response.ok) {
@@ -35,7 +65,9 @@ export async function getFavoriteProduct({ query }: Query) {
}
export async function getProductDetail(id: number) {
- const response = await fetch(`${BASE_URL}/products/${id}`);
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/products/${id}`
+ );
if (!response.ok) {
throw new Error("상품 불러오기 실패");
@@ -45,8 +77,59 @@ export async function getProductDetail(id: number) {
}
}
+export async function patchProductDetail(id: number, data: ProductType) {
+ const accessToken = localStorage.getItem("token");
+ const filteredTagsName = data.tags.map((tag) => tag.name);
+ const productData = {
+ images:
+ data.images ||
+ "https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Sprint_Mission/user/3/1721991853452/5389615.png",
+ tags: filteredTagsName,
+ price: data.price,
+ description: data.description,
+ name: data.name,
+ };
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/products/${id}`,
+ {
+ method: "PATCH",
+ body: JSON.stringify(productData),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+ if (!response.ok) {
+ throw new Error("상품 불러오기 실패");
+ } else {
+ const productData = await response.json();
+ return productData;
+ }
+}
+
+export async function removeProduct(id: number) {
+ const accessToken = localStorage.getItem("token");
+
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/products/${id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+ if (!response.ok) {
+ throw new Error("상품 불러오기 실패");
+ }
+}
+
export async function getCommentList(id: number) {
- const response = await fetch(`${BASE_URL}/products/${id}/comments?limit=10`);
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/products/${id}/comments?limit=10`
+ );
if (!response.ok) {
throw new Error("댓글 불러오기 실패");
}
@@ -54,35 +137,136 @@ export async function getCommentList(id: number) {
return list;
}
-export async function signUp(data) {
+export async function signUp(data: RegisterType) {
const userData = {
email: data.email,
nickname: data.nickname,
password: data.password,
passwordConfirmation: data.passwordCheck,
};
- const response = await fetch(`${BASE_URL}/auth/signUp`, {
- method: "POST",
- body: JSON.stringify(userData),
- headers: {
- "Content-Type": "application/json",
- },
- });
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/auth/signUp`,
+ {
+ method: "POST",
+ body: JSON.stringify(userData),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ const result = await response.json();
+ if (!response.ok) {
+ throw new Error(result.message || "예기치 않은 오류가 발생했습니다.");
+ }
+ return result;
+}
+
+export async function updateToken(refreshToken: string) {
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/auth/refresh-token`,
+ {
+ method: "POST",
+ body: JSON.stringify({ refreshToken: refreshToken }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
console.log(response);
+ const result = await response.json();
+ if (!response.ok) {
+ throw new Error(result.message || "예기치 않은 오류가 발생했습니다.");
+ }
+ return result;
}
-export async function signIn(data) {
+export async function signIn(data: LoginType) {
const userData = {
email: data.email,
password: data.password,
};
- const response = await fetch(`${BASE_URL}/auth/signIn`, {
- method: "POST",
- body: JSON.stringify(userData),
- headers: {
- "Content-Type": "application/json",
- },
- });
- const user = await response.json();
- return user;
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/auth/signIn`,
+ {
+ method: "POST",
+ body: JSON.stringify(userData),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ const result = await response.json();
+ if (!response.ok) {
+ throw new Error(result.message || "예기치 않은 오류가 발생했습니다.");
+ }
+ return result;
+}
+
+export async function imageUpload(image: File) {
+ const accessToken = localStorage.getItem("token");
+ const imageData = new FormData();
+ imageData.append("image", image);
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/images/upload`,
+ {
+ method: "POST",
+ body: imageData,
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+ const data = await response.json();
+ return data;
+}
+
+export async function postProduct(data: ProductType, method: string) {
+ const accessToken = localStorage.getItem("token");
+ const filteredTagsName = data.tags.map((tag) => tag.name);
+
+ const productData = {
+ images: data.images || "null",
+ tags: filteredTagsName,
+ price: data.price,
+ description: data.description,
+ name: data.name,
+ };
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/products`,
+ {
+ method: method,
+ body: JSON.stringify(productData),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+ const result = await response.json();
+ if (!response.ok) {
+ throw new Error(result.message || "예기치 않은 오류가 발생했습니다.");
+ }
+ return result;
+}
+
+export async function postProductComment({ id, data }: PostCommentType) {
+ const accessToken = localStorage.getItem("token");
+ const response = await fetch(
+ `${process.env.REACT_APP_API_BASE_URL}/products/${id}/comments`,
+ {
+ method: "POST",
+ body: JSON.stringify({ content: data }),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error("상품 불러오기 실패");
+ } else {
+ const productData = await response.json();
+ return productData;
+ }
}