-
Notifications
You must be signed in to change notification settings - Fork 39
[명지우] sprint7 #210
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-\uBA85\uC9C0\uC6B0-sprint7"
[명지우] sprint7 #210
Changes from 23 commits
33dbfa1
107698b
875a173
ad8b2ea
d08aa19
b91ce19
7cc0522
09a16d9
c8075f6
3badf19
f866ebe
25a6d47
b7ae85f
fc5a67e
126415b
08c4591
7f475f4
cb98059
b24c7a2
da173eb
4113bbb
ff54bb6
f2c7f0e
e77114e
2355225
ea8d576
5da7345
f23008f
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 |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { instance } from "@apis/instance"; | ||
|
|
||
| const getProduct = async (productId) => { | ||
| try { | ||
| const { data } = await instance.get(`/products/${productId}`); | ||
| return data; | ||
| } catch (error) { | ||
| throw new Error(`상품 상세 조회 불러오기 실패: ${error.message}`); | ||
| } | ||
| }; | ||
|
|
||
| const getProductComments = async (productId, limit = 10, cursor = null) => { | ||
| try { | ||
| let url = `/products/${productId}/comments?limit=${limit}`; | ||
| if (cursor) { | ||
| url += `&cursor=${cursor}`; | ||
| } | ||
|
|
||
| const { data } = await instance.get(url); | ||
| return data; | ||
| } catch (error) { | ||
| throw new Error(`상품의 댓글 정보 불러오기 실패: ${error.message}`); | ||
| } | ||
| }; | ||
|
|
||
| export { getProduct, getProductComments }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| const BackIcon = () => { | ||
| return ( | ||
| <svg | ||
| width="25" | ||
| height="24" | ||
| viewBox="0 0 25 24" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path | ||
| d="M6.53333 3.60012C6.03627 3.60012 5.63333 4.00307 5.63333 4.50012C5.63333 4.99718 6.03627 5.40012 6.53333 5.40012V3.60012ZM6.53333 5.40012H16.6667V3.60012H6.53333V5.40012ZM21.1 9.83345V10.9001H22.9V9.83345H21.1ZM16.6667 15.3335H6.53333V17.1335H16.6667V15.3335ZM21.1 10.9001C21.1 13.3486 19.1151 15.3335 16.6667 15.3335V17.1335C20.1092 17.1335 22.9 14.3427 22.9 10.9001H21.1ZM16.6667 5.40012C19.1151 5.40012 21.1 7.38499 21.1 9.83345H22.9C22.9 6.39088 20.1092 3.60012 16.6667 3.60012V5.40012Z" | ||
| fill="white" | ||
| /> | ||
| <path d="M3 16.2335L10.2 12.5384L10.2 19.9285L3 16.2335Z" fill="white" /> | ||
| </svg> | ||
| ); | ||
| }; | ||
|
|
||
| export default BackIcon; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| const MoreIcon = () => { | ||
| return ( | ||
| <svg | ||
| width="3" | ||
| height="13" | ||
| viewBox="0 0 3 13" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <circle cx="1.5" cy="1.50012" r="1.5" fill="#9CA3AF" /> | ||
| <circle cx="1.5" cy="6.50012" r="1.5" fill="#9CA3AF" /> | ||
| <circle cx="1.5" cy="11.5001" r="1.5" fill="#9CA3AF" /> | ||
| </svg> | ||
| ); | ||
| }; | ||
|
|
||
| export default MoreIcon; |
|
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. 💊 제안 |
|
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. 💊 제안 |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,89 @@ | ||||||||||
| import { useState } from "react"; | ||||||||||
| import styled from "@emotion/styled"; | ||||||||||
| import BaseButton from "@/components/common/BaseButton"; | ||||||||||
| import MoreIcon from "@assets/icons/more"; | ||||||||||
|
|
||||||||||
| const DROPDOWN_LIST_POSITION = { | ||||||||||
| left: "right: 0; left: auto;", | ||||||||||
| right: "left: 0; right: auto;", | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const DropdownMenu = ({ | ||||||||||
| dropdownItem1, | ||||||||||
| onDropdownItem1Click, | ||||||||||
| dropdownItem2, | ||||||||||
| onDropdownItem2Click, | ||||||||||
| position = "left", | ||||||||||
| }) => { | ||||||||||
|
Comment on lines
+11
to
+17
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. 💊 제안 |
||||||||||
| const [isOpen, setIsOpen] = useState(false); | ||||||||||
|
|
||||||||||
| const handleToggle = () => setIsOpen((prev) => !prev); | ||||||||||
|
Comment on lines
+18
to
+20
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
|
||||||||||
|
|
||||||||||
| const handleDropdownItem1Click = () => { | ||||||||||
| onDropdownItem1Click(); | ||||||||||
| setIsOpen(false); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const handleDropdownItem2Click = () => { | ||||||||||
| onDropdownItem2Click(); | ||||||||||
| setIsOpen(false); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| return ( | ||||||||||
| <DropdownMenuContainer> | ||||||||||
| <StyledButton onClick={handleToggle}> | ||||||||||
| <MoreIcon /> | ||||||||||
| </StyledButton> | ||||||||||
|
|
||||||||||
| {isOpen && ( | ||||||||||
| <DropdownList listPosition={DROPDOWN_LIST_POSITION[position]}> | ||||||||||
| <DropdownItem onClick={handleDropdownItem1Click}> | ||||||||||
| {dropdownItem1} | ||||||||||
| </DropdownItem> | ||||||||||
| <DropdownItem onClick={handleDropdownItem2Click}> | ||||||||||
| {dropdownItem2} | ||||||||||
| </DropdownItem> | ||||||||||
| </DropdownList> | ||||||||||
| )} | ||||||||||
| </DropdownMenuContainer> | ||||||||||
| ); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| export default DropdownMenu; | ||||||||||
|
|
||||||||||
| const DropdownMenuContainer = styled.div` | ||||||||||
| position: relative; | ||||||||||
| `; | ||||||||||
|
|
||||||||||
| const StyledButton = styled(BaseButton)` | ||||||||||
| width: 2.4rem; | ||||||||||
| height: 2.4rem; | ||||||||||
| `; | ||||||||||
|
|
||||||||||
| const DropdownList = styled.ul` | ||||||||||
| width: fit-content; | ||||||||||
| position: absolute; | ||||||||||
| top: 3rem; | ||||||||||
| ${(props) => props.listPosition}; | ||||||||||
| border-radius: 0.7rem; | ||||||||||
| border: 1px solid var(--gray200); | ||||||||||
| background-color: var(--white); | ||||||||||
| `; | ||||||||||
|
|
||||||||||
| const DropdownItem = styled.li` | ||||||||||
| width: 10rem; | ||||||||||
| padding: 0.8rem 1rem; | ||||||||||
| text-align: center; | ||||||||||
| list-style: none; | ||||||||||
| font-size: 1.4rem; | ||||||||||
| color: var(--gray500); | ||||||||||
| cursor: pointer; | ||||||||||
|
|
||||||||||
| &:hover { | ||||||||||
| background-color: var(--gray100); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| &:first-of-type { | ||||||||||
| border-bottom: 1px solid var(--gray200); | ||||||||||
| } | ||||||||||
| `; | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import styled from "@emotion/styled"; | ||
| import BaseButton from "@/components/common/BaseButton"; | ||
|
|
||
| const BUTTON_SIZE = { | ||
| s: "1.2rem", | ||
| m: "1.4rem", | ||
| l: "1.6rem", | ||
| }; | ||
|
|
||
| const BUTTON_TYPE = { | ||
| primary: "background-color: var(--blue); color: var(--white);", | ||
| cancel: "background-color: var(--white); var(--gray500);", | ||
| danger: "background-color: red; color: var(--white);", | ||
| }; | ||
|
|
||
| const TextButton = ({ | ||
| text, | ||
| onClick, | ||
| disabled = false, | ||
| size = "m", | ||
| type = "primary", | ||
| }) => { | ||
| return ( | ||
| <StyledButton | ||
| onClick={onClick} | ||
| disabled={disabled} | ||
| size={BUTTON_SIZE[size]} | ||
| type={BUTTON_TYPE[type]} | ||
| > | ||
| {text} | ||
| </StyledButton> | ||
| ); | ||
| }; | ||
|
|
||
| export default TextButton; | ||
|
|
||
| const StyledButton = styled(BaseButton)` | ||
| padding: 0.8rem 2rem; | ||
| border-radius: 1rem; | ||
| font-size: ${(props) => props.size}; | ||
| ${(props) => props.type}; | ||
|
|
||
| &:disabled { | ||
| background-color: var(--gray300); | ||
| cursor: not-allowed; | ||
| } | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import styled from "@emotion/styled"; | ||
|
|
||
| const BaseButton = ({ onClick, children, ...props }) => { | ||
| return ( | ||
| <Button onClick={onClick} {...props}> | ||
| {children} | ||
| </Button> | ||
| ); | ||
| }; | ||
|
|
||
| export default BaseButton; | ||
|
|
||
| const Button = styled.button` | ||
| border: none; | ||
| background: none; | ||
|
|
||
| display: inline-flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
|
|
||
| &:hover { | ||
| opacity: 0.7; | ||
| } | ||
| `; |
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.
💊 제안
getProductComments에서 쿼리 문자열을 직접 조합하기보다는 URLSearchParams을 활용하시면 가독성과 유지보수성이 더 좋을 것 같아요~
https://developer.mozilla.org/ko/docs/Web/API/URLSearchParams