Skip to content

Commit

Permalink
Merge pull request #579 from YoungUnKim/Sprint9-김영운
Browse files Browse the repository at this point in the history
Sprint9 김영운
  • Loading branch information
kiJu2 authored Jun 2, 2024
2 parents 8d163dd + ba1ce6a commit 79d63f1
Show file tree
Hide file tree
Showing 39 changed files with 1,363 additions and 470 deletions.
89 changes: 89 additions & 0 deletions components/BestPost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import axios from '@/utils/axio';
import { useEffect, useState } from 'react';
import style from '@/styles/bestPost.module.css';
import bestBadge from '@/public/icon/img_badge.svg';
import heartImg from '@/public/icon/ic_heart.svg';
import Image from 'next/image';
import timeString from '@/utils/timeString';
import { useMediaQuery } from 'react-responsive';
import Link from 'next/link';

interface List {
id: number;
title: string;
content: string;
image: null | string;
likeCount: number;
createdAt: string;
updatedAt: string;
writer: Writer;
}

interface Writer {
id: number;
nickname: string;
}

export default function BestPost() {
const [bestPosts, setBestPosts] = useState<List[]>([]);

const isTablet = useMediaQuery({
query: `(max-width: 1024px)`,
});

const isMobile = useMediaQuery({
query: `(max-width: 768px)`,
});

function pageSizePerSize() {
if (isMobile) return 1;
else if (isTablet) return 2;
else return 3;
}

async function getBest() {
const pageSize = pageSizePerSize();
const res = await axios.get(`/articles?page=1&pageSize=${pageSize}&orderBy=like`);
const posts = res.data.list ?? [];
setBestPosts(posts);
}

useEffect(() => {
try {
getBest();
} catch (error) {
console.log(error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTablet, isMobile]);

return (
<>
{bestPosts.map(item => (
<Link key={item.id} href={`/boards/${item.id}`}>
<div className={style.BestContainer}>
<div className={style.BestBadge}>
<Image width={102} height={30} src={bestBadge} alt="베스트뱃지" className={style.bestBadge} />
</div>
<div className={style.BestTitle}>
<span className={style.BestTitleText}>{item.title}</span>
{item.image && <Image width={72} height={72} alt="이미지" src={item.image} />}
</div>
<div className={style.BestInfo}>
<div className={style.BestInfoFirst}>
<span className={style.Writer}>{item.writer.nickname}</span>
<div className={style.BestInfoHeart}>
<Image width={16} height={16} alt="하트" src={heartImg} />
<span className={style.LikeCount}>{item.likeCount}</span>
</div>
</div>
<div>
<span className={style.times}>{timeString(item.createdAt)}</span>
</div>
</div>
</div>
</Link>
))}
</>
);
}
90 changes: 90 additions & 0 deletions components/DropDown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Image from 'next/image';
import { useEffect, useRef, useState } from 'react';
import arrow from '@/public/icon/ic_arrow_down.svg';
import sort from '@/public/icon/ic_sort.svg';
import style from '@/styles/dropdown.module.css';
import { useMediaQuery } from 'react-responsive';

interface DropdownProps {
onChange: (order: 'recent' | 'like') => void;
}

export default function Dropdown({ onChange }: DropdownProps) {
const [isDropdownOpen, setDropdownOpen] = useState(false);
const [order, setOrder] = useState<'recent' | 'like'>('recent');

const ORDER_KR = {
recent: '최신순',
like: '좋아요순',
};

const isMobile = useMediaQuery({
query: '(max-width: 768px)',
});

const handleMobileChange = () => {
if (isMobile) return <Image src={sort} alt="arrow-icon" />;
else
return (
<>
<span>{order === 'recent' ? '최신순' : '좋아요순'}</span>
<Image src={arrow} alt="arrow-icon" />
</>
);
};

const dropdownContainerRef = useRef<HTMLDivElement>(null);

const handleOrderChange = (order: 'recent' | 'like') => {
setOrder(order);
onChange(order as 'recent' | 'like');
setDropdownOpen(false);
};

const handleDropdownClick = () => {
setDropdownOpen(prev => !prev);
};

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (dropdownContainerRef.current && !dropdownContainerRef.current.contains(e.target as Node)) {
setDropdownOpen(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);

useEffect(() => {
setDropdownOpen(false);
}, [order]);

return (
<div className={style.dropdown_container} ref={dropdownContainerRef}>
<div className={`${style.dropdown} ${isDropdownOpen ? style.open : ''}`} onClick={handleDropdownClick}>
<div className={style.dropdown_button}>
<div className={style.select}>{handleMobileChange()}</div>
{isDropdownOpen && (
<ul className={style.dropdown_contents}>
<li
className={`${style.dropdown_li} ${order === 'recent' ? style.selected : ''}`}
onClick={() => handleOrderChange('recent')}
>
최신순
</li>
<li
className={`${style.dropdown_li} ${order === 'like' ? style.selected : ''}`}
onClick={() => handleOrderChange('like')}
>
좋아요순
</li>
</ul>
)}
</div>
</div>
</div>
);
}
34 changes: 34 additions & 0 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import MainLogo from '@/public/icon/main_logo.svg';
import SmallMainLogo from '@/public/icon/main_logo_small.svg';
import Link from 'next/link';
import { useRouter } from 'next/router';
import Image from 'next/image';
import styles from '@/styles/Header.module.css';
import user_icon from '@/public/icon/user_icon.svg';

const NavBar: React.FC = () => {
const router = useRouter();

return (
<nav className={styles.navvar}>
<Link href="/">
<Image width={153} height={51} className={styles.mainlogo} src={MainLogo} alt="로고" />
<Image width={81} height={27} className={styles.mainlogo} src={SmallMainLogo} alt="로고" />
</Link>
<div className={styles.menus}>
<Link href="/boards" className={router.pathname === '/boards' ? styles.focus : ''}>
<span>자유게시판</span>
</Link>
<Link href="/items" className={router.pathname === '/items' ? styles.focus : ''}>
<span>중고마켓</span>
</Link>
</div>
<Link href="/mypage">
<Image width={40} height={40} alt="user-icon" src={user_icon} />
</Link>
</nav>
);
};

export default NavBar;
51 changes: 51 additions & 0 deletions components/posts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styles from '@/styles/posts.module.css';
import Image from 'next/image';
import heartImg from '@/public/icon/ic_heart.svg';
import timeString from '@/utils/timeString';
import user_icon from '@/public/icon/user_icon.svg';
import Link from 'next/link';

interface List {
id: number;
title: string;
content: string;
image: null | string;
likeCount: number;
createdAt: string;
updatedAt: string;
writer: Writer;
}
interface Writer {
id: number;
nickname: string;
}

interface PostsProps {
posts: List;
}

export default function Posts({ posts }: PostsProps) {
return (
<Link href={`/borads/${posts.id}`}>
<div key={posts.id} className={styles.postContainer}>
<div className={styles.postsTop}>
<span className={styles.postsTitle}>{posts.title}</span>
{posts.image && <Image width={72} height={72} alt="이미지" src={posts.image} />}
</div>
<div className={styles.postsBottom}>
<div className={styles.BestInfoFirst}>
<Image width={24} height={24} alt="프로필사진" src={user_icon} />
<span className={styles.Writer}>{posts.writer.nickname}</span>
<span className={styles.times}>{timeString(posts.createdAt)}</span>
</div>
<div>
<div className={styles.BestInfoHeart}>
<Image width={16} height={16} alt="하트" src={heartImg} />
<span className={styles.LikeCount}>{posts.likeCount}</span>
</div>
</div>
</div>
</div>
</Link>
);
}
23 changes: 23 additions & 0 deletions components/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styles from '@/styles/Board.module.css';
import searchIC from '@/public/icon/ic_Search.svg';
import Image from 'next/image';

interface SearchInputProps {
value: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}

export default function SearchInput({ value, onChange }: SearchInputProps) {
return (
<div className={styles.SearchContainer}>
<Image width={15} height={15} src={searchIC} alt="검색 아이콘" className={styles.SearchIcon} />
<input
type="text"
placeholder="검색할 상품을 입력해주세요"
value={value}
onChange={onChange}
className={styles.SearchInput}
/>
</div>
);
}
14 changes: 12 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com',
port: '',
pathname: '/Sprint_Mission/**',
},
],
},
};

module.exports = nextConfig
module.exports = nextConfig;
Loading

0 comments on commit 79d63f1

Please sign in to comment.