From f9bce9c69233daf3ed4e6924f9f6223d01d0ecf3 Mon Sep 17 00:00:00 2001 From: yurim Date: Sat, 30 Aug 2025 13:13:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20new-home=EC=9D=84=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=ED=99=88(/timline)=EC=9C=BC=EB=A1=9C=20=EB=91=90=EA=B3=A0,=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=ED=99=88=EC=9D=80=20legacy-timeline=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fundamentals/today-i-learned/src/App.tsx | 6 +- .../features/discussions/PostCard.tsx | 4 +- .../src/components/shared/layout/Layout.tsx | 2 +- .../src/components/shared/ui/Avatar.tsx | 2 +- .../src/components/shared/ui/Badge.tsx | 2 +- .../src/components/shared/ui/Button.tsx | 2 +- .../src/components/shared/ui/Card.tsx | 2 +- .../src/components/shared/ui/Input.tsx | 2 +- .../src/components/shared/ui/Select.tsx | 9 +- .../src/components/shared/ui/Skeleton.tsx | 9 +- .../src/{lib/utils => libs}/cn.ts | 0 .../pages/legacy-timeline/TimelinePage.tsx | 66 ++++++ .../components/CategoryTabs.tsx | 0 .../components/CreatePost.tsx | 0 .../components/CreatePostForm.tsx | 0 .../legacy-timeline/components/PostList.tsx | 115 ++++++++++ .../src/pages/newHome/components/PostList.tsx | 117 ---------- .../src/pages/newHome/index.tsx | 179 ---------------- .../pages/postDetail/components/Comment.tsx | 45 ++-- .../postDetail/components/CommentInput.tsx | 14 +- .../postDetail/components/CommentList.tsx | 4 +- .../postDetail/components/PostActions.tsx | 12 +- .../postDetail/components/PostContent.tsx | 10 +- .../postDetail/components/PostHeader.tsx | 28 ++- .../pages/postDetail/hooks/usePostDetail.ts | 105 ++++----- .../src/pages/{newHome => timeline}/README.md | 0 .../src/pages/timeline/TimelinePage.tsx | 199 ++++++++++++++---- .../components/FilterSection.tsx | 0 .../components/MonthlyChallenge.tsx | 18 +- .../components/NewHomeHeader.tsx | 0 .../components/PostInput.tsx | 0 .../pages/timeline/components/PostList.tsx | 114 +++++----- .../components/PostMoreMenu.tsx | 0 .../components/SprintChallenge.tsx | 0 .../hooks/useWritePostModal.tsx | 0 .../{newHome => timeline}/utils/types.ts | 0 36 files changed, 537 insertions(+), 529 deletions(-) rename fundamentals/today-i-learned/src/{lib/utils => libs}/cn.ts (100%) create mode 100644 fundamentals/today-i-learned/src/pages/legacy-timeline/TimelinePage.tsx rename fundamentals/today-i-learned/src/pages/{timeline => legacy-timeline}/components/CategoryTabs.tsx (100%) rename fundamentals/today-i-learned/src/pages/{timeline => legacy-timeline}/components/CreatePost.tsx (100%) rename fundamentals/today-i-learned/src/pages/{timeline => legacy-timeline}/components/CreatePostForm.tsx (100%) create mode 100644 fundamentals/today-i-learned/src/pages/legacy-timeline/components/PostList.tsx delete mode 100644 fundamentals/today-i-learned/src/pages/newHome/components/PostList.tsx delete mode 100644 fundamentals/today-i-learned/src/pages/newHome/index.tsx rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/README.md (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/components/FilterSection.tsx (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/components/MonthlyChallenge.tsx (91%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/components/NewHomeHeader.tsx (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/components/PostInput.tsx (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/components/PostMoreMenu.tsx (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/components/SprintChallenge.tsx (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/hooks/useWritePostModal.tsx (100%) rename fundamentals/today-i-learned/src/pages/{newHome => timeline}/utils/types.ts (100%) diff --git a/fundamentals/today-i-learned/src/App.tsx b/fundamentals/today-i-learned/src/App.tsx index 48ba7f64..766ff572 100644 --- a/fundamentals/today-i-learned/src/App.tsx +++ b/fundamentals/today-i-learned/src/App.tsx @@ -1,9 +1,9 @@ import { Route, Routes } from "react-router-dom"; import { Layout } from "./components/shared/layout/Layout"; -import { NewHomePage } from "./pages/newHome"; +import { LegacyTimelinePage } from "./pages/legacy-timeline/TimelinePage"; +import { PostDetailPage } from "./pages/postDetail/PostDetailPage"; import { MyPage } from "./pages/profile/MyPage"; import { TimelinePage } from "./pages/timeline/TimelinePage"; -import { PostDetailPage } from "./pages/postDetail/PostDetailPage"; function App() { return ( @@ -11,7 +11,7 @@ function App() { } /> } /> - } /> + } /> } /> diff --git a/fundamentals/today-i-learned/src/components/features/discussions/PostCard.tsx b/fundamentals/today-i-learned/src/components/features/discussions/PostCard.tsx index 43d4766d..e8d05c2d 100644 --- a/fundamentals/today-i-learned/src/components/features/discussions/PostCard.tsx +++ b/fundamentals/today-i-learned/src/components/features/discussions/PostCard.tsx @@ -2,8 +2,8 @@ import { Heart, MessageCircle, ChevronUp } from "lucide-react"; import { useState } from "react"; import { Avatar } from "@/components/shared/ui/Avatar"; import { Card } from "@/components/shared/ui/Card"; -import { useWritePostModal } from "../../../pages/newHome/hooks/useWritePostModal"; -import { PostMoreMenu } from "../../../pages/newHome/components/PostMoreMenu"; +import { useWritePostModal } from "../../../pages/timeline/hooks/useWritePostModal"; +import { PostMoreMenu } from "../../../pages/timeline/components/PostMoreMenu"; import type { GitHubDiscussion } from "@/api/remote/discussions"; import { PostDetailModal } from "@/components/features/discussions/PostDetailModal"; diff --git a/fundamentals/today-i-learned/src/components/shared/layout/Layout.tsx b/fundamentals/today-i-learned/src/components/shared/layout/Layout.tsx index c2375612..4a1ca98d 100644 --- a/fundamentals/today-i-learned/src/components/shared/layout/Layout.tsx +++ b/fundamentals/today-i-learned/src/components/shared/layout/Layout.tsx @@ -1,6 +1,6 @@ import { useLocation } from "react-router-dom"; import { OneNavigationReact } from "@shared/components"; -import { NewHomeHeader } from "@/pages/newHome/components/NewHomeHeader"; +import { NewHomeHeader } from "@/pages/timeline/components/NewHomeHeader"; export const Layout: React.FC<{ children: React.ReactNode }> = ({ children diff --git a/fundamentals/today-i-learned/src/components/shared/ui/Avatar.tsx b/fundamentals/today-i-learned/src/components/shared/ui/Avatar.tsx index d5cda22d..db6aa113 100644 --- a/fundamentals/today-i-learned/src/components/shared/ui/Avatar.tsx +++ b/fundamentals/today-i-learned/src/components/shared/ui/Avatar.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; const avatarVariants = cva( "relative inline-flex shrink-0 overflow-hidden rounded-full bg-gray-100", diff --git a/fundamentals/today-i-learned/src/components/shared/ui/Badge.tsx b/fundamentals/today-i-learned/src/components/shared/ui/Badge.tsx index bc0c229a..dbc73f27 100644 --- a/fundamentals/today-i-learned/src/components/shared/ui/Badge.tsx +++ b/fundamentals/today-i-learned/src/components/shared/ui/Badge.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; const badgeVariants = cva( "inline-flex items-center justify-center px-5 py-4 text-sm font-bold transition-colors outline-none focus:outline-none focus-visible:outline-none active:outline-none rounded-[200px] tracking-tight leading-tight", diff --git a/fundamentals/today-i-learned/src/components/shared/ui/Button.tsx b/fundamentals/today-i-learned/src/components/shared/ui/Button.tsx index 625ccb54..3d63fae9 100644 --- a/fundamentals/today-i-learned/src/components/shared/ui/Button.tsx +++ b/fundamentals/today-i-learned/src/components/shared/ui/Button.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full font-bold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", diff --git a/fundamentals/today-i-learned/src/components/shared/ui/Card.tsx b/fundamentals/today-i-learned/src/components/shared/ui/Card.tsx index 175d263f..41e05744 100644 --- a/fundamentals/today-i-learned/src/components/shared/ui/Card.tsx +++ b/fundamentals/today-i-learned/src/components/shared/ui/Card.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; const cardVariants = cva("rounded-2xl border bg-white transition-all", { variants: { diff --git a/fundamentals/today-i-learned/src/components/shared/ui/Input.tsx b/fundamentals/today-i-learned/src/components/shared/ui/Input.tsx index 517b5eab..85d06c2e 100644 --- a/fundamentals/today-i-learned/src/components/shared/ui/Input.tsx +++ b/fundamentals/today-i-learned/src/components/shared/ui/Input.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; const inputVariants = cva( "flex w-full border-0 bg-transparent text-base placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50", diff --git a/fundamentals/today-i-learned/src/components/shared/ui/Select.tsx b/fundamentals/today-i-learned/src/components/shared/ui/Select.tsx index c8855ba1..ea550df5 100644 --- a/fundamentals/today-i-learned/src/components/shared/ui/Select.tsx +++ b/fundamentals/today-i-learned/src/components/shared/ui/Select.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { ChevronDown } from "lucide-react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; const selectVariants = cva( "flex items-center justify-center gap-1.5 rounded-lg border border-black/8 bg-white text-sm font-bold transition-colors hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 tracking-tight leading-[160%]", @@ -89,7 +89,12 @@ const Select = React.forwardRef( onClick={handleToggle} {...props} > - + {displayText} { className?: string; @@ -7,11 +7,8 @@ interface SkeletonProps extends React.HTMLAttributes { export function Skeleton({ className, ...props }: SkeletonProps) { return (
); -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/lib/utils/cn.ts b/fundamentals/today-i-learned/src/libs/cn.ts similarity index 100% rename from fundamentals/today-i-learned/src/lib/utils/cn.ts rename to fundamentals/today-i-learned/src/libs/cn.ts diff --git a/fundamentals/today-i-learned/src/pages/legacy-timeline/TimelinePage.tsx b/fundamentals/today-i-learned/src/pages/legacy-timeline/TimelinePage.tsx new file mode 100644 index 00000000..1c0713e3 --- /dev/null +++ b/fundamentals/today-i-learned/src/pages/legacy-timeline/TimelinePage.tsx @@ -0,0 +1,66 @@ +import { useCallback, useState } from "react"; +import { CreatePost } from "./components/CreatePost"; +import { PostList } from "./components/PostList"; +import { MyStreak } from "../profile/components/MyStreak"; +import { CategoryTabs, TabContent } from "./components/CategoryTabs"; +import { WeeklyTop5 } from "@/components/features/discussions/WeeklyTop5"; +import type { PostCategory } from "@/types"; +import { useCreateDiscussion } from "@/api/hooks/useDiscussions"; + +export function LegacyTimelinePage() { + const [activeTab, setActiveTab] = useState("latest"); + const createDiscussionMutation = useCreateDiscussion(); + + const handleCreatePost = useCallback( + async (title: string, content: string) => { + await createDiscussionMutation.mutateAsync({ + title, + body: content + }); + }, + [createDiscussionMutation] + ); + + const handleTabChange = useCallback((tab: PostCategory) => { + setActiveTab(tab); + }, []); + + // 탭별 PostList props 조건부 계산 + const getPostListProps = () => { + switch (activeTab) { + case "latest": + return { sortBy: "latest" as const }; + case "weekly": + return { sortBy: "popularity" as const }; + case "hall-of-fame": + return { + sortBy: "latest" as const, + filterBy: { label: "성지 ⛲" } + }; + default: + return { sortBy: "latest" as const }; + } + }; + + return ( +
+
+ + + + + + +
+ +
+
+ +
+
+
+ ); +} diff --git a/fundamentals/today-i-learned/src/pages/timeline/components/CategoryTabs.tsx b/fundamentals/today-i-learned/src/pages/legacy-timeline/components/CategoryTabs.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/timeline/components/CategoryTabs.tsx rename to fundamentals/today-i-learned/src/pages/legacy-timeline/components/CategoryTabs.tsx diff --git a/fundamentals/today-i-learned/src/pages/timeline/components/CreatePost.tsx b/fundamentals/today-i-learned/src/pages/legacy-timeline/components/CreatePost.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/timeline/components/CreatePost.tsx rename to fundamentals/today-i-learned/src/pages/legacy-timeline/components/CreatePost.tsx diff --git a/fundamentals/today-i-learned/src/pages/timeline/components/CreatePostForm.tsx b/fundamentals/today-i-learned/src/pages/legacy-timeline/components/CreatePostForm.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/timeline/components/CreatePostForm.tsx rename to fundamentals/today-i-learned/src/pages/legacy-timeline/components/CreatePostForm.tsx diff --git a/fundamentals/today-i-learned/src/pages/legacy-timeline/components/PostList.tsx b/fundamentals/today-i-learned/src/pages/legacy-timeline/components/PostList.tsx new file mode 100644 index 00000000..19fc7fbb --- /dev/null +++ b/fundamentals/today-i-learned/src/pages/legacy-timeline/components/PostList.tsx @@ -0,0 +1,115 @@ +import { useCallback } from "react"; +import { PostCard } from "@/components/features/discussions/LegacyPostCard"; +import { Button } from "@/components/shared/ui/Button"; +import { LoadingSpinner } from "@/components/shared/ui/LoadingSpinner"; +import { useInfiniteDiscussions } from "@/api/hooks/useDiscussions"; +import { useIntersectionObserver } from "@/hooks/useIntersectionObserver"; + +interface PostListProps { + owner?: string; + repo?: string; + categoryName?: string; + sortBy?: "latest" | "lastActivity" | "created" | "popularity"; + filterBy?: { + label?: string; + }; +} + +export function PostList({ + owner, + repo, + categoryName, + sortBy = "latest", + filterBy +}: PostListProps) { + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + error, + refetch + } = useInfiniteDiscussions({ owner, repo, categoryName, sortBy, filterBy }); + + const handleLoadMore = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + + const { elementRef } = useIntersectionObserver({ + enabled: hasNextPage && !isFetchingNextPage, + onIntersect: handleLoadMore, + rootMargin: "300px" + }); + + const handleComment = (id: string) => { + // TODO: 댓글 페이지로 이동 + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+

+ 게시물을 불러올 수 없습니다 +

+

{error.message}

+ +
+ ); + } + + const allDiscussions = data?.pages.flatMap((page) => page.discussions) ?? []; + + if (allDiscussions.length === 0) { + return ( +
+

+ 아직 게시물이 없습니다 +

+

+ 첫 번째 Today I Learned 게시물을 작성해보세요! +

+ +
+ ); + } + + return ( +
+
+ {allDiscussions.map((discussion, index) => ( + + ))} +
+ + {hasNextPage && ( +
+ {isFetchingNextPage && ( + + )} +
+ )} +
+ ); +} diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/PostList.tsx b/fundamentals/today-i-learned/src/pages/newHome/components/PostList.tsx deleted file mode 100644 index 32e97fa8..00000000 --- a/fundamentals/today-i-learned/src/pages/newHome/components/PostList.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useCallback } from "react"; -import { - PostCard, - PostCardSkeleton -} from "../../../components/features/discussions/PostCard"; -import { useInfiniteDiscussions } from "@/api/hooks/useDiscussions"; -import { useIntersectionObserver } from "@/hooks/useIntersectionObserver"; - -interface PostListProps { - owner?: string; - repo?: string; - categoryName?: string; - sortBy?: "latest" | "lastActivity" | "created" | "popularity"; - filterBy?: { - label?: string; - }; - onLike: (postId: string) => void; - onComment: (postId: string) => void; - onUpvote: (postId: string) => void; - onDelete?: (postId: string) => void; -} - -export function PostList({ - owner, - repo, - categoryName, - sortBy = "latest", - filterBy, - onLike, - onComment, - onUpvote, - onDelete -}: PostListProps) { - const { - data: postsData, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isLoading - } = useInfiniteDiscussions({ - owner, - repo, - categoryName, - sortBy, - filterBy - }); - - const handleLoadMore = useCallback(() => { - if (hasNextPage && !isFetchingNextPage) { - fetchNextPage(); - } - }, [hasNextPage, isFetchingNextPage, fetchNextPage]); - - const { elementRef } = useIntersectionObserver({ - enabled: hasNextPage && !isFetchingNextPage, - onIntersect: handleLoadMore, - rootMargin: "300px" - }); - - const discussions = - postsData?.pages.flatMap((page) => page.discussions) || []; - if (isLoading) { - return ( -
- {[...new Array(3)].map((_, index) => ( -
- -
- ))} -
- ); - } - - if (discussions.length === 0) { - return ( -
-
-
- 📝 -
-

- 아직 포스트가 없습니다 -

-

- 첫 번째 포스트를 작성해서 오늘 배운 내용을 공유해보세요! -

-
-
- ); - } - - return ( -
- {discussions.map((discussion, index) => ( -
- -
- ))} - - {/* Load more trigger */} - {hasNextPage && ( -
- {isFetchingNextPage ? : null} -
- )} -
- ); -} diff --git a/fundamentals/today-i-learned/src/pages/newHome/index.tsx b/fundamentals/today-i-learned/src/pages/newHome/index.tsx deleted file mode 100644 index 41fb9fa0..00000000 --- a/fundamentals/today-i-learned/src/pages/newHome/index.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import * as React from "react"; -import { PostInput } from "./components/PostInput"; -import { FilterSection } from "./components/FilterSection"; -import { PostList } from "./components/PostList"; -import { WeeklyTop5 } from "@/components/features/discussions/WeeklyTop5"; -import { SprintChallenge } from "./components/SprintChallenge"; -import { useAuth } from "@/contexts/AuthContext"; -import type { SortOption } from "./utils/types"; -import { - useCreateDiscussion, - useToggleDiscussionReaction -} from "@/api/hooks/useDiscussions"; -import { useErrorHandler } from "@/hooks/useErrorHandler"; -import { useToast } from "@/components/shared/ui/Toast"; - -export function NewHomePage() { - const { user } = useAuth(); - const [sortOption, setSortOption] = React.useState("newest"); - - const createPostMutation = useCreateDiscussion(); - const { handleApiError } = useErrorHandler(); - const { success: showSuccessToast } = useToast(); - - // sortOption에 따른 API 파라미터 계산 - const getPostListProps = () => { - switch (sortOption) { - case "newest": - return { - categoryName: "Today I Learned", - sortBy: "latest" as const - }; - case "realtime": - return { - categoryName: "Today I Learned", - sortBy: "lastActivity" as const - }; - case "hall-of-fame": - return { - categoryName: "Today I Learned", - sortBy: "latest" as const, - filterBy: { label: "성지 ⛲" } - }; - default: - return { - categoryName: "Today I Learned", - sortBy: "latest" as const - }; - } - }; - - const handlePostSubmit = async (data: { title: string; content: string }) => { - try { - await createPostMutation.mutateAsync({ - title: data.title, - body: data.content - }); - showSuccessToast( - "포스트 작성 완료", - "오늘 배운 내용이 성공적으로 게시되었습니다." - ); - } catch (error) { - handleApiError(error, "포스트 작성"); - } - }; - - const handleSortChange = (option: SortOption) => { - setSortOption(option); - }; - - const toggleReactionMutation = useToggleDiscussionReaction(); - - const handleLike = async (postId: string) => { - if (!user?.accessToken) return; - - try { - await toggleReactionMutation.mutateAsync({ - subjectId: postId, - isReacted: false, // TODO: 현재 반응 상태 확인 로직 필요 - content: "HEART" - }); - } catch (error) { - handleApiError(error, "좋아요"); - } - }; - - const handleComment = (postId: string) => { - // TODO: 댓글 모달 또는 댓글 입력 영역으로 이동 - console.log("Comment on post:", postId); - }; - - const handleUpvote = async (postId: string) => { - if (!user?.accessToken) return; - - try { - await toggleReactionMutation.mutateAsync({ - subjectId: postId, - isReacted: false, // TODO: 현재 반응 상태 확인 로직 필요 - content: "THUMBS_UP" - }); - } catch (error) { - handleApiError(error, "업보트"); - } - }; - - return ( -
-
- {/* 메인 그리드 레이아웃 */} -
- {/* 왼쪽 컬럼: 메인 피드 */} -
- {/* 3일 스프린트 챌린지 */} -
- -
- - {/* 구분선 */} -
-
-
- - {/* 포스트 입력 */} -
- {user ? ( - - ) : ( -
- 로그인이 필요합니다. -
- )} -
- - {/* 구분선 */} -
-
-
- - {/* 필터 섹션 */} -
- -
- - {/* 포스트 리스트 */} -
- { - // TODO: 삭제 기능 구현 - console.log("Delete post:", postId); - }} - /> -
-
- - {/* 오른쪽 컬럼: 사이드바 (1024px 이상에서만 표시) */} -
-
- -
-
-
-
-
- ); -} diff --git a/fundamentals/today-i-learned/src/pages/postDetail/components/Comment.tsx b/fundamentals/today-i-learned/src/pages/postDetail/components/Comment.tsx index 025b2144..5aae4383 100644 --- a/fundamentals/today-i-learned/src/pages/postDetail/components/Comment.tsx +++ b/fundamentals/today-i-learned/src/pages/postDetail/components/Comment.tsx @@ -2,18 +2,18 @@ import { useState } from "react"; import { ChevronUp, MessageCircle } from "lucide-react"; import { Avatar } from "@/components/shared/ui/Avatar"; import { CommentInput } from "./CommentInput"; -import type { CommentProps } from "../../newHome/utils/types"; +import type { CommentProps } from "../../timeline/utils/types"; import { formatTimeAgo, formatNumber } from "../utils/formatters"; -function CommentContainer({ - children, - depth = 0 -}: { - children: React.ReactNode; +function CommentContainer({ + children, + depth = 0 +}: { + children: React.ReactNode; depth?: number; }) { const indentLevel = Math.min(depth, 3); const marginLeft = indentLevel * 48; - + return (
@@ -23,7 +23,7 @@ function CommentContainer({ ); } -function CommentHeader({ comment }: { comment: CommentProps['comment'] }) { +function CommentHeader({ comment }: { comment: CommentProps["comment"] }) { return (
void; }) { if (count === 0) return null; - + return (
); -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/pages/postDetail/components/CommentList.tsx b/fundamentals/today-i-learned/src/pages/postDetail/components/CommentList.tsx index b339e412..0f2409aa 100644 --- a/fundamentals/today-i-learned/src/pages/postDetail/components/CommentList.tsx +++ b/fundamentals/today-i-learned/src/pages/postDetail/components/CommentList.tsx @@ -1,5 +1,5 @@ import { Comment } from "./Comment"; -import type { CommentListProps } from "../../newHome/utils/types"; +import type { CommentListProps } from "../../timeline/utils/types"; export function CommentList({ comments, onUpvote, onReply }: CommentListProps) { if (comments.length === 0) { @@ -24,4 +24,4 @@ export function CommentList({ comments, onUpvote, onReply }: CommentListProps) { ))}
); -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/pages/postDetail/components/PostActions.tsx b/fundamentals/today-i-learned/src/pages/postDetail/components/PostActions.tsx index 9c787ecb..d26d442b 100644 --- a/fundamentals/today-i-learned/src/pages/postDetail/components/PostActions.tsx +++ b/fundamentals/today-i-learned/src/pages/postDetail/components/PostActions.tsx @@ -1,5 +1,5 @@ import { Heart, MessageCircle, Share, ChevronUp } from "lucide-react"; -import type { Post } from "../../newHome/utils/types"; +import type { Post } from "../../timeline/utils/types"; interface PostActionsProps { post: Post; @@ -19,7 +19,13 @@ function formatNumber(num: number): string { return num.toString(); } -export function PostActions({ post, onLike, onComment, onShare, onUpvote }: PostActionsProps) { +export function PostActions({ + post, + onLike, + onComment, + onShare, + onUpvote +}: PostActionsProps) { return (
); -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/pages/postDetail/components/PostContent.tsx b/fundamentals/today-i-learned/src/pages/postDetail/components/PostContent.tsx index eaf2a72d..0b986297 100644 --- a/fundamentals/today-i-learned/src/pages/postDetail/components/PostContent.tsx +++ b/fundamentals/today-i-learned/src/pages/postDetail/components/PostContent.tsx @@ -1,4 +1,4 @@ -import type { Post } from "../../newHome/utils/types"; +import type { Post } from "../../timeline/utils/types"; interface PostContentProps { post: Post; @@ -12,12 +12,10 @@ export function PostContent({ post }: PostContentProps) { return "bg-[rgba(237,204,248,0.4)] text-[#DA9BEF]"; if (tagName.includes("가독성")) return "bg-[rgba(255,212,214,0.4)] text-[#FB8890]"; - if (tagName.includes("결합도")) - return "bg-green-100 text-green-700"; + if (tagName.includes("결합도")) return "bg-green-100 text-green-700"; if (tagName.includes("기여") || tagName.includes("설계")) return "bg-orange-100 text-orange-700"; - if (tagName.includes("Changes")) - return "bg-yellow-100 text-yellow-700"; + if (tagName.includes("Changes")) return "bg-yellow-100 text-yellow-700"; if (tagName.includes("조건부") || tagName.includes("렌더링")) return "bg-blue-100 text-blue-700"; return "bg-gray-100 text-gray-600"; @@ -57,4 +55,4 @@ export function PostContent({ post }: PostContentProps) {
); -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/pages/postDetail/components/PostHeader.tsx b/fundamentals/today-i-learned/src/pages/postDetail/components/PostHeader.tsx index 00b4ce11..bcd2ee5c 100644 --- a/fundamentals/today-i-learned/src/pages/postDetail/components/PostHeader.tsx +++ b/fundamentals/today-i-learned/src/pages/postDetail/components/PostHeader.tsx @@ -2,7 +2,7 @@ import { MoreHorizontal } from "lucide-react"; import { Avatar } from "@/components/shared/ui/Avatar"; import { Button } from "@/components/shared/ui/Button"; import { formatTimeAgo } from "../utils/formatters"; -import type { Post } from "../../newHome/utils/types"; +import type { Post } from "../../timeline/utils/types"; interface PostHeaderProps { post: Post; @@ -10,27 +10,23 @@ interface PostHeaderProps { function HeaderContainer({ children }: { children: React.ReactNode }) { return ( -
- {children} -
+
{children}
); } function AuthorSection({ children }: { children: React.ReactNode }) { return ( -
- {children} -
+
{children}
); } -function AuthorInfo({ - name, - username, - createdAt -}: { - name: string; - username: string; +function AuthorInfo({ + name, + username, + createdAt +}: { + name: string; + username: string; createdAt: string; }) { return ( @@ -74,7 +70,7 @@ export function PostHeader({ post }: PostHeaderProps) { fallback={author.name} className="shrink-0" /> - } ); -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/pages/postDetail/hooks/usePostDetail.ts b/fundamentals/today-i-learned/src/pages/postDetail/hooks/usePostDetail.ts index 195c0295..d4f7cfe7 100644 --- a/fundamentals/today-i-learned/src/pages/postDetail/hooks/usePostDetail.ts +++ b/fundamentals/today-i-learned/src/pages/postDetail/hooks/usePostDetail.ts @@ -1,22 +1,25 @@ import { useState } from "react"; -import { - useDiscussionDetail, +import { + useDiscussionDetail, useWeeklyTopDiscussions, useAddDiscussionComment, useToggleDiscussionReaction } from "@/api/hooks/useDiscussions"; -import type { - GitHubDiscussionDetail, - GitHubComment +import type { + GitHubDiscussionDetail, + GitHubComment } from "@/api/remote/discussions"; -import type { Post, Comment, PopularPost } from "../../newHome/utils/types"; +import type { Post, Comment, PopularPost } from "../../timeline/utils/types"; function truncateText(text: string, maxLength: number): string { if (text.length <= maxLength) return text; return text.slice(0, maxLength) + "..."; } -function mapGitHubUserToAuthor(githubUser: { login: string; avatarUrl: string }) { +function mapGitHubUserToAuthor(githubUser: { + login: string; + avatarUrl: string; +}) { return { id: githubUser.login, name: githubUser.login, @@ -35,17 +38,18 @@ function mapGitHubCommentToComment(comment: GitHubComment): Comment { upvotes: comment.reactions.totalCount, replies: comment.replies?.totalCount || 0 }, - replies: comment.replies?.nodes?.map(reply => ({ - id: reply.id, - content: reply.body, - author: mapGitHubUserToAuthor(reply.author), - createdAt: reply.createdAt, - stats: { - upvotes: reply.reactions.totalCount, - replies: 0 - }, - parentId: comment.id - })) || [] + replies: + comment.replies?.nodes?.map((reply) => ({ + id: reply.id, + content: reply.body, + author: mapGitHubUserToAuthor(reply.author), + createdAt: reply.createdAt, + stats: { + upvotes: reply.reactions.totalCount, + replies: 0 + }, + parentId: comment.id + })) || [] }; } @@ -57,7 +61,7 @@ function mapDiscussionToPost(discussion: GitHubDiscussionDetail): Post { author: mapGitHubUserToAuthor(discussion.author), createdAt: discussion.createdAt, category: discussion.category.name, - tags: discussion.labels?.nodes?.map(label => label.name) || [], + tags: discussion.labels?.nodes?.map((label) => label.name) || [], stats: { upvotes: discussion.reactions.totalCount, hearts: 0, @@ -69,7 +73,11 @@ function mapDiscussionToPost(discussion: GitHubDiscussionDetail): Post { } export function usePostDetail(postId: string | undefined) { - const { data: discussion, isLoading, error } = useDiscussionDetail(postId || ""); + const { + data: discussion, + isLoading, + error + } = useDiscussionDetail(postId || ""); const { data: weeklyPosts } = useWeeklyTopDiscussions({ limit: 5 }); const addComment = useAddDiscussionComment(); const toggleReaction = useToggleDiscussionReaction(); @@ -81,13 +89,13 @@ export function usePostDetail(postId: string | undefined) { const handleLike = (postId: string) => { const isLiked = interactionState.likes.has(postId); - toggleReaction.mutate({ - subjectId: postId, + toggleReaction.mutate({ + subjectId: postId, isReacted: isLiked, content: "HEART" }); - - setInteractionState(prev => { + + setInteractionState((prev) => { const newLikes = new Set(prev.likes); if (newLikes.has(postId)) { newLikes.delete(postId); @@ -108,13 +116,13 @@ export function usePostDetail(postId: string | undefined) { const handleUpvote = (postId: string) => { const isUpvoted = interactionState.upvotes.has(postId); - toggleReaction.mutate({ - subjectId: postId, + toggleReaction.mutate({ + subjectId: postId, isReacted: isUpvoted, content: "THUMBS_UP" }); - - setInteractionState(prev => { + + setInteractionState((prev) => { const newUpvotes = new Set(prev.upvotes); if (newUpvotes.has(postId)) { newUpvotes.delete(postId); @@ -126,8 +134,8 @@ export function usePostDetail(postId: string | undefined) { }; const handleCommentUpvote = (commentId: string) => { - toggleReaction.mutate({ - subjectId: commentId, + toggleReaction.mutate({ + subjectId: commentId, isReacted: false, content: "THUMBS_UP" }); @@ -135,12 +143,12 @@ export function usePostDetail(postId: string | undefined) { const handleReply = (commentId: string, content: string) => { if (!discussion?.id || !content.trim()) return; - - const parentComment = comments.find(comment => comment.id === commentId); - const replyPrefix = parentComment + + const parentComment = comments.find((comment) => comment.id === commentId); + const replyPrefix = parentComment ? `> @${parentComment.author.name}: ${content}\n\n` : `> ${content}\n\n`; - + addComment.mutate({ discussionId: discussion.id, body: `${replyPrefix}답글 내용을 입력하세요.` @@ -149,7 +157,7 @@ export function usePostDetail(postId: string | undefined) { const handleNewComment = (content: string) => { if (!discussion?.id || !content.trim()) return; - + addComment.mutate({ discussionId: discussion.id, body: content @@ -157,17 +165,20 @@ export function usePostDetail(postId: string | undefined) { }; const postData = discussion ? mapDiscussionToPost(discussion) : null; - - const comments = discussion?.comments.nodes.map(mapGitHubCommentToComment) || []; + + const comments = + discussion?.comments.nodes.map(mapGitHubCommentToComment) || []; const weeklyTop5Data = { - posts: (weeklyPosts || []).map((post, index): PopularPost => ({ - id: post.id, - title: post.title, - author: mapGitHubUserToAuthor(post.author), - excerpt: truncateText(post.body.replace(/[#*`\n]/g, " ").trim(), 80), - rank: index + 1 - })), + posts: (weeklyPosts || []).map( + (post, index): PopularPost => ({ + id: post.id, + title: post.title, + author: mapGitHubUserToAuthor(post.author), + excerpt: truncateText(post.body.replace(/[#*`\n]/g, " ").trim(), 80), + rank: index + 1 + }) + ), weekInfo: "이번 주 인기글" }; @@ -175,11 +186,11 @@ export function usePostDetail(postId: string | undefined) { postId, isLoading, error, - + postData, comments, weeklyTop5Data, - + handlers: { handleLike, handleComment, @@ -190,4 +201,4 @@ export function usePostDetail(postId: string | undefined) { handleNewComment } }; -} \ No newline at end of file +} diff --git a/fundamentals/today-i-learned/src/pages/newHome/README.md b/fundamentals/today-i-learned/src/pages/timeline/README.md similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/README.md rename to fundamentals/today-i-learned/src/pages/timeline/README.md diff --git a/fundamentals/today-i-learned/src/pages/timeline/TimelinePage.tsx b/fundamentals/today-i-learned/src/pages/timeline/TimelinePage.tsx index 6eebdff1..479387f4 100644 --- a/fundamentals/today-i-learned/src/pages/timeline/TimelinePage.tsx +++ b/fundamentals/today-i-learned/src/pages/timeline/TimelinePage.tsx @@ -1,64 +1,177 @@ -import { useCallback, useState } from "react"; -import { CreatePost } from "./components/CreatePost"; +import * as React from "react"; +import { PostInput } from "./components/PostInput"; +import { FilterSection } from "./components/FilterSection"; import { PostList } from "./components/PostList"; -import { MyStreak } from "../profile/components/MyStreak"; -import { CategoryTabs, TabContent } from "./components/CategoryTabs"; import { WeeklyTop5 } from "@/components/features/discussions/WeeklyTop5"; -import type { PostCategory } from "@/types"; -import { useCreateDiscussion } from "@/api/hooks/useDiscussions"; +import { SprintChallenge } from "./components/SprintChallenge"; +import { useAuth } from "@/contexts/AuthContext"; +import type { SortOption } from "./utils/types"; +import { + useCreateDiscussion, + useToggleDiscussionReaction +} from "@/api/hooks/useDiscussions"; +import { useErrorHandler } from "@/hooks/useErrorHandler"; +import { useToast } from "@/components/shared/ui/Toast"; export function TimelinePage() { - const [activeTab, setActiveTab] = useState("latest"); - const createDiscussionMutation = useCreateDiscussion(); - - const handleCreatePost = useCallback( - async (title: string, content: string) => { - await createDiscussionMutation.mutateAsync({ - title, - body: content - }); - }, - [createDiscussionMutation] - ); + const { user } = useAuth(); + const [sortOption, setSortOption] = React.useState("newest"); - const handleTabChange = useCallback((tab: PostCategory) => { - setActiveTab(tab); - }, []); + const createPostMutation = useCreateDiscussion(); + const { handleApiError } = useErrorHandler(); + const { success: showSuccessToast } = useToast(); - // 탭별 PostList props 조건부 계산 + // sortOption에 따른 API 파라미터 계산 const getPostListProps = () => { - switch (activeTab) { - case "latest": - return { sortBy: "latest" as const }; - case "weekly": - return { sortBy: "popularity" as const }; + switch (sortOption) { + case "newest": + return { + categoryName: "Today I Learned", + sortBy: "latest" as const + }; + case "realtime": + return { + categoryName: "Today I Learned", + sortBy: "lastActivity" as const + }; case "hall-of-fame": return { + categoryName: "Today I Learned", sortBy: "latest" as const, filterBy: { label: "성지 ⛲" } }; default: - return { sortBy: "latest" as const }; + return { + categoryName: "Today I Learned", + sortBy: "latest" as const + }; + } + }; + + const handlePostSubmit = async (data: { title: string; content: string }) => { + try { + await createPostMutation.mutateAsync({ + title: data.title, + body: data.content + }); + showSuccessToast( + "포스트 작성 완료", + "오늘 배운 내용이 성공적으로 게시되었습니다." + ); + } catch (error) { + handleApiError(error, "포스트 작성"); + } + }; + + const handleSortChange = (option: SortOption) => { + setSortOption(option); + }; + + const toggleReactionMutation = useToggleDiscussionReaction(); + + const handleLike = async (postId: string) => { + if (!user?.accessToken) return; + + try { + await toggleReactionMutation.mutateAsync({ + subjectId: postId, + isReacted: false, // TODO: 현재 반응 상태 확인 로직 필요 + content: "HEART" + }); + } catch (error) { + handleApiError(error, "좋아요"); + } + }; + + const handleComment = (postId: string) => { + // TODO: 댓글 모달 또는 댓글 입력 영역으로 이동 + console.log("Comment on post:", postId); + }; + + const handleUpvote = async (postId: string) => { + if (!user?.accessToken) return; + + try { + await toggleReactionMutation.mutateAsync({ + subjectId: postId, + isReacted: false, // TODO: 현재 반응 상태 확인 로직 필요 + content: "THUMBS_UP" + }); + } catch (error) { + handleApiError(error, "업보트"); } }; return ( -
-
- - - - - - -
+
+
+ {/* 메인 그리드 레이아웃 */} +
+ {/* 왼쪽 컬럼: 메인 피드 */} +
+ {/* 3일 스프린트 챌린지 */} +
+ +
+ + {/* 구분선 */} +
+
+
+ + {/* 포스트 입력 */} +
+ {user ? ( + + ) : ( +
+ 로그인이 필요합니다. +
+ )} +
+ + {/* 구분선 */} +
+
+
+ + {/* 필터 섹션 */} +
+ +
+ + {/* 포스트 리스트 */} +
+ { + // TODO: 삭제 기능 구현 + console.log("Delete post:", postId); + }} + /> +
+
-
-
- + {/* 오른쪽 컬럼: 사이드바 (1024px 이상에서만 표시) */} +
+
+ +
+
diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/FilterSection.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/FilterSection.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/components/FilterSection.tsx rename to fundamentals/today-i-learned/src/pages/timeline/components/FilterSection.tsx diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/MonthlyChallenge.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/MonthlyChallenge.tsx similarity index 91% rename from fundamentals/today-i-learned/src/pages/newHome/components/MonthlyChallenge.tsx rename to fundamentals/today-i-learned/src/pages/timeline/components/MonthlyChallenge.tsx index 528fadbc..178f1051 100644 --- a/fundamentals/today-i-learned/src/pages/newHome/components/MonthlyChallenge.tsx +++ b/fundamentals/today-i-learned/src/pages/timeline/components/MonthlyChallenge.tsx @@ -1,5 +1,5 @@ import { Card } from "@/components/shared/ui/Card"; -import { cn } from "@/lib/utils/cn"; +import { cn } from "@/libs/cn"; import type { MonthlyChallengeProps, ChallengeDay } from "../utils/types"; const MONTH_NAMES = [ @@ -17,12 +17,7 @@ const MONTH_NAMES = [ "12월" ]; -function ChallengeDayItem({ - day -}: { - day: ChallengeDay; -}) { - +function ChallengeDayItem({ day }: { day: ChallengeDay }) { const getDayStyle = () => { switch (day.status) { case "completed": @@ -73,9 +68,7 @@ function ChallengeDayItem({ ); } -export function MonthlyChallenge({ - challenge -}: MonthlyChallengeProps) { +export function MonthlyChallenge({ challenge }: MonthlyChallengeProps) { const monthName = MONTH_NAMES[challenge.month - 1]; // 7x5 그리드로 배치 (주단위) @@ -104,10 +97,7 @@ export function MonthlyChallenge({ className="grid grid-cols-7 gap-4 justify-items-center" > {week.map((day) => ( - + ))} {/* 빈 칸 채우기 (마지막 주가 7일 미만인 경우) */} {week.length < 7 && diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/NewHomeHeader.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/NewHomeHeader.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/components/NewHomeHeader.tsx rename to fundamentals/today-i-learned/src/pages/timeline/components/NewHomeHeader.tsx diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/PostInput.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/PostInput.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/components/PostInput.tsx rename to fundamentals/today-i-learned/src/pages/timeline/components/PostInput.tsx diff --git a/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx index 19fc7fbb..32e97fa8 100644 --- a/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx +++ b/fundamentals/today-i-learned/src/pages/timeline/components/PostList.tsx @@ -1,7 +1,8 @@ import { useCallback } from "react"; -import { PostCard } from "@/components/features/discussions/LegacyPostCard"; -import { Button } from "@/components/shared/ui/Button"; -import { LoadingSpinner } from "@/components/shared/ui/LoadingSpinner"; +import { + PostCard, + PostCardSkeleton +} from "../../../components/features/discussions/PostCard"; import { useInfiniteDiscussions } from "@/api/hooks/useDiscussions"; import { useIntersectionObserver } from "@/hooks/useIntersectionObserver"; @@ -13,6 +14,10 @@ interface PostListProps { filterBy?: { label?: string; }; + onLike: (postId: string) => void; + onComment: (postId: string) => void; + onUpvote: (postId: string) => void; + onDelete?: (postId: string) => void; } export function PostList({ @@ -20,17 +25,25 @@ export function PostList({ repo, categoryName, sortBy = "latest", - filterBy + filterBy, + onLike, + onComment, + onUpvote, + onDelete }: PostListProps) { const { - data, + data: postsData, fetchNextPage, hasNextPage, isFetchingNextPage, - isLoading, - error, - refetch - } = useInfiniteDiscussions({ owner, repo, categoryName, sortBy, filterBy }); + isLoading + } = useInfiniteDiscussions({ + owner, + repo, + categoryName, + sortBy, + filterBy + }); const handleLoadMore = useCallback(() => { if (hasNextPage && !isFetchingNextPage) { @@ -44,70 +57,59 @@ export function PostList({ rootMargin: "300px" }); - const handleComment = (id: string) => { - // TODO: 댓글 페이지로 이동 - }; - + const discussions = + postsData?.pages.flatMap((page) => page.discussions) || []; if (isLoading) { return ( -
- -
- ); - } - - if (error) { - return ( -
-

- 게시물을 불러올 수 없습니다 -

-

{error.message}

- +
+ {[...new Array(3)].map((_, index) => ( +
+ +
+ ))}
); } - const allDiscussions = data?.pages.flatMap((page) => page.discussions) ?? []; - - if (allDiscussions.length === 0) { + if (discussions.length === 0) { return ( -
-

- 아직 게시물이 없습니다 -

-

- 첫 번째 Today I Learned 게시물을 작성해보세요! -

- +
+
+
+ 📝 +
+

+ 아직 포스트가 없습니다 +

+

+ 첫 번째 포스트를 작성해서 오늘 배운 내용을 공유해보세요! +

+
); } return ( -
-
- {allDiscussions.map((discussion, index) => ( +
+ {discussions.map((discussion, index) => ( +
- ))} -
+
+ ))} + {/* Load more trigger */} {hasNextPage && ( -
- {isFetchingNextPage && ( - - )} +
+ {isFetchingNextPage ? : null}
)}
diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/PostMoreMenu.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/PostMoreMenu.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/components/PostMoreMenu.tsx rename to fundamentals/today-i-learned/src/pages/timeline/components/PostMoreMenu.tsx diff --git a/fundamentals/today-i-learned/src/pages/newHome/components/SprintChallenge.tsx b/fundamentals/today-i-learned/src/pages/timeline/components/SprintChallenge.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/components/SprintChallenge.tsx rename to fundamentals/today-i-learned/src/pages/timeline/components/SprintChallenge.tsx diff --git a/fundamentals/today-i-learned/src/pages/newHome/hooks/useWritePostModal.tsx b/fundamentals/today-i-learned/src/pages/timeline/hooks/useWritePostModal.tsx similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/hooks/useWritePostModal.tsx rename to fundamentals/today-i-learned/src/pages/timeline/hooks/useWritePostModal.tsx diff --git a/fundamentals/today-i-learned/src/pages/newHome/utils/types.ts b/fundamentals/today-i-learned/src/pages/timeline/utils/types.ts similarity index 100% rename from fundamentals/today-i-learned/src/pages/newHome/utils/types.ts rename to fundamentals/today-i-learned/src/pages/timeline/utils/types.ts