diff --git a/.github/workflows/Dev-CD.yml b/.github/workflows/Dev-CD.yml index 861b653a..479af180 100644 --- a/.github/workflows/Dev-CD.yml +++ b/.github/workflows/Dev-CD.yml @@ -2,7 +2,7 @@ name: FrontEnd Dev CD on: push: - branches: [ "dev" ] + branches: ['dev'] workflow_dispatch: jobs: @@ -11,7 +11,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -28,3 +28,4 @@ jobs: bucket: ${{ secrets.DEV_S3_BUCKET_NAME }} bucket-region: ${{ secrets.AWS_DEFAULT_REGION }} dist-id: ${{ secrets.DEV_CLOUDFRONT_ID }} + delete-removed: true diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} diff --git a/next.config.js b/next.config.js index 1e428737..f06257b6 100644 --- a/next.config.js +++ b/next.config.js @@ -2,7 +2,6 @@ const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin'); const withVanillaExtract = createVanillaExtractPlugin(); /** @type {import('next').NextConfig} */ - const nextConfig = { webpack(config) { config.module.rules.push({ diff --git a/package.json b/package.json index ed554685..605f0417 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,19 @@ } }, "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", "axios": "^1.6.5", "next": "14.0.4", "react": "^18", + "react-beautiful-dnd": "^13.1.1", "react-dom": "^18", + "react-hook-form": "^7.50.0", "react-scripts": "^5.0.1", "zustand": "^4.4.7" }, @@ -39,6 +43,7 @@ "@svgr/webpack": "^8.1.0", "@commitlint/cli": "^18.6.0", "@commitlint/config-conventional": "^18.6.0", + "@svgr/webpack": "^8.1.0", "@testing-library/jest-dom": "^6.2.0", "@testing-library/react": "^14.1.2", "@testing-library/react-hooks": "^8.0.1", @@ -47,6 +52,7 @@ "@types/jest": "^29.5.11", "@types/node": "^20", "@types/react": "^18", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.0.4", diff --git a/public/icons/add.svg b/public/icons/add.svg new file mode 100644 index 00000000..35b07a43 --- /dev/null +++ b/public/icons/add.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/arrow_left.svg b/public/icons/arrow_left.svg new file mode 100644 index 00000000..8f212969 --- /dev/null +++ b/public/icons/arrow_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/arrow_up.svg b/public/icons/arrow_up.svg new file mode 100644 index 00000000..a2f043aa --- /dev/null +++ b/public/icons/arrow_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/attach_image.svg b/public/icons/attach_image.svg new file mode 100644 index 00000000..38cdc43e --- /dev/null +++ b/public/icons/attach_image.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/back.svg b/public/icons/back.svg index 8b25e262..61596fba 100644 --- a/public/icons/back.svg +++ b/public/icons/back.svg @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= + +>>>>>>> 6a517f1030ec36d504181cba3c219bbbfd70f47f diff --git a/public/icons/clear_x_black.svg b/public/icons/clear_x_black.svg new file mode 100644 index 00000000..ae632feb --- /dev/null +++ b/public/icons/clear_x_black.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/clear_x_gray.svg b/public/icons/clear_x_gray.svg new file mode 100644 index 00000000..1a896c83 --- /dev/null +++ b/public/icons/clear_x_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/close_button.svg b/public/icons/close_button.svg new file mode 100644 index 00000000..80319e7d --- /dev/null +++ b/public/icons/close_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/default_profile.svg b/public/icons/default_profile.svg new file mode 100644 index 00000000..6322bad5 --- /dev/null +++ b/public/icons/default_profile.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/icons/dnd.svg b/public/icons/dnd.svg new file mode 100644 index 00000000..6f03a70f --- /dev/null +++ b/public/icons/dnd.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/link.svg b/public/icons/link.svg new file mode 100644 index 00000000..a10401e5 --- /dev/null +++ b/public/icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/lock_alt.svg b/public/icons/lock_alt.svg new file mode 100644 index 00000000..231ca711 --- /dev/null +++ b/public/icons/lock_alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/plus.svg b/public/icons/plus.svg index 3d8edea2..ab8905f6 100644 --- a/public/icons/plus.svg +++ b/public/icons/plus.svg @@ -1,3 +1,8 @@ +<<<<<<< HEAD +======= + + +>>>>>>> 6a517f1030ec36d504181cba3c219bbbfd70f47f diff --git a/public/icons/search.svg b/public/icons/search.svg new file mode 100644 index 00000000..d244da79 --- /dev/null +++ b/public/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/setting.svg b/public/icons/setting.svg new file mode 100644 index 00000000..b517b015 --- /dev/null +++ b/public/icons/setting.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/x_circle_fill.svg b/public/icons/x_circle_fill.svg new file mode 100644 index 00000000..7cfdd245 --- /dev/null +++ b/public/icons/x_circle_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/init.ts b/public/images/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/public/images/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/public/images/mock_profile.png b/public/images/mock_profile.png new file mode 100644 index 00000000..a435695d Binary files /dev/null and b/public/images/mock_profile.png differ diff --git a/src/app/(BeforeLogin)/_components/init.ts b/src/app/(BeforeLogin)/_components/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/src/app/(BeforeLogin)/_components/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/src/app/(BeforeLogin)/login/_components/init.ts b/src/app/(BeforeLogin)/login/_components/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/src/app/(BeforeLogin)/login/_components/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/src/app/(BeforeLogin)/login/page.tsx b/src/app/(BeforeLogin)/login/page.tsx deleted file mode 100644 index e0e5ebeb..00000000 --- a/src/app/(BeforeLogin)/login/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Login() { - return
로그인페이지
; -} diff --git a/src/app/[userNickname]/_components/Action.css.ts b/src/app/[userNickname]/_components/Action.css.ts new file mode 100644 index 00000000..fad5fe4f --- /dev/null +++ b/src/app/[userNickname]/_components/Action.css.ts @@ -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', +}); diff --git a/src/app/[userNickname]/_components/Action.tsx b/src/app/[userNickname]/_components/Action.tsx new file mode 100644 index 00000000..5680f586 --- /dev/null +++ b/src/app/[userNickname]/_components/Action.tsx @@ -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 ( + + ); +} diff --git a/src/app/[userNickname]/_components/Card.css.ts b/src/app/[userNickname]/_components/Card.css.ts new file mode 100644 index 00000000..0c685b9f --- /dev/null +++ b/src/app/[userNickname]/_components/Card.css.ts @@ -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', +}); diff --git a/src/app/[userNickname]/_components/Card.tsx b/src/app/[userNickname]/_components/Card.tsx new file mode 100644 index 00000000..8869df5d --- /dev/null +++ b/src/app/[userNickname]/_components/Card.tsx @@ -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 ( + + ); +} diff --git a/src/app/[userNickname]/_components/CardItem.css.ts b/src/app/[userNickname]/_components/CardItem.css.ts new file mode 100644 index 00000000..a2d4862e --- /dev/null +++ b/src/app/[userNickname]/_components/CardItem.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; + +export const container = style({ + display: 'flex', + gap: '5px', +}); diff --git a/src/app/[userNickname]/_components/CardItem.tsx b/src/app/[userNickname]/_components/CardItem.tsx new file mode 100644 index 00000000..a544f108 --- /dev/null +++ b/src/app/[userNickname]/_components/CardItem.tsx @@ -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 ( +
  • + {item.rank}. + {item.title} +
  • + ); +} diff --git a/src/app/[userNickname]/_components/Categories.css.ts b/src/app/[userNickname]/_components/Categories.css.ts new file mode 100644 index 00000000..8d07fdd1 --- /dev/null +++ b/src/app/[userNickname]/_components/Categories.css.ts @@ -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', +}); diff --git a/src/app/[userNickname]/_components/Categories.tsx b/src/app/[userNickname]/_components/Categories.tsx new file mode 100644 index 00000000..ba97cb20 --- /dev/null +++ b/src/app/[userNickname]/_components/Categories.tsx @@ -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({ + // queryKey: [queryKeys.getCategories], + // queryFn: getCategories, + // }); + + const handleChangeCategory = (kind: string) => () => { + onClick(kind); + setSelected(kind); + }; + + return ( +
    + {KINDS.map((kind) => ( + + ))} +
    + ); +} diff --git a/src/app/[userNickname]/_components/Content.css.ts b/src/app/[userNickname]/_components/Content.css.ts new file mode 100644 index 00000000..debc53eb --- /dev/null +++ b/src/app/[userNickname]/_components/Content.css.ts @@ -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', +}); diff --git a/src/app/[userNickname]/_components/Content.tsx b/src/app/[userNickname]/_components/Content.tsx new file mode 100644 index 00000000..342df24b --- /dev/null +++ b/src/app/[userNickname]/_components/Content.tsx @@ -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 ( +
    +
    + + + + + + +
    + +
    + + {LISTS_ME.map((list: ListType) => ( + + ))} + +
    +
    + ); +} diff --git a/src/app/[userNickname]/_components/Profile.css.ts b/src/app/[userNickname]/_components/Profile.css.ts new file mode 100644 index 00000000..f08e6cc0 --- /dev/null +++ b/src/app/[userNickname]/_components/Profile.css.ts @@ -0,0 +1,104 @@ +import { style, createVar } from '@vanilla-extract/css'; + +export const imageUrl = createVar(); + +export const container = style({ + padding: '1.9rem 1.9rem 6.4rem 1.5rem', + height: '464px', + + position: 'fixed', + top: 0, + + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + gap: '2.1rem', + + backgroundImage: imageUrl, + backgroundSize: 'cover', + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', +}); + +export const header = style({ + paddingTop: '2.5rem', + display: 'flex', + justifyContent: 'space-between', +}); + +export const profileContainer = style({ + padding: '0 2.9rem 0 3.3rem', + + display: 'flex', + flexDirection: 'column', + gap: '1.2rem', +}); + +export const icon = style({ + cursor: 'pointer', +}); + +export const profile = style({ + display: 'flex', + alignItems: 'center', + gap: '1.6rem', +}); + +export const avatar = style({ + borderRadius: '50%', + border: '2px solid #FFF', +}); + +export const info = style({ + display: 'flex', + flexDirection: 'column', + gap: '0.8rem', +}); + +export const user = style({ + display: 'flex', + alignItems: 'center', + gap: '1.2rem', +}); + +export const nickName = style({ + fontSize: '2rem', + fontWeight: ' 700', + color: '#202020', + letterSpacing: '-0.6px', +}); + +export const follow = style({ + display: 'flex', + gap: '1.6rem', + lineHeight: '2.5rem', +}); + +export const text = style({ + display: 'flex', + alignItems: 'center', + gap: '0.5rem', + + fontSize: '1rem', + fontWeight: '500', + letterSpacing: '-0.3px', +}); + +export const count = style({ + fontSize: '1.3rem', + fontWeight: '600', + letterSpacing: '-0.39px', +}); + +export const description = style({ + paddingBottom: '1.9rem', + + width: '100%', + maxHeight: '80px', + + fontSize: '1.2rem', + fontWeight: '500', + color: '#333', + lineHeight: '1.6rem', + letterSpacing: '-0.36px', +}); diff --git a/src/app/[userNickname]/_components/Profile.tsx b/src/app/[userNickname]/_components/Profile.tsx new file mode 100644 index 00000000..5f9513ac --- /dev/null +++ b/src/app/[userNickname]/_components/Profile.tsx @@ -0,0 +1,64 @@ +/** + TODO + - [ ] 디자인 최종본으로 수정 + - [ ] 프로필 이미지, 배경 이미지 적용 + - [ ] api 연동 + */ +import { UserType } from '../mockData/mockDataTypes'; // 삭제 예정 + +import Image from 'next/image'; +import { assignInlineVars } from '@vanilla-extract/dynamic'; +import * as styles from './Profile.css'; + +import Action from './Action'; +import ArrowLeftIcon from '/public/icons/arrow_left.svg'; +import SettingIcon from '/public/icons/setting.svg'; + +interface ProfileProps { + user: UserType; +} + +export default function Profile({ user }: ProfileProps) { + return ( +
    +
    + + +
    +
    +
    + 프로필 이미지 +
    +
    + {user.nickname} + +
    +
    +
    + {user.followingCount} + 팔로잉 +
    +
    + {user.followerCount} + 팔로워 +
    +
    +
    +
    +

    {user.description}

    +
    +
    + ); +} diff --git a/src/app/[userNickname]/collabolist/page.tsx b/src/app/[userNickname]/collabolist/page.tsx new file mode 100644 index 00000000..02058891 --- /dev/null +++ b/src/app/[userNickname]/collabolist/page.tsx @@ -0,0 +1,39 @@ +/** + TODO + - [ ] user api 연동 + - [ ] 페이지 하위 컴포넌트 global css 변수로 변경 + - [ ] 반응형 UI 구현 + */ + +import '@/styles/globalStyles.css'; + +import { USER_DATA_ME } from '../mockData/user'; // 삭제 예정 + +import Profile from '../_components/Profile'; +import Content from '../_components/Content'; +import FloatingContainer from '@/components/floatingButton/FloatingContainer'; +import PlusOptionFloatingButton from '@/components/floatingButton/PlusOptionFloatingButton'; +import ArrowUpFloatingButton from '@/components/floatingButton/ArrowUpFloatingButton'; + +// 타입 사용할 때 재정의 +// interface CollaboListPageProps { +// params: { +// userNickname: number; +// }; +// userId: number; +// } + +export default function CollaboListPage() { + // 1. userId로 유저 정보 가져오는 api 요청 + + return ( +
    + + + + + + +
    + ); +} diff --git a/src/app/[userNickname]/mockData/categories.ts b/src/app/[userNickname]/mockData/categories.ts new file mode 100644 index 00000000..60b48add --- /dev/null +++ b/src/app/[userNickname]/mockData/categories.ts @@ -0,0 +1,49 @@ +// 카테고리 데이터 + +export const KINDS = [ + { + codeValue: '1', + nameValue: 'ENTIRE', + korNameValue: '전체', + }, + { + codeValue: '2', + nameValue: 'CULTURE', + korNameValue: '문화', + }, + { + codeValue: '3', + nameValue: 'LIFE', + korNameValue: '일상생활', + }, + { + codeValue: '4', + nameValue: 'PLACE', + korNameValue: '장소', + }, + { + codeValue: '5', + nameValue: 'MUSIC', + korNameValue: '음악', + }, + { + codeValue: '6', + nameValue: 'MOVIE_DRAMA', + korNameValue: '영화/드라마', + }, + { + codeValue: '7', + nameValue: 'BOOK', + korNameValue: '도서', + }, + { + codeValue: '8', + nameValue: 'ANIMAL_PLANT', + korNameValue: '동식물', + }, + { + codeValue: '9', + nameValue: 'ETC', + korNameValue: '기타', + }, +]; diff --git a/src/app/[userNickname]/mockData/items.ts b/src/app/[userNickname]/mockData/items.ts new file mode 100644 index 00000000..e6e44a45 --- /dev/null +++ b/src/app/[userNickname]/mockData/items.ts @@ -0,0 +1,283 @@ +export const ITEMS_01 = { + id: 1, + listId: 1000, + data: [ + { + id: 1, + rank: 1, + title: '프랭크버거', + comment: '', // Nullable + link: '', // Nullable + imageUrl: '', // Nullable + // items 다른 속성 추가 될 수도 있음 + }, + { + id: 2, + rank: 2, + title: '캔모아', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 3, + rank: 3, + title: '젤라띠젤라띠', + comment: '', + link: '', + imageUrl: '', + }, + ], +}; + +export const ITEMS_02 = { + id: 2, + listId: 1001, + data: [ + { + id: 1, + rank: 1, + title: '스타벅스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 2, + rank: 2, + title: '투썸플레이스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 3, + rank: 3, + title: '원두볶는 사람들', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 4, + rank: 4, + title: '할리스커피', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 5, + rank: 5, + title: '탐앤탐스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 6, + rank: 6, + title: '커스텀커피', + comment: '', + link: '', + imageUrl: '', + }, + ], +}; + +export const ITEMS_03 = { + id: 3, + listId: 1002, + data: [ + { + id: 1, + rank: 1, + title: '달러구트 꿈 백화점 1', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 2, + rank: 2, + title: '달러구트 꿈 백화점 2', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 3, + rank: 3, + title: '모순', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 4, + rank: 4, + title: '지구끝의 온실', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 5, + rank: 5, + title: '메리골드 마음 세탁소', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 6, + rank: 6, + title: '작별인사', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 7, + rank: 7, + title: '튜브', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 8, + rank: 8, + title: '아몬드', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 9, + rank: 9, + title: '백의 그림자', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 10, + rank: 10, + title: '내게 무해한 사람', + comment: '', + link: '', + imageUrl: '', + }, + ], +}; + +export const ITEMS_04 = { + id: 4, + listId: 1003, + data: [ + { + id: 1, + rank: 1, + title: '스타벅스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 2, + rank: 2, + title: '투썸플레이스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 3, + rank: 3, + title: '원두볶는 사람들', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 4, + rank: 4, + title: '할리스커피', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 5, + rank: 5, + title: '탐앤탐스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 6, + rank: 6, + title: '커스텀커피', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 7, + rank: 7, + title: '빽다방', + comment: '', + link: '', + imageUrl: '', + }, + ], +}; + +export const ITEMS_05 = { + id: 5, + listId: 1004, + data: [ + { + id: 1, + rank: 1, + title: '도라지꽃', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 2, + rank: 2, + title: '라넌큘러스', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 3, + rank: 3, + title: '해바라기', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 4, + rank: 4, + title: '튤립', + comment: '', + link: '', + imageUrl: '', + }, + { + id: 5, + rank: 5, + title: '카라', + comment: '', + link: '', + imageUrl: '', + }, + ], +}; diff --git a/src/app/[userNickname]/mockData/lists.ts b/src/app/[userNickname]/mockData/lists.ts new file mode 100644 index 00000000..78c02f3c --- /dev/null +++ b/src/app/[userNickname]/mockData/lists.ts @@ -0,0 +1,66 @@ +// 리스트 데이터 + +import { ITEMS_01, ITEMS_02, ITEMS_03, ITEMS_04, ITEMS_05 } from './items'; + +export const LISTS_ME = [ + { + listId: 1000, + category: '장소', // 코드 + title: '내 동네 미사2동 맛집은 글자수가 30자일까요 top3', + ownerId: 100, // userId + ownerNickname: '파도타기', + ownerProfileImageUrl: '', + createdDate: '', + backgroundColor: '#B7EEFF', + items: ITEMS_01.data, + isPublic: true, + }, + { + listId: 1001, + category: '장소', // 코드 + title: 'What is essential is invisible to the eye.', + ownerId: 100, // userId + ownerNickname: '파도타기', + ownerProfileImageUrl: '', + createdDate: '', + backgroundColor: '#FFF6A5', + items: ITEMS_02.data, + isPublic: false, + }, + { + listId: 1002, + category: '도서', // 코드 + title: '내 기준 지루하지 않은 소설 TOP10', + ownerId: 100, // userId + ownerNickname: '파도타기', + ownerProfileImageUrl: '', + createdDate: '', + backgroundColor: '#D0FF89B2', + items: ITEMS_03.data, + isPublic: true, + }, + { + listId: 1003, + category: '장소', // 코드 + title: '자주 가는 카페(순위가 변경 될 수도 있음!!)', + ownerId: 100, // userId + ownerNickname: '파도타기', + ownerProfileImageUrl: '', + createdDate: '', + backgroundColor: '#FFDCB2', + items: ITEMS_04.data, + isPublic: true, + }, + { + listId: 1004, + category: '동식물', // 코드 + title: '좋아하는 꽃', + ownerId: 100, // userId + ownerNickname: '파도타기', + ownerProfileImageUrl: '', + createdDate: '', + backgroundColor: '#E6C6FF', + items: ITEMS_05.data, + isPublic: false, + }, +]; diff --git a/src/app/[userNickname]/mockData/mockDataTypes.ts b/src/app/[userNickname]/mockData/mockDataTypes.ts new file mode 100644 index 00000000..753fedda --- /dev/null +++ b/src/app/[userNickname]/mockData/mockDataTypes.ts @@ -0,0 +1,42 @@ +/** + TODO + - [ ] 타입 공통파일로 분리 + - [ ] 이에 따른 타입 재점검 + */ + +// 공통 타입 + +export interface UserType { + id: number; + backgroundImageUrl?: string; // mock 데이터 기준 optional + profileImageUrl?: string; // mock 데이터 기준 optional + nickname: string; + description: string; + followerCount: number; + followingCount: number; + isFollowed: boolean; + isOwner: boolean; +} + +export interface ListType { + listId: number; + category: string; + title: string; + ownerId: number; + ownerNickname: string; + ownerProfileImageUrl?: string; // mock 데이터 기준 optional + createdDate?: string; // mock 데이터 기준 optional + backgroundColor: string; + items: ItemType[]; + isPublic: boolean; +} + +export interface ItemType { + id: number; + rank: number; + title: string; + comment?: string; + link?: string; + imageUrl?: string; + // items 다른 속성 추가 될 수도 있음 +} diff --git a/src/app/[userNickname]/mockData/user.ts b/src/app/[userNickname]/mockData/user.ts new file mode 100644 index 00000000..431665ba --- /dev/null +++ b/src/app/[userNickname]/mockData/user.ts @@ -0,0 +1,26 @@ +// 사용자 데이터 + +export const USER_DATA_ME = { + id: 100, + nickname: '파도타기', + description: + '리스트는 데이터를 순서대로 저장하는 자료 구조입니다. 파이썬에서는 대괄호([])로 표현하며, 다양한 연산을 지원합니다. 인덱스를 사용하여 요소에 접근할 수 있고, 데이터 처리에 효율적입니다. 리스트는 프로그래밍에서 중요한 개념이며, 다양한 알고리즘과 과제에 활용됩니다.', + profileImageUrl: 'https://image.utoimage.com/preview/cp872722/2022/12/202212008462_500.jpg', + backgroundImageUrl: 'https://image.utoimage.com/preview/cp872722/2022/12/202212008462_500.jpg', + followingCount: 1000, // 최대 1,000 + followerCount: 0, + isFollowed: false, // default false + isOwner: true, +}; + +export const USER_DATA_OTHER = { + id: 101, + nickname: '파도타기1', + description: '파도타기 좋아요', + profileImageUrl: '', + backgroundImageUrl: '', + followingCount: 1000, // 최대 1,000 + followerCount: 999, + isFollowed: false, // default false + isOwner: false, +}; diff --git a/src/app/[userNickname]/mylist/page.tsx b/src/app/[userNickname]/mylist/page.tsx new file mode 100644 index 00000000..d4feb866 --- /dev/null +++ b/src/app/[userNickname]/mylist/page.tsx @@ -0,0 +1,41 @@ +/** + TODO + - [ ] user api 연동 + - [ ] 페이지 하위 컴포넌트 global css 변수로 변경 + - [ ] 반응형 UI 구현 + */ + +import '@/styles/globalStyles.css'; + +import { USER_DATA_ME } from '../mockData/user'; // 삭제 예정 + +import Profile from '../_components/Profile'; +import Content from '../_components/Content'; +import FloatingContainer from '@/components/floatingButton/FloatingContainer'; +import PlusButton from '@/components/floatingButton/PlusOptionFloatingButton'; +import ArrowUpButton from '@/components/floatingButton/ArrowUpFloatingButton'; + +// 타입 사용할 때 재정의 +// interface MyListPageProps { +// params: { +// userNickname: number; +// }; +// userId: number; +// } + +export default function MyListPage() { + // console.log(params.userNickname); // 삭제 예정 + + // 1. userId로 유저 정보 가져오는 api 요청 + + return ( +
    + + + + + + +
    + ); +} diff --git a/src/app/_api/category/getCategories.ts b/src/app/_api/category/getCategories.ts new file mode 100644 index 00000000..637001e9 --- /dev/null +++ b/src/app/_api/category/getCategories.ts @@ -0,0 +1,8 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; +import { CategoriesType } from '@/lib/types/categoriesType'; + +export const getCategories = async () => { + const response = await axiosInstance.get('/categories'); + + return response.data; +}; diff --git a/src/app/_api/init.ts b/src/app/_api/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/src/app/_api/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/src/app/_api/list/createList.ts b/src/app/_api/list/createList.ts new file mode 100644 index 00000000..99d78ac3 --- /dev/null +++ b/src/app/_api/list/createList.ts @@ -0,0 +1 @@ +// 리스트 생성 api diff --git a/src/app/_api/list/deleteList.ts b/src/app/_api/list/deleteList.ts new file mode 100644 index 00000000..d0064953 --- /dev/null +++ b/src/app/_api/list/deleteList.ts @@ -0,0 +1 @@ +// 리스트 삭제 api diff --git a/src/app/_api/list/getLists.ts b/src/app/_api/list/getLists.ts new file mode 100644 index 00000000..4b7bcac9 --- /dev/null +++ b/src/app/_api/list/getLists.ts @@ -0,0 +1 @@ +// 리스트 조회 api diff --git a/src/app/create/CreateListMock.ts b/src/app/create/CreateListMock.ts new file mode 100644 index 00000000..a1a5f0dd --- /dev/null +++ b/src/app/create/CreateListMock.ts @@ -0,0 +1,71 @@ +interface UserProfileType { + id: number; + profileImageUrl: string; + nickname: string; +} + +const generateMockData = (count: number): UserProfileType[] => { + const mockData: UserProfileType[] = []; + + mockData.push({ + id: 101, + profileImageUrl: `/images/mock_profile.png`, + nickname: '안유진', + }); + + mockData.push({ + id: 102, + profileImageUrl: `/images/mock_profile.png`, + nickname: '강나현', + }); + + mockData.push({ + id: 103, + profileImageUrl: `/images/mock_profile.png`, + nickname: '민서영', + }); + + mockData.push({ + id: 104, + profileImageUrl: `/images/mock_profile.png`, + nickname: '박소현', + }); + + mockData.push({ + id: 105, + profileImageUrl: `/images/mock_profile.png`, + nickname: '강현지', + }); + + mockData.push({ + id: 106, + profileImageUrl: `/images/mock_profile.png`, + nickname: '신은서', + }); + + mockData.push({ + id: 107, + profileImageUrl: `/images/mock_profile.png`, + nickname: '동호', + }); + + mockData.push({ + id: 108, + profileImageUrl: `/images/mock_profile.png`, + nickname: 'JJUNGSU', + }); + + for (let i = 1; i <= count; i++) { + const user: UserProfileType = { + id: i, + profileImageUrl: '', + nickname: `User${i}`, + }; + + mockData.push(user); + } + + return mockData; +}; + +export default generateMockData(20); diff --git a/src/app/create/_components/AddItemButton.css.ts b/src/app/create/_components/AddItemButton.css.ts new file mode 100644 index 00000000..e28b7a6f --- /dev/null +++ b/src/app/create/_components/AddItemButton.css.ts @@ -0,0 +1,23 @@ +import { style } from '@vanilla-extract/css'; + +export const addButton = style({ + width: '100%', + height: '60px', + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '12px', + + //body1 + fontSize: '1.6rem', + fontWeight: '400', + lineHeight: '1.6rem', + letterSpacing: '-0.48px', + color: '#61646B', + + backgroundColor: '#FFF', + + border: 'solid 1px #AFB1B6 ', + borderRadius: '15px', +}); diff --git a/src/app/create/_components/AddItemButton.tsx b/src/app/create/_components/AddItemButton.tsx new file mode 100644 index 00000000..1f48d3f2 --- /dev/null +++ b/src/app/create/_components/AddItemButton.tsx @@ -0,0 +1,14 @@ +import AddIcon from '/public/icons/add.svg'; +import * as styles from './AddItemButton.css'; + +interface AddItemButton { + handleAddButtonClick: () => void; +} + +export default function AddItemButton({ handleAddButtonClick }: AddItemButton) { + return ( + + ); +} diff --git a/src/app/create/_components/CreateItem.css.ts b/src/app/create/_components/CreateItem.css.ts new file mode 100644 index 00000000..0ce41170 --- /dev/null +++ b/src/app/create/_components/CreateItem.css.ts @@ -0,0 +1,71 @@ +import { style } from '@vanilla-extract/css'; + +export const header = style({ + width: '100%', + height: '90px', + paddingLeft: '20px', + paddingRight: '20px', + + position: 'sticky', + top: '0', + left: '0', + zIndex: '10', + + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + + backgroundColor: '#fff', + + borderBottom: '1px solid rgba(0, 0, 0, 0.10)', +}); + +export const headerTitle = style({ + fontSize: '2rem', +}); + +export const headerNextButton = style({ + fontSize: '1.6rem', + backgroundColor: 'transparent', +}); + +export const headerNextButtonDisabled = style([ + headerNextButton, + { + color: '#AFB1B6', //활성화 검정, 아닐때는 회색 + }, +]); + +export const article = style({ + padding: '16px 20px 30px', +}); + +//body1 +export const label = style({ + marginBottom: '1.6rem', + + fontSize: '1.6rem', + fontWeight: '600', + letterSpacing: '-0.048rem', +}); + +export const required = style({ + marginLeft: '6px', + + fontSize: '1.6rem', + fontWeight: '500', + letterSpacing: '-0.048rem', + color: '#FF5454', +}); + +//body3 +export const description = style({ + marginBottom: '1.6rem', + + fontSize: '1.4rem', + color: '#8A8A8E', + fontWeight: '400', + lineHeight: '2.5rem', + letterSpacing: '-0.042rem', +}); diff --git a/src/app/create/_components/CreateItem.tsx b/src/app/create/_components/CreateItem.tsx new file mode 100644 index 00000000..7cd378e6 --- /dev/null +++ b/src/app/create/_components/CreateItem.tsx @@ -0,0 +1,46 @@ +import { useFormContext } from 'react-hook-form'; + +import BackIcon from '/public/icons/back.svg'; +import Items from './Items'; +import * as styles from './CreateItem.css'; + +interface CreateItemProps { + onBackClick: () => void; +} + +export default function CreateItem({ onBackClick }: CreateItemProps) { + const { + formState: { isValid }, + } = useFormContext(); + + return ( +
    +
    + +

    리스트 생성

    + +
    +
    +

    + 아이템 추가 * +

    + +

    + 최소 3개, 최대 10개까지 아이템을 추가할 수 있어요.
    + 아이템의 순서대로 순위가 정해져요. +

    + +
    +
    + ); +} diff --git a/src/app/create/_components/CreateList.css.ts b/src/app/create/_components/CreateList.css.ts new file mode 100644 index 00000000..98ef3122 --- /dev/null +++ b/src/app/create/_components/CreateList.css.ts @@ -0,0 +1,351 @@ +import { style } from '@vanilla-extract/css'; +import * as GlobalStyles from '@/styles/globalStyles.css'; + +GlobalStyles; + +export const header = style({ + width: '100%', + height: '90px', + paddingLeft: '20px', + paddingRight: '20px', + + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + + borderBottom: '1px solid rgba(0, 0, 0, 0.10)', +}); + +export const headerTitle = style({ + fontSize: '2rem', +}); + +export const headerNextButton = style({ + fontSize: '1.6rem', + color: '#8d8d8d', +}); + +export const body = style({ + width: '100vw', + padding: '37px 20px 100px', + + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + rowGap: '50px', +}); + +export const title = style({ + marginBottom: '16px', + fontSize: '1.6rem', + fontWeight: '600', +}); + +export const required = style({ + color: '#ff0000', +}); + +export const content = style({ + fontSize: '1.5rem', +}); + +export const error = style({ + margin: '10px', + + fontSize: '1.5rem', + + color: 'red', +}); + +export const listTitleContainer = style({ + position: 'relative', +}); + +export const titleInputBox = style({ + width: '100%', + padding: '11px', + + position: 'relative', + + fontSize: '1.5rem', + border: '0px', + borderBottom: '1px solid rgba(0, 0, 0, 0.10)', + outline: 'none', +}); + +export const clearButton = style({ + position: 'absolute', + top: '20px', + right: '8px', + transform: 'translateY(-50%)', + cursor: 'pointer', +}); + +export const listDescriptionContainer = style({ + position: 'relative', +}); + +export const descriptionInputBox = style({ + width: '100%', + padding: '12px', + + fontSize: '1.5rem', + + resize: 'none', + whiteSpace: 'pre-wrap', + // overflowY: 'hidden', + + border: '1px solid rgba(0, 0, 0, 0.10)', + borderRadius: '8px', + + outline: 'none', +}); + +export const dragIcon = style({ + position: 'absolute', + top: '50%', + right: '5px', + cursor: 'pointer', +}); + +export const categoryContainer = style({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + columnGap: '12px', + + overflow: 'auto', + whiteSpace: 'nowrap', + scrollbarWidth: 'none', + '::-webkit-scrollbar': { + width: '0', + }, +}); + +export const categoryInputBox = style({ + display: 'none', +}); + +export const categoryButton = style({ + width: '', + height: '40px', + padding: '8px 12px', + + fontSize: '1.6rem', + fontWeight: '600', + + backgroundColor: 'transparent', + + whiteSpace: 'nowrap', + + border: '1px solid #DEDEDE', + borderRadius: '10px', +}); + +export const categoryButtonActive = style({ + backgroundColor: '#EBF4FF', +}); + +export const labelContainer = style({ + display: 'flex', + flexDirection: 'column', +}); + +export const labelInputBox = style({ + width: '100%', + padding: '10px', + + fontSize: '1.5rem', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + outline: 'none', + cursor: 'pointer', +}); + +export const labels = style({ + marginTop: '10px', + + display: 'flex', + flexDirection: 'row', + columnGap: '5px', +}); + +export const label = style({ + width: 'fit-content', + padding: '7px', + paddingRight: '20px', + + position: 'relative', + + color: '#333333', + backgroundColor: '#EBF4FF', + + fontSize: '1.3rem', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + cursor: 'pointer', +}); + +export const labelDeleteButton = style({ + position: 'absolute', + top: '10px', + right: '5px', + + stroke: '#8E8E93', + strokeWidth: '1.5', + + cursor: 'pointer', +}); + +export const colaboContainer = style({ + position: 'relative', +}); + +export const colaboInputBox = style({ + width: '100%', + padding: '10px', + paddingLeft: '30px', + + fontSize: '1.5rem', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + outline: 'none', + cursor: 'pointer', +}); + +export const colaboDropdown = style({ + height: '152px', + marginTop: '5px', + marginBottom: '10px', + padding: '11px', + + display: 'flex', + flexDirection: 'column', + rowGap: '5px', + + borderRadius: '10px', + border: '1px solid rgba(0, 0, 0, 0.1)', + + overflowY: 'auto', +}); + +export const colaboProfileContainer = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + columnGap: '16px', + + fontSize: '1.5rem', +}); + +export const colaboList = style({ + padding: '4.5px', + + display: 'flex', + flexDirection: 'column', + rowGap: '5px', +}); + +export const colaboItem = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', +}); + +export const colaboPlaceholder = style({ + fontSize: '1.5rem', + color: '#61646B', +}); + +export const colaboProfileImage = style({ + borderRadius: '50%', +}); + +export const searchIcon = style({ + width: '15.7px', + height: '15.7px', + + position: 'absolute', + top: '20px', + left: '8px', + transform: 'translateY(-50%)', +}); + +export const backgroundContainer = style({ + display: 'flex', + flexDirection: 'row', + columnGap: '12px', +}); + +export const colorCircle = style({ + width: '50px', + height: '50px', + + appearance: 'none', + MozAppearance: 'none', + WebkitAppearance: 'none', + outline: 'none', + + accentColor: 'red', + backgroundColor: '#ffffff', + + border: '3px #ffffff solid', + WebkitBorderBefore: '3px #ffffff solid', + borderRadius: '50%', + WebkitBorderRadius: '50%', + MozBorderRadius: '50%', + boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', + + cursor: 'pointer', +}); + +export const checkedColor = style({ + borderColor: '#0047FF', +}); + +export const white = style({ + backgroundColor: '#FFFFFF', +}); +export const yellow = style({ + backgroundColor: '#FFF6A5', +}); +export const orange = style({ + backgroundColor: '#FFDCB2', +}); +export const green = style({ + backgroundColor: '#D0FF89', +}); +export const blue = style({ + backgroundColor: '#B7EEFF', +}); +export const purple = style({ + backgroundColor: '#E6C6FF', +}); + +export const publicContainer = style({ + marginBottom: '8px', + + display: 'flex', + flexDirection: 'row', + columnGap: '16px', + + accentColor: 'black', +}); + +export const publicMessage = style({ + marginLeft: '5px', + + fontSize: '1.4rem', + color: '#909090', +}); + +export const checkedIcon = style({ + marginLeft: '5px', + color: '#008000', +}); diff --git a/src/app/create/_components/CreateList.tsx b/src/app/create/_components/CreateList.tsx new file mode 100644 index 00000000..8e80d36c --- /dev/null +++ b/src/app/create/_components/CreateList.tsx @@ -0,0 +1,428 @@ +'use client'; + +import React, { useEffect, useRef, useState } from 'react'; +import Link from 'next/link'; +import { useFormContext, useWatch } from 'react-hook-form'; + +import '@/styles/globalStyles.css'; +import * as styles from './CreateList.css'; + +import CloseButton from '/public/icons/close_button.svg'; +import EraseButton from '/public/icons/x_circle_fill.svg'; +import SearchIcon from '/public/icons/search.svg'; +import DefaultProfile from '/public/icons/default_profile.svg'; + +import mockdata from '../CreateListMock'; +import Image from 'next/image'; + +interface UserProfileType { + id: number; + profileImageUrl: string; + nickname: string; +} + +function CreateList({ onNextClick }: { onNextClick: () => void }) { + const { register, getValues, setValue, setError, control, formState } = useFormContext(); + const { errors, isValid } = formState; + + const category = useWatch({ control, name: 'category' }); + const labels = useWatch({ control, name: 'labels' }); + const colaboIDs = useWatch({ control, name: 'collaboratorIds' }); + const backgroundColor = useWatch({ control, name: 'backgroundColor' }); + const isPublic = useWatch({ control, name: 'isPublic' }); + + const [labelInput, setLabelInput] = useState(''); + const [colaboInput, setColaboInput] = useState(''); + const [colaboList, setColabolist] = useState([]); + const [isDropDownOpen, setIsDropDownOpen] = useState(false); + + const colaboInputRef = useRef(null); + const dropdownRef = useRef(null); + + useEffect(() => { + const closeDropdown = (event: MouseEvent) => { + if ( + dropdownRef.current && + colaboInputRef.current && + !dropdownRef.current.contains(event.target as Node) && + !colaboInputRef.current.contains(event.target as Node) + ) { + setIsDropDownOpen(false); + } + }; + document.addEventListener('click', closeDropdown); + + return () => { + document.removeEventListener('click', closeDropdown); + }; + }, []); + + return ( +
    + {/* 헤더 */} +
    + +

    리스트 생성

    + +
    + +
    + {/* 제목 */} +
    +
    + 타이틀 * +
    +
    +
    + + { + setValue('title', ''); + }} + /> + {errors.title &&
    {errors.title.message?.toString()}
    } +
    +
    +
    + + {/* 한 줄 소개 */} +
    +
    소개
    +
    +
    +