Skip to content

Commit

Permalink
Feat: 마이리스트, 콜라보리스트 페이지 구현 (#10)
Browse files Browse the repository at this point in the history
* Feat: 사용자 피드 페이지 mock 데이터 연결해서 프로필 완성

* Chore: 프리티어 추가

* Feat: 카테고리, 리스트 mock data 추가

* Feat: 리스트 mock data 연결

* Feat: 아이템 목록 mock data 연결

* Feat: 피드페이지 프로필 컴포넌트 분리, 타입 추가

* Feat: Content 컴포넌트 분리, 마이/콜라보 타입에 따른 리스트 목록 불러오기 로직 추가

* Feat: 피드 페이지 - 카테고리 타입에 따른 리스트 목록 불러오기 로직 추가

* Design: 글로벌css 설정, 피드페이지 - 프로필 컴포넌트 UI 구현

* Design: 피드페이지 - 리스트 타입, 카테고리 UI 구현

* Design: 피드페이지 - 리스트 UI 구현(Masonry 레이아웃 적용)

* Design: 피드페이지 전체 스타일 점검, 리스트 배경색 dynamic 스타일 적용

* Design: 리스트, 카테고리 selected 상태일때 스타일 적용

* Feat: 마이리스트, 콜라보리스트 페이지로 각각 분리

* Style: styles prefix 수정, 주석 정리

* Design: 피드페이지 - 플로팅버튼, 헤더 UI 구현, 배경이미지 불러와서 UI 구현, 스타일 일부 수정

* Feat: 리액트쿼리, axios 기본 설정

* Feat: 카테고리 조회 api 추가

* Feat: svg 컴포넌트화 설정 및 기존 img 태그 컴포넌트로 변경

* Design: 리스트 비공개 디자인 수정

* Feat: 플로팅버튼 공통 컴포넌트로 분리, 스크롤 top 기능 구현

* Chore: next/Image 컴포넌트 사용을 위한 hostname 설정

* Design: 프로필 컴포넌트 이미지 next/Image 컴포넌트로 수정

* Chore: 서버 도메인 URL 환경변수로 설정

* Feat: ArrowUp 컴포넌트 스크롤이벤트 쓰로틀링 커스텀 훅 구현

* Design: ArrowUp 버튼 스타일 효과 수정

* Style: props 이름 수정

* Chore: axios 설정

* Style: 콜라보리스트 페이지 props 주석 처리

* Style: 마이리스트 페이지 사용하지 않는 props 주석 처리

---------

Co-authored-by: seoyoung-min <[email protected]>
  • Loading branch information
ParkSohyunee and seoyoung-min authored Feb 3, 2024
1 parent e09aa56 commit 6a517f1
Show file tree
Hide file tree
Showing 43 changed files with 1,332 additions and 6 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
}
},
"dependencies": {
"@egjs/react-grid": "^1.16.0",
"@tanstack/react-query": "^5.17.12",
"@tanstack/react-query-devtools": "^5.17.12",
"@vanilla-extract/dynamic": "^2.1.0",
"@vanilla-extract/integration": "^6.2.4",
"@vanilla-extract/next-plugin": "^2.3.2",
"@yaireo/tagify": "^4.19.0",
Expand Down
3 changes: 3 additions & 0 deletions public/icons/arrow_left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/arrow_up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/lock_alt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/setting.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/app/[userNickname]/_components/Action.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { style } from '@vanilla-extract/css';

export const button = style({
padding: '0.8rem 1.2rem',

backgroundColor: 'var(--Blue, #0047FF)',
borderRadius: '5rem',

fontSize: '1rem',
fontWeight: '600',
color: '#fff',
});
27 changes: 27 additions & 0 deletions src/app/[userNickname]/_components/Action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

/**
TODO
- [ ] 상태(팔로우, 언팔로우)에 따른 팔로우 버튼 UI
- [ ] 조건(비회원, 회원)에 따른 팔로우 버튼 동작(api 연동)
*/

import * as styles from './Action.css';

interface ActionProps {
isFollowed: boolean;
}

export default function Action({ isFollowed }: ActionProps) {
const label = isFollowed ? '팔로우' : '팔로우 취소';

const handleFollowUser = () => {
// 1. follow 하는 api 요청 + update
};

return (
<button className={styles.button} onClick={handleFollowUser}>
{label}
</button>
);
}
51 changes: 51 additions & 0 deletions src/app/[userNickname]/_components/Card.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { style, createVar } from '@vanilla-extract/css';

export const listColor = createVar();

export const container = style({
width: '185px',
padding: '3rem 1.2rem',

borderRadius: '1.5rem',
backgroundColor: listColor,
});

export const title = style({
padding: '1.1rem',

fontSize: '1.7rem',
fontWeight: '600',
color: 'var(--text-text-grey-dark, #202020)',
textAlign: 'right',
letterSpacing: '-0.51px',
wordBreak: 'keep-all',
});

export const list = style({
padding: '1rem 0',

display: 'flex',
flexDirection: 'column',

fontSize: '1.2rem',
fontWeight: '400',
color: 'var(--text-text-grey-dark, #202020)',
lineHeight: '2.5rem',
letterSpacing: '-0.36px',
});

export const lockIcon = style({
padding: '0 1rem',

display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
gap: '2px',
});

export const lockText = style({
fontSize: '1.1rem',
fontWeight: '400',
letterSpacing: '-0.33px',
color: '#AFB1B6',
});
43 changes: 43 additions & 0 deletions src/app/[userNickname]/_components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
TODO
- [ ] 다른 사람 피드볼때, 비공개 리스트는 보여지지 않음
- [ ] svg 아이콘 컴포넌트화
*/

import { ListType } from '../mockData/mockDataTypes'; // 삭제 예정
import { assignInlineVars } from '@vanilla-extract/dynamic';
import * as styles from './Card.css';

import CardItem from './CardItem';
import LockIcon from '/public/icons/lock_alt.svg';

interface CardProps {
list: ListType;
isOwner: boolean;
}

export default function Card({ list, isOwner }: CardProps) {
const isVisibleLockIcon = isOwner && !list.isPublic;

return (
<ul
className={styles.container}
style={assignInlineVars({
[styles.listColor]: `${list.backgroundColor}`,
})}
>
{isVisibleLockIcon && (
<div className={styles.lockIcon}>
<span className={styles.lockText}>비공개</span>
<LockIcon alt="비공개 리스트 표시" />
</div>
)}
<h2 className={styles.title}>{list.title}</h2>
<ul className={styles.list}>
{list.items.map((item) => (
<CardItem key={item.id} item={item} />
))}
</ul>
</ul>
);
}
6 changes: 6 additions & 0 deletions src/app/[userNickname]/_components/CardItem.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { style } from '@vanilla-extract/css';

export const container = style({
display: 'flex',
gap: '5px',
});
16 changes: 16 additions & 0 deletions src/app/[userNickname]/_components/CardItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ItemType } from '../mockData/mockDataTypes'; // 삭제 예정

import * as styles from './CardItem.css';

interface CardItemProps {
item: ItemType;
}

export default function CardItem({ item }: CardItemProps) {
return (
<li className={styles.container}>
<span>{item.rank}&#46;</span>
<span>{item.title}</span>
</li>
);
}
35 changes: 35 additions & 0 deletions src/app/[userNickname]/_components/Categories.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { style } from '@vanilla-extract/css';

export const container = style({
padding: '2.1rem 0 1.5rem 1.5rem',

display: 'flex',
alignItems: 'flex-start',
gap: '1.2rem',

overflow: 'scroll',
msOverflowStyle: 'none',
'::-webkit-scrollbar': {
display: 'none',
},
});

export const button = style({
padding: '0.8rem 1.2rem',

backgroundColor: '#FFF',
borderRadius: '5rem',
border: '1px solid #DEDEDE',

fontSize: '1.6rem',
fontWeight: '500',
color: '#828282',
letterSpacing: '-0.48px',
whiteSpace: 'nowrap',
});

export const variant = style({
backgroundColor: '#0047FF',
color: '#FFF',
border: 'none',
});
52 changes: 52 additions & 0 deletions src/app/[userNickname]/_components/Categories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client';

/**
TODO
- [ ] api 연동
- [ ] 클릭했을때 로직 (상위요소에 핸들러 고민) (리팩토링)
*/
import { KINDS } from '../mockData/categories'; // 삭제 예정

import { useState } from 'react';
// import { useQuery } from '@tanstack/react-query'; // 주석 import 나중에 사용 예정

import * as styles from './Categories.css';

// import { getCategories } from '@/app/_api/getCategories';
// import { CategoriesType } from '@/lib/types/categoriesType';
// import { queryKeys } from '@/lib/constants/queryKeys';

interface CategoriesProps {
onClick: (kind: string) => void;
}

const DEFAULT_CATEGORY = '전체'; // 나중에 constants 파일로 분리

export default function Categories({ onClick }: CategoriesProps) {
const [selected, setSelected] = useState(DEFAULT_CATEGORY);

// 1. 카테고리 api 요청
// const { data } = useQuery<CategoriesType>({
// queryKey: [queryKeys.getCategories],
// queryFn: getCategories,
// });

const handleChangeCategory = (kind: string) => () => {
onClick(kind);
setSelected(kind);
};

return (
<div className={styles.container}>
{KINDS.map((kind) => (
<button
key={kind.codeValue}
onClick={handleChangeCategory(kind.korNameValue)}
className={`${styles.button} ${kind.korNameValue === selected ? styles.variant : ''}`}
>
{kind.korNameValue}
</button>
))}
</div>
);
}
59 changes: 59 additions & 0 deletions src/app/[userNickname]/_components/Content.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { style } from '@vanilla-extract/css';

export const container = style({
width: '100%',
marginTop: '40rem',

position: 'absolute',
top: 0,

backgroundColor: '#FFF',
borderTopLeftRadius: '2.5rem',
borderTopRightRadius: '2.5rem',
});

export const options = style({
height: '6.4rem',
display: 'flex',
borderBottom: '1px solid rgba(0, 0, 0, 0.10)',
});

export const link = style({
flexGrow: '1',
});

export const button = style({
width: '100%',
height: '100%',

backgroundColor: 'white',
borderTop: '1px solid rgba(0, 0, 0, 0.25)',
borderBottom: '1px solid rgba(0, 0, 0, 0.10)',

fontSize: '1.6rem',
fontWeight: '500',
});

export const leftButton = style([
button,
{
paddingLeft: '5.75rem',
borderTopLeftRadius: '2.5rem',
},
]);

export const rightButton = style([
button,
{
paddingRight: '5.75rem',
borderTopRightRadius: '2.5rem',
},
]);

export const variant = style({
borderBottom: '1px solid #0047FF',
});

export const cards = style({
padding: '2.1rem',
});
54 changes: 54 additions & 0 deletions src/app/[userNickname]/_components/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client';

/**
TODO
- [ ] api 연동
- [ ] 무한스크롤 적용
- [ ] 피드페이지 스켈레톤 ui 적용
*/

import Link from 'next/link';
import { MasonryGrid } from '@egjs/react-grid';

import * as styles from './Content.css';

import { ListType, UserType } from '../mockData/mockDataTypes'; // 삭제 예정
import { LISTS_ME } from '../mockData/lists'; // 삭제 예정

import Card from './Card';
import Categories from './Categories';

interface ContentProps {
user: UserType;
type: string;
}

export default function Content({ user, type }: ContentProps) {
// 1. props로 받아온 userId, type으로 피드 정보 가져오는 api 요청

const handleFetchListsOnCategory = (kind: string) => {
// console.log(type, kind); // 삭제 예정
// 2. userId, type, category로 피드 정보 가져오는 api 요청
};

return (
<div className={styles.container}>
<div className={styles.options}>
<Link href={`/${user.nickname}/mylist`} className={styles.link}>
<button className={`${styles.leftButton} ${type === 'my' ? styles.variant : ''}`}>마이 리스트</button>
</Link>
<Link href={`/${user.nickname}/collabolist`} className={styles.link}>
<button className={`${styles.rightButton} ${type === 'collabo' ? styles.variant : ''}`}>콜라보 리스트</button>
</Link>
</div>
<Categories onClick={handleFetchListsOnCategory} />
<div className={styles.cards}>
<MasonryGrid gap={16} defaultDirection={'end'} align={'start'}>
{LISTS_ME.map((list: ListType) => (
<Card key={list.listId} list={list} isOwner={user.isOwner} />
))}
</MasonryGrid>
</div>
</div>
);
}
Loading

0 comments on commit 6a517f1

Please sign in to comment.