Skip to content

Commit

Permalink
[Feat] portfolio page 구현 (#62)
Browse files Browse the repository at this point in the history
* chore: sweetAlert2 설치

* feat: sweetAlert2 설정

* style: sweetAlert 스타일 설정

* feat: WriteGatheringPage

* fix: 스니펫 인터페이스 = 빼기

* chore: 리액트 훅 폼 설치

* feat: 게더링에서 사용될 옵션 타입 정의

* feat: 게더링 셀렉트 버튼 구현

* feat: 게더링 deadline 옵션 구현

* feat: 게더링 링크 옵션 구현

* feat: 게더링 태그 옵션 구현

* feat: 게더링 타이틀 옵션 구현

* refactor: 게더링 옵션에 맞게 공유 컴포넌트 수정

* refactor: 게더링 옵션에 맞게 셀렉 위젯 수정

* feat: 게더링 마크다운 에디터 임시 컴포넌트 구현

* feat: 게더링 상단 옵션 위젯 구현

* feat: 게더링 하단 디테일 위젯 구현

* feat: 게더링 페이지 레이아웃 구현

* feat: 포트폴리오리스트 페이지 페이지 레이아웃 구현

* rename 게더링 카드 컴포넌트 ->feature/gathering 이동

* feat: portfolio card, gird 레이아웃 1

* feat: 포트폴리오 카드 구현

* feature: widget 포트폴리오 그리드 위젝 구현

* feat: 포트폴리오 페이지 레이아웃 구현

* feat: 포트폴리오 관련 index 정리

* feat: 게더링 등록하기 버튼 추가

* fix: 배포 오류(import 오류) 수정

* fix: 서치탭 소모임 -> 게더링으로 수정
  • Loading branch information
joarthvr authored Nov 29, 2024
1 parent 0419250 commit cd526fd
Show file tree
Hide file tree
Showing 23 changed files with 430 additions and 22 deletions.
3 changes: 2 additions & 1 deletion src/app/appRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DetailArchivePage,
ArchiveListPage,
WriteGatheringPage,
PortfolioListPage,
WriteArchivePage,
RegisterPage,
SearchPage,
Expand All @@ -22,7 +23,7 @@ const AppRouter = () => {
},
{
path: '/portfolio',
element: <>{/** portfolioPage */}</>,
element: <PortfolioListPage />,
},
{
path: '/archive',
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions src/features/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './archive';
export * from './gathering';
export * from './portfolio';
export * from './search';

2 changes: 2 additions & 0 deletions src/features/portfolio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PortfolioCard } from './ui/PortfolioCard';
export type { Portfolio } from './model/types';
9 changes: 9 additions & 0 deletions src/features/portfolio/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Portfolio = {
portFolioId: number;
portFolioUrl: string;
username: string;
introduction: string;
majorJobGroup: string;
minorJobGroup: string;
memberImageUrl: string;
};
105 changes: 105 additions & 0 deletions src/features/portfolio/ui/PortfolioCard.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
.container {
width: 18rem;
height: 30.5625rem;
padding-top: 2rem;

// border: 1px solid $primary-color;
transition:
transform 0.3s ease,
box-shadow 0.3s ease; // 트랜지션 효과 추가

&:hover {
transform: translateY(-8px) scale(1.02); // 위로 떠오르면서 약간 확대
}
}

.card {
display: flex;
flex-direction: column;
gap: 1rem;
}

.cardHeader {
position: relative;
width: 18rem;
height: 18.75rem;
overflow: hidden;
}

.cardImg {
display: block;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 12px;

img {
width: 100%;
height: 100%;
object-fit: cover;
background-color: #afafaf;
border-radius: 12px;
fill: $primary-color;
transition: transform 0.3s ease;
}
}

.contactBtn {
position: absolute;
top: 1rem;
right: 1rem;
padding: 0.5rem 1rem;
cursor: pointer;
background-color: white;
border: 1px solid $primary-color;
border-radius: 2rem;
}

.cardFooter {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 0 0.5rem;
}

.firstInfo {
display: flex;
gap: 1rem;
align-items: center;
justify-content: space-between;
color: $primary-color;
}

.name {
font-size: 1.25rem;
font-weight: 600;
color: $primary-color;
}

.job {
display: flex;
gap: 0.5rem;
justify-content: space-between;
font-size: 1rem;
font-weight: 500;
color: $primary-color;
}

.introduction {
display: -webkit-box;
overflow: hidden;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5;
color: #afafaf;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
line-clamp: 3;
word-break: break-word;
-webkit-box-orient: vertical;
}

// hover 시 이미지만 확대하고 싶은 경우 추가
.container:hover .cardImg img {
transform: scale(1.05);
}
55 changes: 55 additions & 0 deletions src/features/portfolio/ui/PortfolioCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Link } from 'react-router-dom';

import styles from './PortfolioCard.module.scss';

import profileImg from '@/shared/assets/paletteLogo.svg';

interface PortfolioCardProps {
portFolioId: number;
portFolioUrl: string;
username: string;
introduction: string;
majorJobGroup: string;
minorJobGroup: string;
memberImageUrl: string;
}

export const PortfolioCard = ({
// portFolioId,
portFolioUrl,
username,
introduction,
majorJobGroup,
minorJobGroup,
memberImageUrl,
}: PortfolioCardProps) => {
return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles.cardHeader}>
<Link className={styles.cardImg} to={portFolioUrl}>
<img
alt={`${username}의 프로필 이미지`}
src={memberImageUrl || profileImg} // 이미지가 없을 경우 기본 이미지 사용
/>
</Link>
<button className={styles.contactBtn}>연락하기</button>
</div>
<div className={styles.cardFooter}>
<div className={styles.firstInfo}>
<span className={styles.name}>{username}</span>
<span className={styles.heart}></span>
</div>
<div className={styles.job}>
<div>
<span>{minorJobGroup}</span>
<span>@{majorJobGroup}</span>
</div>
<div>대표 색</div>
</div>
<div className={styles.introduction}>{introduction}</div>
</div>
</div>
</div>
);
};
78 changes: 78 additions & 0 deletions src/pages/PortfolioListPage/PortfolioListPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
.pageWrapper {
position: relative;
width: 100%;
max-width: 1640px;
min-height: 100vh;
padding: 0 1rem;
margin: 0 auto;
}

.h1 {
margin-top: 4rem;
margin-bottom: 2.5rem;
font-size: 1.875rem;
font-weight: 700;
color: $primary-color;
text-align: center;
}

.contentContainer {
position: sticky;
display: flex; // grid 대신 flex 사용
gap: 2rem;
overflow: auto;

@media screen and (width <= 1440px) {
gap: 2rem;
}

@media screen and (width <= 1200px) {
gap: 1.5rem;
}

@media screen and (width <= 830px) {
flex-direction: column;
gap: 1.5rem;
}
}

.sidebarWrapper {
flex-shrink: 0;
width: 250px;

@media screen and (width <= 1200px) {
width: 220px;
}

@media screen and (width <= 830px) {
width: 100%;
}
}

.sidebarContainer {
position: fixed;
top: 16.5rem;
width: inherit;
max-height: calc(100vh - 14.5rem);
overflow-y: auto;

@media screen and (width <= 830px) {
position: relative;
top: 0;
width: 100%;
max-height: none;
}
}

.mainContent {
flex: 1;
min-width: 0; // flex item 오버플로우 방지
}

.buttonWrapper {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1340px;
margin: 0 auto;
}
49 changes: 49 additions & 0 deletions src/pages/PortfolioListPage/PortfolioListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

import styles from './PortfolioListPage.module.scss';

import { JOB_CATEGORIES } from '@/shared/model';
import { SidebarFilter } from '@/shared/ui';
import { Button, SelectBtn } from '@/shared/ui';
import { PortFolioGrid } from '@/widgets';
export const PortfolioListPage = () => {
const navigate = useNavigate();
const [sort, setSort] = useState({ label: '최신순', value: 'latest' });
return (
<div className={styles.pageWrapper}>
<h1 className={styles.h1}>포트폴리오</h1>

<div className={styles.contentContainer}>
<div className={styles.sidebarWrapper}>
<aside className={styles.sidebarContainer}>
<SidebarFilter categories={JOB_CATEGORIES} />
</aside>
</div>
<div className={styles.mainContent}>
<div className={styles.buttonWrapper}>
<SelectBtn
isClearable={false}
onChange={newValue => {
setSort(newValue as { label: string; value: string });
}}
options={[
{ label: '최신순', value: 'latest' },
{ label: '인기순', value: 'popular' },
]}
value={sort}
/>
<Button
onClick={() => {
navigate('/');
}}
>
포트폴리오 등록하기
</Button>
</div>
<PortFolioGrid />
</div>
</div>
</div>
);
};
10 changes: 6 additions & 4 deletions src/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export * from './GatheringListPage';
export { WriteArchivePage } from './WriteArchivePage/WriteArchivePage';
export { ArchiveListPage } from './ArchiveListPage/ArchiveListPage';
export { DetailArchivePage } from './DetailArchivePage/DetailArchivePage';
export * from './GatheringListPage';
export { PortfolioListPage } from './PortfolioListPage/PortfolioListPage';
export { RegisterPage } from './RegisterPage/RegisterPage';
export { WriteGatheringPage } from './WriteGatheringPage/WriteGatheringPage';
export { SearchPage } from './SearchPage/SearchPage';
export { ArchiveListPage } from './ArchiveListPage/ArchiveListPage';
export { WriteArchivePage } from './WriteArchivePage/WriteArchivePage';
export { WriteGatheringPage } from './WriteGatheringPage/WriteGatheringPage';

1 change: 1 addition & 0 deletions src/shared/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {JOB_CATEGORIES} from './jobCategories'
4 changes: 2 additions & 2 deletions src/shared/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { Button } from './Button/Button';
export { Modal } from './Modal/Modal';
export { GatheringCard } from './GatheringCard/GatheringCard';
export { JobTag } from './GatheringCard/JobTag';
export { GatheringCard } from '../../features/gathering/ui/GatheringCard/GatheringCard';
export { JobTag } from '../../features/gathering/ui/GatheringCard/JobTag';
export { SelectBtn } from './SelectBtn/SelectBtn';
export { MarkdownEditor } from './MarkdownEditor/MarkdownEditor';
export { Input } from './Input/Input';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
.container {
display: flex;
gap: 9.38rem;
justify-content: center;
align-items: center;
justify-content: center;
width: 100%;
margin-bottom: 2rem;
padding: 1rem;
margin-right: 1rem;
margin-bottom: 2rem;
}
Loading

1 comment on commit cd526fd

@github-actions
Copy link

Choose a reason for hiding this comment

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

⚡ Lighthouse report for http://localhost:3000/

Category Score
🔴 Performance 25
🟢 Accessibility 95
🟢 Best Practices 100
🟠 SEO 83

Detailed Metrics

Metric Value
🔴 First Contentful Paint 41.3 s
🔴 Largest Contentful Paint 68.7 s
🔴 Total Blocking Time 860 ms
🟢 Cumulative Layout Shift 0
🔴 Speed Index 54.2 s

Please sign in to comment.