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

FE-17 ✨ 최근 에피그램 더보기 기능 구현 #67

Merged
merged 17 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ module.exports = {
'react/jsx-props-no-spreading': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': ['off'],
"react/require-default-props": 'off',
"react/self-closing-comp": 'off',
},
settings: {
react: {
Expand Down
6 changes: 6 additions & 0 deletions public/icon/plus-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/apis/getRecentEpigrams.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { GetRecentEpigramsResponseType } from '@/schema/recentEpigram';
import httpClient from './index';

const getRecentEpigrams = async (): Promise<GetRecentEpigramsResponseType> => {
const getRecentEpigrams = async (limit: number): Promise<GetRecentEpigramsResponseType> => {
const response = await httpClient.get('/epigrams', {
params: {
limit: 3,
limit,
},
});
return response.data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/*
1개의 감정 아이콘 카드를 랜더링 합니다.
아이콘의 타입, 상태, 크기, 클릭 이벤트를 관리합니다.
아이콘 타입과 상태에 따라 아이콘의 모양과 스타일을 조정합니다.
*/

import React from 'react';
import cn from '@/lib/utils';
import Image from 'next/image';
Expand Down Expand Up @@ -104,11 +110,4 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm',
);
}

EmotionIconCard.displayName = 'EmotionIconCard';

// 기본 props 설정
EmotionIconCard.defaultProps = {
onClick: () => {},
};

export default EmotionIconCard;
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/*
여러 개의 EmotionIconCard를 관리합니다.
사용자 인터페이스에 필요한 상호 작용 로직을 포함합니다.
*/

import React, { useState } from 'react';
import InteractiveEmotionIconCard from '@/components/Emotion/card/InteractiveEmotionIconCard';
import EmotionIconCard from '@/components/Emotion/EmotionCard';
import useMediaQuery from '@/hooks/useMediaQuery';
import { EmotionType, EmotionState } from '@/types/EmotionTypes';

Expand Down Expand Up @@ -52,7 +57,7 @@ function EmotionSelector() {
return (
<div className={`justify-start items-start inline-flex ${containerClass}`}>
{(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => (
<InteractiveEmotionIconCard key={iconType} iconType={iconType} size={cardSize} state={states[iconType]} onClick={() => handleCardClick(iconType)} />
<EmotionIconCard key={iconType} iconType={iconType} size={cardSize} state={states[iconType]} onClick={() => handleCardClick(iconType)} />
))}
</div>
);
Expand Down
12 changes: 0 additions & 12 deletions src/components/Emotion/card/InteractiveEmotionIconCard.tsx

This file was deleted.

32 changes: 22 additions & 10 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import React from 'react';
import { useRouter } from 'next/router';
import Image from 'next/image';
import { HeaderProps } from '../../types/Header';
import { useToast } from '../ui/use-toast';
import LOGO_ICON from '../../../public/epigram-icon.png';
import ARROW_LEFT_ICON from '../../../public/icon/arrow-left-icon.svg';
import PROFILE_ICON from '../../../public/icon/profile-icon.svg';
import SEARCH_ICON from '../../../public/icon/search-icon.svg';
import SHARE_ICON from '../../../public/icon/share-icon.svg';

// TODO 네비게이션 바를 나타내는 컴포넌트 입니다.
// TODO 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다.
// TODO 사용 예시
// TODO <Header icon='back' routerPage='원하는 페이지 주소' isLogo={false} insteadOfLogo='센터 텍스트' isProfileIcon={false} isShareIcon={false} isButton textInButton='버튼 텍스트' disabled={false} onClick={동작할 함수} />
// TODO <Header icon='search' routerPage='/search' isLogo insteadOfLogo='' isProfileIcon isShareIcon={false} isButton={false} textInButton='' disabled={false} onClick={() => {}} />;
// TODO icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다.
// TODO isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다.
// TODO isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다.
// TODO SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 <Toaster /> 를 추가해주세요.
// NOTE 네비게이션 바를 나타내는 컴포넌트 입니다.
// NOTE 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다.
// NOTE 사용 예시
// NOTE <Header icon='back' routerPage='원하는 페이지 주소' isLogo={false} insteadOfLogo='센터 텍스트' isProfileIcon={false} isShareIcon={false} isButton textInButton='버튼 텍스트' disabled={false} onClick={동작할 함수} />
// NOTE <Header icon='search' routerPage='/search' isLogo insteadOfLogo='' isProfileIcon isShareIcon={false} isButton={false} textInButton='' disabled={false} onClick={() => {}} />;
// NOTE icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다.
// NOTE isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다.
// NOTE isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다.
// NOTE SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 <Toaster /> 를 추가해주세요.

export interface HeaderProps {
icon: 'back' | 'search' | '';
routerPage: string;
isLogo: boolean;
insteadOfLogo: string;
isProfileIcon: boolean;
isShareIcon: boolean;
isButton: boolean;
textInButton: string;
disabled: boolean;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
}

function Header({ isLogo, icon, insteadOfLogo, isButton, isProfileIcon, isShareIcon, textInButton, routerPage, disabled, onClick }: HeaderProps) {
const router = useRouter();
Expand Down
29 changes: 29 additions & 0 deletions src/components/main/LoadMoreButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import Image from 'next/image';

interface LoadMoreButtonProps {
onClick: () => void;
}

function LoadMoreButton({ onClick }: LoadMoreButtonProps) {
return (
<div
onClick={onClick}
className='h-12 px-[18px] py-3 rounded-[100px] border border-[#cfdbea] justify-center items-center gap-1 inline-flex cursor-pointer'
role='button'
tabIndex={0}
onKeyPress={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onClick();
}
}}
>
<div className='w-6 h-6 relative'>
<Image src='/icon/plus-icon.svg' alt='plus icon' layout='fill' objectFit='contain' />
</div>
<div className='text-#abb8ce text-sm font-normal font-Pretendard leading-normal'>더보기</div>
</div>
);
}

export default LoadMoreButton;
34 changes: 31 additions & 3 deletions src/components/main/RecentEpigram.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import React from 'react';
import React, { useState } from 'react';
import { useRouter } from 'next/router';
import useGetRecentEpigrams from '@/hooks/useGetRecentEpigrams';
import EpigramCard from '@/components/Card/EpigramCard';
import { RecentEpigramType } from '@/schema/recentEpigram';
import LoadMoreButton from './LoadMoreButton';

function RecentEpigrams() {
const { data, error, isLoading } = useGetRecentEpigrams();
const router = useRouter();
const [limit, setLimit] = useState(3);
const { data, error, isLoading } = useGetRecentEpigrams(limit);

const handleEpigramClick = (id: number) => {
router.push(`/epigrams/${id}`);
};

const loadMore = () => {
setLimit((prevLimit) => prevLimit + 5);
};

if (isLoading) return <p>로딩 중...</p>;
if (error) return <p>{error.message}</p>;

return (
<div>
<h1>최신 에피그램</h1>
{data?.list.map((epigram) => <EpigramCard key={epigram.id} content={epigram.content} author={epigram.author} tags={epigram.tags} />)}
{data?.list.map((epigram: RecentEpigramType) => (
<div
key={epigram.id}
onClick={() => handleEpigramClick(epigram.id)}
role='button'
tabIndex={0}
onKeyPress={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleEpigramClick(epigram.id);
}
}}
>
<EpigramCard content={epigram.content} author={epigram.author} tags={epigram.tags} />
</div>
))}
{data && limit < data.totalCount && <LoadMoreButton onClick={loadMore} />}
</div>
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useGetRecentEpigrams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';
import getRecentEpigrams from '@/apis/getRecentEpigrams';
import { GetRecentEpigramsResponseType } from '@/schema/recentEpigram';

const useGetRecentEpigrams = () =>
const useGetRecentEpigrams = (limit: number) =>
useQuery<GetRecentEpigramsResponseType, Error>({
queryKey: ['recentEpigrams', 3],
queryFn: getRecentEpigrams,
queryKey: ['recentEpigrams', limit],
queryFn: () => getRecentEpigrams(limit),
});

export default useGetRecentEpigrams;
12 changes: 0 additions & 12 deletions src/types/Header.ts

This file was deleted.

Loading