Skip to content
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

[이수지] sprint11 #132

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
78 changes: 78 additions & 0 deletions api/itemApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ProductListFetcherParams } from '@/types/productTypes';

export async function getProducts({
orderBy,
pageSize,
page = 1,
}: ProductListFetcherParams) {
const params = new URLSearchParams({
orderBy,
pageSize: String(pageSize),
page: String(page),
});

try {
const response = await fetch(
`http://panda-market-api.vercel.app/products?${params}`
);
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;
}
}

export async function getProductDetail(productId: number) {
if (!productId) {
throw new Error('상품 아이디가 유효가지 않습니다');
}

try {
const response = await fetch(
`https://panda-market-api.vercel.app/products/${productId}`
);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}

const body = await response.json();
return body;
} catch (error) {
console.error('Failed to fetch product detail:', error);
throw error;
}
}

export async function getProductComments({
productId,
limit = 10,
}: {
productId: number;
limit?: number;
}) {
const params = new URLSearchParams({
limit: String(limit),
productId: String(productId),
});

try {
const query = new URLSearchParams(params).toString();
const response = await fetch(
`https://panda-market-api.vercel.app/products/${productId}/comments?${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 product comments:', error);
throw error;
}
}
3 changes: 2 additions & 1 deletion components/boards/BestArticlesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ const BestArticleCard = ({ article }: { article: Article }) => {
return (
<CardContainer href={`/boards/${article.id}`}>
<BestSticker>
<MedalIcon alt="베스트 게시글" />
{/* <MedalIcon alt="베스트 게시글" /> */}
<Image src={MedalIcon} alt="베스트 게시글" />
Best
</BestSticker>

Expand Down
4 changes: 3 additions & 1 deletion components/items/itemPage/CommentThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ProductCommentListResponse,
} from "@/types/commentTypes";
import EmptyState from "@/components/ui/EmptyState";
import Image from "next/image";

const CommentContainer = styled.div`
padding: 24px 0;
Expand Down Expand Up @@ -64,7 +65,8 @@ const CommentItem: React.FC<CommentItemProps> = ({ item }) => {
<CommentContainer>
{/* 참고: 더보기 버튼 기능은 추후 요구사항에 따라 추가 예정 */}
<SeeMoreButton>
<SeeMoreIcon />
{/* <SeeMoreIcon /> */}
<Image src={SeeMoreIcon} alt="더보기" />
</SeeMoreButton>

<CommentContent>{item.content}</CommentContent>
Expand Down
3 changes: 2 additions & 1 deletion components/items/itemPage/ItemProfileSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ const ItemProfileSection: React.FC<ItemProfileSectionProps> = ({ product }) => {
<MainDetails>
{/* 참고: 더보기 버튼 기능은 추후 요구사항에 따라 추가 예정 */}
<SeeMoreButton>
<SeeMoreIcon />
{/* <SeeMoreIcon /> */}
Copy link
Collaborator

Choose a reason for hiding this comment

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

P3:
사용하지 않을 코드는 지워주세요~

<Image src={SeeMoreIcon} alt="더보기" />
</SeeMoreButton>

<div>
Expand Down
43 changes: 28 additions & 15 deletions components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import Logo from "@/public/images/logo/logo.svg";
import Link from "next/link";
import { useRouter } from "next/router";
import styled from "styled-components";
import { StyledLink } from "@/styles/CommonStyles";
import Logo from '@/public/images/logo/logo.svg';
import Link from 'next/link';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { StyledLink } from '@/styles/CommonStyles';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import DefaultProfile from '@/public/images/ui/ic_profile.svg';

const GlobalHeader = styled.header`
display: flex;
Expand All @@ -15,8 +18,7 @@ const HeaderLeft = styled.div`
align-items: center;
`;

// Next.js에서는 next/link의 Link 컴포넌트를 사용해 주세요.
// 참고: Next.js 버전 13부터는 Link 자체가 anchor 태그의 역할을 해요. Link 요소 내에 <a> 태그가 중첩되어 있으면 hydration 오류가 발생하니 주의해 주세요.

const HeaderLogo = styled(Link)`
margin-right: 16px;

Expand Down Expand Up @@ -52,31 +54,38 @@ const NavItem = styled.li`
const LoginLink = styled(StyledLink)``;

function getLinkStyle(isActive: boolean) {
return { color: isActive ? "var(--blue)" : undefined };
return { color: isActive ? 'var(--blue)' : undefined };
}

const Header: React.FC = () => {
const { pathname } = useRouter();
const [isLoggedIn, setIsLoggedIn] = useState(false);

useEffect(() => {
// 컴포넌트가 마운트될 때 sessionStorage에서 토큰을 확인
const token = sessionStorage.getItem('token');
setIsLoggedIn(!!token); // 토큰이 있으면 true, 없으면 false 설정
}, []);

return (
<GlobalHeader>
<HeaderLeft>
<HeaderLogo href="/" aria-label="홈으로 이동">
<Logo alt="판다마켓 로고" width="153" />
<HeaderLogo href='/' aria-label='홈으로 이동'>
<Image src={Logo} alt='leftBtn' width={153} height={51} />
</HeaderLogo>

<nav>
<NavList>
<NavItem>
<Link href="/boards" style={getLinkStyle(pathname === "/boards")}>
<Link href='/boards' style={getLinkStyle(pathname === '/boards')}>
자유게시판
</Link>
</NavItem>
<NavItem>
<Link
href="/items"
href='/items'
style={getLinkStyle(
pathname.includes("/items") || pathname === "/additem"
pathname.includes('/items') || pathname === '/additem'
)}
>
중고마켓
Expand All @@ -85,8 +94,12 @@ const Header: React.FC = () => {
</NavList>
</nav>
</HeaderLeft>

<LoginLink href="/login">로그인</LoginLink>
{isLoggedIn ? (
// 로그인이 되어 있으면 판다 이미지 렌더링
<Image src={DefaultProfile} alt='판다마켓 기본 프로필' width={40} height={40} />
) : (
<LoginLink href='/login'>로그인</LoginLink>
)}
</GlobalHeader>
);
};
Expand Down
4 changes: 3 additions & 1 deletion components/ui/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from "react";
import styled from "styled-components";
import SortIcon from "@/public/images/icons/ic_sort.svg";
import Image from "next/image";

const SortButtonWrapper = styled.div`
position: relative;
Expand Down Expand Up @@ -54,7 +55,8 @@ const DropdownMenu: React.FC<DropdownMenuProps> = ({
return (
<SortButtonWrapper>
<SortDropdownTriggerButton onClick={toggleDropdown}>
<SortIcon alt="정렬" />
{/* <SortIcon alt="정렬" /> */}
<Image src={SortIcon} alt="정렬" />
</SortDropdownTriggerButton>

{isDropdownVisible && (
Expand Down
20 changes: 11 additions & 9 deletions components/ui/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import styled from "styled-components";
import Image from 'next/image';
import styled from 'styled-components';

// SVG 이미지 형식을 이용하는 큰 장점 중 하나는 비슷한 아이콘을 색상별로 저장할 필요 없이 하나의 파일로 유연하게 변경할 수 있다는 거예요.
// SVG 파일 내에 정의된 크기 또는 선 및 배경색을 동적으로 변경할 수 있는 컴포넌트를 만들어 볼게요.
Expand All @@ -15,21 +16,21 @@ const IconWrapper = styled.div<IconWrapperProps>`
justify-content: center;

svg {
fill: ${({ $fillColor }) => $fillColor || "current"}; // 색 채움
width: ${({ $size }) => ($size ? `${$size}px` : "auto")};
height: ${({ $size }) => ($size ? `${$size}px` : "auto")};
fill: ${({ $fillColor }) => $fillColor || 'current'}; // 색 채움
width: ${({ $size }) => ($size ? `${$size}px` : 'auto')};
height: ${({ $size }) => ($size ? `${$size}px` : 'auto')};
}

/* 선(stroke)의 색상 변경은 svg 내의 path 요소에 넣어줘야 적용돼요 */
/* - 채움색이 있을 때는 아웃라인 색상도 함께 바꿔주는 것이 일반적이기 때문에 선에 대한 속성이지만 fillColor를 outlineColor보다 우선적으로 적용하도록 했어요 */
svg path {
stroke: ${({ $fillColor, $outlineColor }) =>
$fillColor || $outlineColor || "currentColor"};
$fillColor || $outlineColor || 'currentColor'};
}
`;

interface IconProps {
iconComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
iconComponent: string;
size?: number;
fillColor?: string;
outlineColor?: string;
Expand All @@ -40,11 +41,12 @@ const Icon: React.FC<IconProps> = ({
// - 통일성을 위해 prop 이름에는 camelCase를 사용했지만, SVG를 ReactComponent 형태로 전달하고 있기 때문에 PascalCase으로 바꿔 사용
iconComponent: IconComponent,
size, // Optional props이며, prop이 생략될 경우 SVG 파일의 기본 크기를 사용함
fillColor = "currentColor",
outlineColor = "currentColor",
fillColor = 'currentColor',
outlineColor = 'currentColor',
}) => (
<IconWrapper $size={size} $fillColor={fillColor} $outlineColor={outlineColor}>
<IconComponent />
{/* <IconComponent /> */}
<Image src={IconComponent} alt='더보기' />
</IconWrapper>
);

Expand Down
4 changes: 3 additions & 1 deletion components/ui/LikeCountDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import styled from "styled-components";
import HeartIcon from "@/public/images/icons/ic_heart.svg";
import { FlexRowCentered } from "@/styles/CommonStyles";
import Image from "next/image";

const LikeCountWrapper = styled(FlexRowCentered)<{
$fontSize: number;
Expand Down Expand Up @@ -37,7 +38,8 @@ const LikeCountDisplay: React.FC<LikeCountDisplayProps> = ({

return (
<LikeCountWrapper className={className} $fontSize={fontSize} $gap={gap}>
<HeartIcon width={iconWidth} alt="좋아요 아이콘" />
{/* <HeartIcon width={iconWidth} alt="좋아요 아이콘" /> */}
<Image src={HeartIcon} width={iconWidth} alt="좋아요 아이콘" />
{displayCount}
</LikeCountWrapper>
);
Expand Down
7 changes: 5 additions & 2 deletions components/ui/PaginationBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from "styled-components";
import LeftArrow from "@/public/images/icons/arrow_left.svg";
import RightArrow from "@/public/images/icons/arrow_right.svg";
import Image from "next/image";

const PaginationBarContainer = styled.div`
display: flex;
Expand Down Expand Up @@ -59,7 +60,8 @@ const PaginationBar: React.FC<PaginationBarProps> = ({
onClick={() => onPageChange(activePageNum - 1)}
aria-label="이전 페이지로 이동 버튼"
>
<LeftArrow />
{/* <LeftArrow /> */}
<Image src={LeftArrow} alt='이전 페이지로 이동 버튼 이미지' />
</PaginationButton>
{pages.map((page) => (
<PaginationButton
Expand All @@ -75,7 +77,8 @@ const PaginationBar: React.FC<PaginationBarProps> = ({
onClick={() => onPageChange(activePageNum + 1)}
aria-label="다음 페이지로 이동 버튼"
>
<RightArrow />
{/* <RightArrow /> */}
<Image src={RightArrow} alt="다음페이지로 이동버튼 이미지" />
</PaginationButton>
</PaginationBarContainer>
);
Expand Down
4 changes: 3 additions & 1 deletion components/ui/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FlexRowCentered } from "@/styles/CommonStyles";
import SearchIcon from "@/public/images/icons/ic_search.svg";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import Image from "next/image";

const Container = styled(FlexRowCentered)`
background-color: var(--gray-100);
Expand Down Expand Up @@ -57,7 +58,8 @@ const SearchBar: React.FC<SearchBarProps> = ({

return (
<Container>
<SearchIcon alt="검색" />
{/* <SearchIcon alt="검색" /> */}
<Image src={SearchIcon} alt="검색" />
<SearchBarInput
value={keyword}
onChange={handleInputChange}
Expand Down
11 changes: 0 additions & 11 deletions declarations.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
declare module "*.svg" {
import * as React from "react";

// ReactComponent는 SVG를 React 컴포넌트로 사용할 수 있게 합니다.
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;

// 타입스크립트는 SVG 모듈의 ReactComponent를 default export로 처리하지 않기 때문에, ReactComponent와 SVG 파일의 경로(src)를 함께 export 해주어야 합니다.
const src: string;
export default src;
}

declare module "*.png" {
const value: string;
export default value;
Expand Down
Loading
Loading