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-29 🔀 브랜치 최신화 #170

Merged
merged 2 commits into from
Aug 3, 2024
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
8 changes: 8 additions & 0 deletions public/icon/grid-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions public/icon/sort-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/main/FAB.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function FAB() {
<button
type='button'
onClick={scrollToTop}
className='fixed bottom-28 right-16 w-12 h-12 md:w-16 md:h-16 bg-blue-900 rounded-full flex items-center justify-center shadow-lg'
className='fixed z-10 bottom-20 right-6 w-12 h-12 md:w-16 md:h-16 bg-blue-900 rounded-full flex items-center justify-center shadow-lg'
aria-label='Scroll to top'
>
<Image src='/icon/FAB-icon-lg.svg' alt='Scroll to top' width={24} height={24} className='md:w-16 md:h-16' />
Expand Down
36 changes: 36 additions & 0 deletions src/pageLayout/Feed/AddEpigramFAB.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { Button } from '@/components/ui/button';

function AddEpigramFAB() {
const router = useRouter();

const handleAddEpigramClick = () => {
router.push('/addEpigram');
};

return (
<Button
variant='default'
size='lg'
onClick={handleAddEpigramClick}
className='z-10 h-12 lg:h-16 px-3.5 lg:px-5 py-3 lg:py-4 bg-[#2c394d] text-white rounded-[100px] shadow-lg justify-center items-center gap-1 inline-flex fixed bottom-40 right-6 cursor-pointer'
role='button'
aria-label='Add Epigram'
tabIndex={0}
onKeyPress={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleAddEpigramClick();
}
}}
>
<div className='w-6 h-6 relative'>
<Image src='/icon/plus-icon.svg' alt='add icon' layout='fill' objectFit='contain' />
</div>
<span className='text-sm lg:text-xl font-semibold font-pretendard leading-normal lg:leading-loose'>에피그램 만들기</span>
</Button>
);
}

export default AddEpigramFAB;
98 changes: 98 additions & 0 deletions src/pageLayout/Feed/EpigramFeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import useGetRecentEpigrams from '@/hooks/useGetRecentEpigrams';
import { RecentEpigramType } from '@/schema/recentEpigram';
import Image from 'next/image';
import MoreEpigramButton from './MoreEpigramButton';
import FeedCard from './FeedCard';
import spinner from '../../../public/spinner.svg';

function EpigramFeed() {
const router = useRouter();
const [epigrams, setEpigrams] = useState<RecentEpigramType[]>([]);
const [cursor, setCursor] = useState<number>(0);
const [limit, setLimit] = useState<number>(6);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [shouldFetch, setShouldFetch] = useState<boolean>(true);
const [isSingleColumn, setIsSingleColumn] = useState<boolean>(false);

const { data, error, isLoading } = useGetRecentEpigrams({
cursor,
limit,
enabled: shouldFetch,
});

useEffect(() => {
if (data) {
setEpigrams((prevEpigrams) => [...prevEpigrams, ...data.list]);
if (data.list.length > 0) {
setCursor(data.list[data.list.length - 1].id);
}
setIsLoadingMore(false);
setShouldFetch(false);
}
}, [data]);

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

const loadMore = () => {
setIsLoadingMore(true);
setLimit(10);
setShouldFetch(true);
};

const toggleLayout = () => {
setIsSingleColumn(!isSingleColumn);
};

const handleSortKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Enter' || e.key === ' ') {
toggleLayout();
}
};

if (isLoading && epigrams.length === 0) return <p>로딩 중...</p>;
if (error) return <p>{error.message}</p>;

return (
<div>
<div className='w-full h-[26px] justify-between items-center inline-flex mt-[32px] lg:mt-[120px]'>
<h1 className='text-[#373737] text-base lg:text-2xl font-semibold font-pretendard leading-relaxed'>피드</h1>
<div className='w-6 h-6 relative block md:hidden' onClick={toggleLayout} onKeyPress={handleSortKeyPress} role='button' tabIndex={0} aria-label='Toggle layout'>
<Image src={isSingleColumn ? '/icon/grid-icon.svg' : '/icon/sort-icon.svg'} alt={isSingleColumn ? 'grid layout' : 'sort layout'} width={24} height={24} className='w-full h-full' />
</div>
</div>
<div className={`mt-[24px] lg:mt-[40px] mb-[10px] gap-x-2 gap-y-4 md:gap-y-6 lg:gap-y-10 ${isSingleColumn ? 'grid grid-cols-1' : 'grid grid-cols-2'}`}>
{epigrams.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);
}
}}
>
<FeedCard content={epigram.content} author={epigram.author} tags={epigram.tags} size={isSingleColumn ? 'sm2' : 'sm1'} />
</div>
))}
</div>
{isLoadingMore && (
<div className='w-full flex items-center justify-center lg:mt-[70px] md:mt-[50px]'>
<Image src={spinner} alt='로딩중' width={50} height={50} />
</div>
)}
{!isLoadingMore && data?.nextCursor !== null && (
<div className='mt-10 mb-14 w-full flex justify-center'>
<MoreEpigramButton onClick={loadMore} />
</div>
)}
</div>
);
}

export default EpigramFeed;
61 changes: 61 additions & 0 deletions src/pageLayout/Feed/FeedCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';

interface Tag {
name: string;
id: number;
}

interface EpigramCardProps {
content: string;
author: string;
tags: Tag[];
size?: 'sm1' | 'sm2' | 'md' | 'lg';
}

const sizeStyles = {
sm1: 'w-[152px] max-h-[154px]',
sm2: 'w-[312px] max-h-[172px]',
md: 'md:w-[294px] md:max-h-[214px]',
lg: 'lg:w-[585px] lg:max-h-[307px]',
};

const textSizeStyles = {
sm1: 'text-xs',
sm2: 'text-sm',
md: 'md:text-base',
lg: 'lg:text-2xl',
};

const paddingStyles = {
sm1: 'p-4',
sm2: 'p-6',
md: 'md:p-6',
lg: 'lg:p-6',
};

function FeedCard({ content, author, tags, size = 'sm1' }: EpigramCardProps) {
return (
<div className={`relative flex-col justify-start items-end gap-[16px] inline-flex ${sizeStyles[size]} ${sizeStyles.md} ${sizeStyles.lg}`}>
<div
className={`w-full ${paddingStyles[size]} ${paddingStyles.md} ${paddingStyles.lg} bg-white rounded-[14.67px] shadow border border-zinc-100 flex-col justify-start items-start flex relative overflow-hidden`}
>
<div className='absolute inset-0 bg-stripes w-full h-full'></div> {/* Background stripes */}
<div className='relative w-full z-10 flex flex-col justify-start items-start flex-1'>
<div className='self-stretch flex-col justify-start items-start gap-2 flex'>
<div className={`self-stretch ${textSizeStyles[size]} ${textSizeStyles.md} ${textSizeStyles.lg} text-neutral-700 font-normal font-iropkeBatang leading-normal`}>{content}</div>
<div className={`self-stretch ${textSizeStyles[size]} ${textSizeStyles.md} ${textSizeStyles.lg} text-right text-slate-400 font-normal font-iropkeBatang leading-normal`}>- {author}</div>
</div>
</div>
</div>
<div className='justify-start items-start gap-2 inline-flex'>
{tags.map((tag) => (
<div key={tag.id} className={`text-right ${textSizeStyles[size]} ${textSizeStyles.md} ${textSizeStyles.lg} text-slate-400 font-normal font-iropkeBatang leading-normal`}>
#{tag.name}
</div>
))}
</div>
</div>
);
}

export default FeedCard;
24 changes: 24 additions & 0 deletions src/pageLayout/Feed/FeedPageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import Header from '@/components/Header/Header';
import FAB from '@/components/main/FAB';
import EpigramFeed from './EpigramFeed';
import AddEpigramFAB from './AddEpigramFAB';

function FeedLayout() {
return (
<>
<Header icon='search' isLogo insteadOfLogo='' isProfileIcon isShareIcon={false} isButton={false} textInButton='' disabled={false} onClick={() => {}} />
<main className='w-full h-auto flex-col justify-start items-center gap-[72px] inline-flex bg-blue-200'>
<div className='w-[312px] md:w-[600px] lg:w-[1200px] h-auto flex-col justify-center items-center gap-14 inline-flex'>
<div className='self-stretch'>
<EpigramFeed />
</div>
</div>
</main>
<AddEpigramFAB />
<FAB />
</>
);
}

export default FeedLayout;
29 changes: 29 additions & 0 deletions src/pageLayout/Feed/MoreEpigramButton.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 MoreEpigramButton({ onClick }: LoadMoreButtonProps) {
return (
<div
onClick={onClick}
className='h-12 lg:h-14 px-[18px] lg:px-10 py-3 bg-[#f5f7fa] rounded-[100px] border border-[#cfdbea] justify-center items-center gap-1 lg:gap-2 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-blue-400 text-sm lg:text-xl font-normal lg:font-medium font-pretendard leading-normal lg:leading-loose'>에피그램 더보기</div>
</div>
);
}

export default MoreEpigramButton;
7 changes: 7 additions & 0 deletions src/pages/feed/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import FeedLayout from '@/pageLayout/Feed/FeedPageLayout';

function FeedPage() {
return <FeedLayout />;
}

export default FeedPage;
Loading