diff --git a/.gitignore b/.gitignore index 8f322f0d8..45c1abce8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..b4bfed357 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/README.md b/README.md index 918365601..82eee95d4 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,20 @@ -## Sprint 9 +## Sprint 10 + +![alt text](image.png) +![alt text](image-1.png) +![alt text](image-2.png) -![Sprint 9]() **** ### 기본 사항 -- 자유 게시판 페이지 주소는 “/boards” 입니다. -- 전체 게시글에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다. -- 게시글 목록 조회 api를 사용하여 베스트 게시글, 게시글을 구현합니다. -- 게시글 title에 검색어가 일부 포함되면 검색이 됩니다 -- 베스트 상품 기준 - - 정렬 : like - - like가 높은 순 +- 상품 등록 페이지 주소는 “/addboard” 입니다. +- 게시판 이미지는 최대 한개 업로드가 가능합니다. +- 각 input의 placeholder 값을 정확히 입력해주세요. +- 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다. +- 회원가입, 로그인 api를 사용하여 받은accessToken을 사용하여 게시물 등록을 합니다. +- ‘등록’ 버튼을 누르면 게시물 상세 페이지로 이동합니다. +- 상품 상세 페이지 주소는 “/addboard/{id}” 입니다. +- 댓글 input 값을 입력하면 ‘등록' 버튼이 활성화 됩니다. +- 활성화된 ‘등록' 버튼을 누르면 댓글이 등록됩니다 ### 심화 @@ -22,5 +27,8 @@ https://panda-market-api.vercel.app/docs/#/ ### 개인적으로 도전할 것 -1. tailwind 적용 -2. 기본 페이지 SSG, 검색 결과 페이지 SSR로 구현 \ No newline at end of file +1. 기본 페이지 SSR로 수정 +2. 상품 상세 페이지 SSR로 구현 +3. react-hook-form 써보기 +4. shadcn 적용 +5. 댓글, 포스트에 인피니티 스크롤 적용 \ No newline at end of file diff --git a/components.json b/components.json new file mode 100644 index 000000000..e935ae858 --- /dev/null +++ b/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "styles/globals.css", + "baseColor": "slate", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx new file mode 100644 index 000000000..d1ef990de --- /dev/null +++ b/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/entities/bestPostCard/ui/bestPostCard.tsx b/entities/bestPostCard/ui/bestPostCard.tsx index 3d534d92e..1c3bad520 100644 --- a/entities/bestPostCard/ui/bestPostCard.tsx +++ b/entities/bestPostCard/ui/bestPostCard.tsx @@ -1,52 +1,60 @@ import { formatDate } from "@/shared/lib/formatDate"; -import { Article } from "@/shared/model"; import { ItemImage } from "@/shared/ui/itemImage"; +import { LikeCount } from "@/shared/ui/LikeCount"; import Image from "next/image"; interface Props { - article: Article; + title: string; + image: string | null; + nickname: string; + likeCount: number; + createdAt: string; + id: number; } -export function BestPostCard({ article }: Props) { +export function BestPostCard({ + title, + image, + nickname, + likeCount, + createdAt, + id, +}: Props) { return ( -
-
+
+
best Post Label -
+
medal -

+

Best

-
- {article.title} - +
+ {title} +
-
-
- {article.writer.nickname} -
- -
{article.likeCount}
-
+
+
+ {nickname} +
- {formatDate(new Date(article.createdAt))} + {formatDate(new Date(createdAt))}
diff --git a/entities/commentsCard/index.tsx b/entities/commentsCard/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/entities/commentsCard/ui/commentsCard.tsx b/entities/commentsCard/ui/commentsCard.tsx new file mode 100644 index 000000000..9a8960bc2 --- /dev/null +++ b/entities/commentsCard/ui/commentsCard.tsx @@ -0,0 +1,55 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { compareDateWithNow } from "@/shared/lib/formatDate"; +import Image from "next/image"; + +interface Props { + createdAt: string; + nickname: string; + content: string; + image: string | null; +} + +export function CommentsCard({ createdAt, nickname, content, image }: Props) { + return ( +
+
+
{content}
+ + + edit button + + + + 수정 + + + 삭제 + + + +
+
+ comments profile +
+

{nickname}

+

{compareDateWithNow(new Date(createdAt)) + " 전"}

+
+
+
+ ); +} diff --git a/entities/postCard/ui/postCard.tsx b/entities/postCard/ui/postCard.tsx index b6c63cdff..7e6983816 100644 --- a/entities/postCard/ui/postCard.tsx +++ b/entities/postCard/ui/postCard.tsx @@ -1,20 +1,32 @@ import { formatDate } from "@/shared/lib/formatDate"; -import { Article } from "@/shared/model"; import { ItemImage } from "@/shared/ui/itemImage"; +import { LikeCount } from "@/shared/ui/LikeCount"; import Image from "next/image"; interface Props { - item: Article; + title: string; + image: string | null; + nickname: string; + createdAt: string; + likeCount: number; + id: number; } -export function PostCard({ item }: Props) { +export function PostCard({ + title, + image, + createdAt, + likeCount, + nickname, + id, +}: Props) { return ( -
-
-

{item.title}

- -
-