diff --git a/apps/extension/public/icon128.png b/apps/extension/public/icon128.png new file mode 100644 index 0000000..be7a5e5 Binary files /dev/null and b/apps/extension/public/icon128.png differ diff --git a/apps/extension/src/assets/menu.svg b/apps/extension/src/assets/menu.svg new file mode 100644 index 0000000..7ff1068 --- /dev/null +++ b/apps/extension/src/assets/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/extension/src/components/loading/index.tsx b/apps/extension/src/components/loading/index.tsx index 5f90798..f8e3684 100644 --- a/apps/extension/src/components/loading/index.tsx +++ b/apps/extension/src/components/loading/index.tsx @@ -3,15 +3,19 @@ import { Spinner } from "@repo/ui"; interface LoadingProps { title: string; + description?: string; children: React.ReactNode; } -const Loading = ({ title, children }: LoadingProps) => { +const Loading = ({ title, description, children }: LoadingProps) => { const isLoading = useLoadingStore((state) => state.isLoading); return isLoading ? (
- {title} +
+

{title}

+

{description}

+
) : ( diff --git a/apps/extension/src/hooks/use-detect-path.ts b/apps/extension/src/hooks/use-detect-path.ts index 8559f83..403e97d 100644 --- a/apps/extension/src/hooks/use-detect-path.ts +++ b/apps/extension/src/hooks/use-detect-path.ts @@ -2,6 +2,7 @@ import { useEffect } from "react"; import { useStarStore } from "@/state/zustand/star"; import { useTabStore } from "@/state/zustand/tab"; +import { useUserStore } from "@/state/zustand/user"; import { updateCurrentTab } from "@/utils/chrome"; import { useReplaceNavigate } from "./use-replace-navigate"; @@ -24,6 +25,7 @@ export function useDetectPath() { const { currentTab, setCurrentTab, setIsFindingExistPath } = useTabStore(); const stars = useStarStore((state) => state.stars); + const isLoggedIn = useUserStore((state) => state.isLoggedIn); const navigate = useReplaceNavigate(); @@ -36,8 +38,8 @@ export function useDetectPath() { }; const onTabUpdated = (_: number, changeInfo: TabChangeInfoProps) => { - setIsFindingExistPath(true); - if (changeInfo.status === "complete") { + if (changeInfo.url && changeInfo.status === "loading") { + setIsFindingExistPath(true); updateCurrentTab(setCurrentTab); } }; @@ -52,6 +54,10 @@ export function useDetectPath() { }, []); useEffect(() => { + if (!isLoggedIn) { + return navigate("/"); + } + const findStar = stars?.starListDto.find((star) => encodeURI(star.siteUrl) === currentTab.url); if (findStar) { @@ -60,7 +66,7 @@ export function useDetectPath() { navigate("/bookmark"); } setIsFindingExistPath(false); - }, [stars, currentTab]); + }, [stars, currentTab, isLoggedIn]); return { currentTab }; } diff --git a/apps/extension/src/index.css b/apps/extension/src/index.css index 5516b0d..f9b2a9c 100644 --- a/apps/extension/src/index.css +++ b/apps/extension/src/index.css @@ -53,3 +53,10 @@ textarea { } outline: none; } + +button { + cursor: pointer; + &:disabled { + cursor: not-allowed; + } +} diff --git a/apps/extension/src/pages/bookmark.tsx b/apps/extension/src/pages/bookmark.tsx index ca7f8a1..fb92624 100644 --- a/apps/extension/src/pages/bookmark.tsx +++ b/apps/extension/src/pages/bookmark.tsx @@ -1,12 +1,12 @@ -import { useMemo } from "react"; +import { useState } from "react"; +import Logo from "@/assets/logo.svg?react"; +import Menu from "@/assets/menu.svg?react"; import Loading from "@/components/loading"; import { useCreateStar } from "@/state/mutation/star"; -import { useStarStore } from "@/state/zustand/star"; import { useTabStore } from "@/state/zustand/tab"; import { getHtmlText } from "@/utils/chrome"; -import { Graph2D } from "@repo/ui"; -import { RectangleButton } from "@repo/ui"; +import { Modal, RectangleButton, useOutsideClick } from "@repo/ui"; import { useNavigate } from "react-router-dom"; @@ -14,27 +14,13 @@ const url = import.meta.env.VITE_BASE_URL; const Bookmark = () => { const { currentTab, isFindingExistPath } = useTabStore(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isLogoutModalOpen, setIsLogoutModalOpen] = useState(false); + + const [menuContainerRef] = useOutsideClick(() => setIsMenuOpen(false)); const navigate = useNavigate(); const { mutateAsync } = useCreateStar(); - const stars = useStarStore((state) => state.stars); - - const graphData = useMemo(() => { - if (!stars?.starListDto || !stars?.linkListDto) return { nodes: [], links: [] }; - - return { - nodes: stars.starListDto.map((star) => ({ - id: star.starId, - name: star.title, - val: Math.min(star.views, 10), - url: star.siteUrl, - })), - links: stars.linkListDto.map((link) => ({ - source: link.linkedNodeIdList[0], - target: link.linkedNodeIdList[1], - })), - }; - }, [stars]); const onClickLogout = async () => { await chrome.cookies.remove({ @@ -65,13 +51,38 @@ const Bookmark = () => { }); }; + const onToggleMenu = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsMenuOpen((prev) => !prev); + }; + + const onOpenLogoutModal = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsMenuOpen(false); + setIsLogoutModalOpen(true); + }; + return ( - + +
+ +
+ + {isMenuOpen && ( +
+ +
+ )} +
+
-
- -

현재 페이지 정보

-

URL

@@ -82,15 +93,24 @@ const Bookmark = () => {

{currentTab.title}

-
- - {isFindingExistPath ? "기존 북마크(추가 불가)" : "북마크에 추가하기"} - - - 로그아웃 - -
+ + {isFindingExistPath ? "기존 북마크(추가 불가)" : "북마크에 추가하기"} +
+ {isLogoutModalOpen && ( + setIsLogoutModalOpen(false)} + > +
+ setIsLogoutModalOpen(false)} variation="outline"> + 취소 + + 로그아웃 +
+
+ )}
); }; diff --git a/apps/extension/src/pages/create-bookmark.tsx b/apps/extension/src/pages/create-bookmark.tsx index edd75a4..3c93a8b 100644 --- a/apps/extension/src/pages/create-bookmark.tsx +++ b/apps/extension/src/pages/create-bookmark.tsx @@ -4,15 +4,15 @@ import AISummary from "@/assets/ai-summary.svg?react"; import Logo from "@/assets/logo.svg?react"; import CardWrapper from "@/components/create-bookmark/card-wrapper"; import Loading from "@/components/loading"; +import { useReplaceNavigate } from "@/hooks/use-replace-navigate"; import { useCreateCategory } from "@/state/mutation/category"; import { useCompleteCreateStar, useUpdateStar } from "@/state/mutation/star"; import { useGetKeywords } from "@/state/query/keyword"; import { useGetStarById } from "@/state/query/star"; import { useStarStore } from "@/state/zustand/star"; -import { useTabStore } from "@/state/zustand/tab"; import { CategoryListProps } from "@/types/category"; import { CompleteSummarizeStarProps } from "@/types/star"; -import { Graph2D } from "@repo/ui"; +import { cn, Graph2D } from "@repo/ui"; import { Keyword, RectangleButton, Spinner, Textarea } from "@repo/ui"; import { Navigate, useLocation, useSearchParams } from "react-router-dom"; @@ -41,9 +41,10 @@ const CreateBookmark = () => { const [searchParams] = useSearchParams(); const id = searchParams.get("id"); + // 추후 AI 요약 기능 추가 시 tanstack query의 isPending 사용 + const [isAISummaryPending, setIsAISummaryPending] = useState(false); const [bookmark, setBookmark] = useState(Object.assign(DEFAULT_BOOKMARK, state)); const stars = useStarStore((state) => state.stars); - const setIsFindingExistPath = useTabStore((state) => state.setIsFindingExistPath); const { mutateAsync: mutateAsyncCompleteCreateStar } = useCompleteCreateStar(); const { mutateAsync: mutateAsyncUpdateStar } = useUpdateStar(); @@ -52,6 +53,8 @@ const CreateBookmark = () => { const { data: keywords } = useGetKeywords(); const { data: star, isLoading: isLoadingGetStar } = useGetStarById(id); + const navigate = useReplaceNavigate(); + useEffect(() => { if (!isLoadingGetStar && star?.result) { setBookmark((prev) => ({ @@ -77,13 +80,31 @@ const CreateBookmark = () => { // 관련된 노드만 필터링 const relatedNodes = stars.starListDto.filter((star) => relatedNodeIds.has(star.starId)); + // 현재 북마크 정보 가져오기 + const currentStar = stars.starListDto.find((star) => star.starId === id); + + // 연관된 노드가 없으면 현재 북마크만 포함 + const nodes = + relatedNodes.length > 0 + ? relatedNodes.map((star) => ({ + id: star.starId, + name: star.title, + val: Math.min(star.views, 10), + url: star.siteUrl, + })) + : currentStar + ? [ + { + id: currentStar.starId, + name: currentStar.title, + val: 10, + url: currentStar.siteUrl, + }, + ] + : []; + return { - nodes: relatedNodes.map((star) => ({ - id: star.starId, - name: star.title, - val: Math.min(star.views, 10), - url: star.siteUrl, - })), + nodes, links: relatedLinks.map((link) => ({ source: link.linkedNodeIdList[0], target: link.linkedNodeIdList[1], @@ -100,8 +121,6 @@ const CreateBookmark = () => { return ; } - const saveDisabled = bookmark.categoryName.trim() === ""; - const onSelectCategory = (category: string) => { setBookmark((prev) => ({ ...prev, categoryName: category })); }; @@ -144,38 +163,22 @@ const CreateBookmark = () => { }; const onClickSave = async () => { - // id가 있으면 수정하기 API 요청 - const categoryName = bookmark.categories.find( - (category) => category.name === bookmark.categoryName - )?.name; - - if (!categoryName) { - return; - } - const body = { ...bookmark, keywordList: bookmark.keywords, }; if (id) { - await mutateAsyncUpdateStar( - { id, body }, - { - onSuccess: () => { - setIsFindingExistPath(true); - }, - } - ); + await mutateAsyncUpdateStar({ id, body }); } else { - await mutateAsyncCompleteCreateStar(body, { - onSuccess: () => { - setIsFindingExistPath(true); - }, - }); + await mutateAsyncCompleteCreateStar(body); } }; + const onClickCancel = () => { + navigate("/bookmark"); + }; + if (isLoadingGetStar) { return (
@@ -188,15 +191,9 @@ const CreateBookmark = () => { return (
-
-
- -

Nebula

-
- +
+ +

Nebula

{id && (
@@ -220,6 +217,17 @@ const CreateBookmark = () => { value={bookmark.summaryAI} onChange={onChangeText} placeholder="북마크에 대한 요약을 작성할 수 있어요." + rightElement={ + + } />