-
Notifications
You must be signed in to change notification settings - Fork 20
[황혜진] sprint6 #68
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-\uD669\uD61C\uC9C4-sprint6"
[황혜진] sprint6 #68
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,3 +21,6 @@ | |
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| #환경변수 무시 | ||
| .env | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,40 +1,16 @@ | ||||||
| //itemApi.js | ||||||
| const BASE_URL = process.env.REACT_APP_BASE_URL; | ||||||
|
|
||||||
| // export async function getProducts(params = {}) { | ||||||
| // // URLSearchParams을 이용하면 파라미터 값을 자동으로 쉽게 인코딩할 수 있어요. | ||||||
| // const query = new URLSearchParams(params).toString(); | ||||||
| import axios from "axios"; | ||||||
|
|
||||||
| // try { | ||||||
| // const response = await fetch(`${BASE_URL}/products?${query}`); | ||||||
| // if (!response.ok) { | ||||||
| // throw new Error(`HTTP error: ${response.status}`); | ||||||
| // } | ||||||
| // const body = await response.json(); | ||||||
| // return body; | ||||||
| // } catch (error) { | ||||||
| // console.error("Failed to fetch products:", error); | ||||||
| // throw error; | ||||||
| // } | ||||||
| // } | ||||||
| const BASE_URL = process.env.REACT_APP_BASE_URL; | ||||||
|
|
||||||
| export async function getProducts({ page, pageSize, orderBy, keyword }) { | ||||||
| const queryParams = new URLSearchParams({ | ||||||
| page, | ||||||
| pageSize, | ||||||
| orderBy, | ||||||
| }); | ||||||
| try { | ||||||
| const response = await axios.get(`${BASE_URL}/products`, { | ||||||
| params: { page, pageSize, orderBy, keyword }, | ||||||
| }); | ||||||
|
Comment on lines
+7
to
+10
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.
|
||||||
|
|
||||||
| if (keyword) { | ||||||
| queryParams.append("keyword", keyword); | ||||||
| return response.data; | ||||||
| } catch (error) { | ||||||
| throw new Error(`HTTP error: ${error.response?.status || error.message}`); | ||||||
|
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. (제안)
|
||||||
| throw new Error(`HTTP error: ${error.response?.status || error.message}`); | |
| throw new Error(`HTTP error: ${error.response?.message || error.status}`); |
message에 서버로부터의 에러가 포함되어있을 수 있을 것 같아요.
만약 서버 에러 메시지가 포함되어있다면 유저에게 노출할 수도 있겠구요 !
상태만 반환하게 된다면 어떤 에러인지 유저, 그리고 컴포넌트에서도 파악하기 어려울 수 있겠죠?:
example:
400상태만 가지고는 이메일 형식 불일치인지, 빈 값인지 알 수 없을거예요.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // ImageUploader.jsx | ||
| import * as S from "./ImageUploader.styles"; | ||
| import upload_de from "../assets/images/icons/upload_de.svg"; | ||
| import ic_X from "../assets/images/icons/ic_X.svg"; | ||
| import { useState } from "react"; | ||
|
|
||
| export default function ImageUploader() { | ||
| const [previewImage, setPreviewimage] = useState(null); | ||
| const [showLimitText, setShowLimitText] = useState(false); | ||
|
|
||
| const handleImageChange = (e) => { | ||
| const file = e.target.files[0]; | ||
| if (file) { | ||
| const imageUrl = URL.createObjectURL(file); | ||
| setPreviewimage(imageUrl); | ||
| setShowLimitText(false); // 새로 업로드 성공했으니 문구 지우기 | ||
| } | ||
| }; | ||
|
|
||
| const handleRemoveImage = () => { | ||
| setPreviewimage(null); | ||
| setShowLimitText(false); // 이미지 삭제했으니 문구 지우기 | ||
| }; | ||
|
|
||
| const handleUploadClick = (e) => { | ||
| if (previewImage) { | ||
| e.preventDefault(); | ||
| setShowLimitText(true); // 문구 보여주기 | ||
| } | ||
| }; | ||
|
|
||
| const isImageUploaded = !!previewImage; | ||
|
Comment on lines
+7
to
+32
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. 굿굿 ! 컴포넌트가 참 깔끔한데요? 💯 |
||
|
|
||
| return ( | ||
| <S.UploadContainer> | ||
| {/* 업로드 버튼 */} | ||
| <div> | ||
| <S.UploadLabel htmlFor="imageUpload"> | ||
| <S.ImageUploadIcon src={upload_de} alt="이미지 업로드 아이콘" /> | ||
| </S.UploadLabel> | ||
| <S.HiddenInput | ||
| id="imageUpload" | ||
| type="file" | ||
| accept="image/*" | ||
| onChange={handleImageChange} | ||
| onClick={handleUploadClick} | ||
| /> | ||
| {showLimitText && ( | ||
| <S.LimitText>*이미지 등록은 최대 1장까지 가능합니다.</S.LimitText> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* 이미지 미리보기 */} | ||
| {isImageUploaded && ( | ||
| <div> | ||
| <S.PreviewWrapper> | ||
| <S.ImageContainer> | ||
| <S.PreviewImage src={previewImage} alt="상품 이미지 미리보기" /> | ||
| <S.RemoveButton onClick={handleRemoveImage}> | ||
| <S.CloseIcon src={ic_X} alt="삭제" /> | ||
| </S.RemoveButton> | ||
| </S.ImageContainer> | ||
| </S.PreviewWrapper> | ||
| </div> | ||
| )} | ||
| </S.UploadContainer> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import styled from "styled-components"; | ||
| import theme from "../styles/theme"; | ||
|
|
||
| export const UploadContainer = styled.div` | ||
| display: flex; | ||
| gap: 24px; | ||
| `; | ||
|
|
||
| export const UploadLabel = styled.label` | ||
| width: 100%; | ||
| height: 100%; | ||
| `; | ||
|
|
||
| export const ImageUploadIcon = styled.img``; | ||
|
|
||
| export const HiddenInput = styled.input` | ||
| display: none; | ||
| `; | ||
|
|
||
| export const PreviewWrapper = styled.div` | ||
| position: relative; | ||
| display: inline-block; | ||
| `; | ||
|
|
||
| export const ImageContainer = styled.div` | ||
| position: relative; | ||
| width: 282px; | ||
| height: 282px; | ||
| border-radius: 12px; | ||
| overflow: hidden; | ||
| `; | ||
|
|
||
| export const PreviewImage = styled.img` | ||
| width: 100%; | ||
| height: 100%; | ||
| object-fit: cover; | ||
| border-radius: 8px; | ||
| `; | ||
|
|
||
| export const RemoveButton = styled.button` | ||
| position: absolute; | ||
| top: 12px; | ||
| right: 12px; | ||
| cursor: pointer; | ||
| `; | ||
|
|
||
| export const CloseIcon = styled.img` | ||
| width: 24px; | ||
| height: 24px; | ||
| `; | ||
|
|
||
| export const LimitText = styled.p` | ||
| color: ${theme.colors.Error100}; | ||
| font: ${theme.fonts.H5Regular}; | ||
| margin-top: 8px; | ||
| `; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||
| // TagInput.jsx | ||||||
| import * as S from "./TagInput.styles"; | ||||||
| import ic_x from "../assets/images/icons/ic_X.svg"; | ||||||
| import { useState } from "react"; | ||||||
|
|
||||||
| export default function TagInput({ onChange }) { | ||||||
| const [tags, setTags] = useState([]); | ||||||
| const [inputValue, setInputValue] = useState(""); | ||||||
|
|
||||||
| const handleKeyDown = (e) => { | ||||||
| if (e.key === "Enter" && inputValue.trim().length > 1) { | ||||||
| e.preventDefault(); | ||||||
|
|
||||||
| if (!tags.includes(inputValue.trim())) { | ||||||
| const updatedTags = [...tags, inputValue.trim()]; | ||||||
| setTags(updatedTags); | ||||||
|
Comment on lines
+15
to
+16
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. 굿굿 ! 배열을 새로 만들어서 상태를 변경하셨군요 ! 👍배열의 경우 요소를 추가/삭제 하는 것이 아닌 새로운 주소의 배열로 변경을 해줘야하지요. 훌륭합니다 😊 |
||||||
| onChange(updatedTags); | ||||||
| } | ||||||
| setTimeout(() => setInputValue(""), 0); // 입력 후 초기화 + 엔터 눌렀을 때 새로고침 방지 비동기처리 | ||||||
|
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.
|
||||||
| setTimeout(() => setInputValue(""), 0); // 입력 후 초기화 + 엔터 눌렀을 때 새로고침 방지 비동기처리 | |
| setInputValue("") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import styled from "styled-components"; | ||
| import theme from "../styles/theme"; | ||
|
|
||
| export const TagWrapper = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 14px; | ||
| `; | ||
|
|
||
| export const StyledInput = styled.input` | ||
| width: 100%; | ||
| padding: 16px 24px; | ||
| gap: 10px; | ||
| border: none; | ||
| border-radius: 12px; | ||
| background-color: ${theme.colors.Gray100}; | ||
| font: ${theme.fonts.H5Regular}; | ||
| `; | ||
|
|
||
| export const TagList = styled.div` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| height: 36px; | ||
| gap: 12px; | ||
| `; | ||
|
|
||
| export const TagItem = styled.div` | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| height: 36px; | ||
| padding: 6px 12px 6px 16px; | ||
| border-radius: 26px; | ||
| background-color: ${theme.colors.Gray100}; | ||
| color: ${theme.fonts.H5Regular}; | ||
| gap: 10px; | ||
| `; | ||
|
|
||
| export const DeleteButton = styled.button` | ||
| width: 24px; | ||
| height: 24px; | ||
| `; |
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.
크으 ~ 굿굿 👍
환경변수를 만들고
gitignore에 추가하셨군요 ! 😊피드백 반영 넘 좋습니당