Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/extension/public/icon128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/extension/src/assets/menu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions apps/extension/src/components/loading/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<div className="flex h-full flex-col items-center justify-center gap-10 text-notification">
{title}
<div className="text-center">
<h1>{title}</h1>
<p className="text-description">{description}</p>
</div>
<Spinner />
</div>
) : (
Expand Down
12 changes: 9 additions & 3 deletions apps/extension/src/hooks/use-detect-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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();

Expand All @@ -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);
}
};
Expand All @@ -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) {
Expand All @@ -60,7 +66,7 @@ export function useDetectPath() {
navigate("/bookmark");
}
setIsFindingExistPath(false);
}, [stars, currentTab]);
}, [stars, currentTab, isLoggedIn]);

return { currentTab };
}
7 changes: 7 additions & 0 deletions apps/extension/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@ textarea {
}
outline: none;
}

button {
cursor: pointer;
&:disabled {
cursor: not-allowed;
}
}
90 changes: 55 additions & 35 deletions apps/extension/src/pages/bookmark.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,26 @@
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";

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<HTMLDivElement>(() => 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({
Expand Down Expand Up @@ -65,13 +51,38 @@ const Bookmark = () => {
});
};

const onToggleMenu = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setIsMenuOpen((prev) => !prev);
};

const onOpenLogoutModal = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setIsMenuOpen(false);
setIsLogoutModalOpen(true);
};

return (
<Loading title="페이지를 요약하고 있어요!">
<Loading title="페이지를 요약하고 있어요!" description="페이지를 벗어나면 요약이 취소돼요.">
<header className="flex items-center justify-between">
<Logo />
<div className="relative">
<button onClick={onToggleMenu}>
<Menu />
</button>
{isMenuOpen && (
<div ref={menuContainerRef} className="absolute bottom-0 right-0 translate-y-full">
<button
onClick={onOpenLogoutModal}
className="w-max rounded-sm border border-black px-2.5 py-1"
>
로그아웃
</button>
</div>
)}
</div>
</header>
<main className="flex h-full flex-col justify-center gap-28 overflow-x-hidden">
<section>
<Graph2D graphData={graphData} />
<h1 className="text-notification">현재 페이지 정보</h1>
</section>
<section className="space-y-4">
<div className="text-body space-y-2">
<h2>URL</h2>
Expand All @@ -82,15 +93,24 @@ const Bookmark = () => {
<p className="text-gray7">{currentTab.title}</p>
</div>
</section>
<div className="flex flex-col gap-2">
<RectangleButton onClick={onClickAdd} disabled={isFindingExistPath}>
{isFindingExistPath ? "기존 북마크(추가 불가)" : "북마크에 추가하기"}
</RectangleButton>
<RectangleButton variation="outline" onClick={onClickLogout}>
로그아웃
</RectangleButton>
</div>
<RectangleButton className="flex-none" onClick={onClickAdd} disabled={isFindingExistPath}>
{isFindingExistPath ? "기존 북마크(추가 불가)" : "북마크에 추가하기"}
</RectangleButton>
</main>
{isLogoutModalOpen && (
<Modal
title="로그아웃"
subTitle="로그아웃 하시겠습니까?"
callback={() => setIsLogoutModalOpen(false)}
>
<div className="flex w-full gap-2">
<RectangleButton onClick={() => setIsLogoutModalOpen(false)} variation="outline">
취소
</RectangleButton>
<RectangleButton onClick={onClickLogout}>로그아웃</RectangleButton>
</div>
</Modal>
)}
</Loading>
);
};
Expand Down
100 changes: 54 additions & 46 deletions apps/extension/src/pages/create-bookmark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<StateProps>(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();
Expand All @@ -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) => ({
Expand All @@ -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],
Expand All @@ -100,8 +121,6 @@ const CreateBookmark = () => {
return <Navigate to="/bad-request" replace />;
}

const saveDisabled = bookmark.categoryName.trim() === "";

const onSelectCategory = (category: string) => {
setBookmark((prev) => ({ ...prev, categoryName: category }));
};
Expand Down Expand Up @@ -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 (
<div className="flex h-full flex-col items-center justify-center gap-10 text-notification">
Expand All @@ -188,15 +191,9 @@ const CreateBookmark = () => {
return (
<Loading title="북마크를 저장하고 있어요!">
<main className="flex h-full flex-col gap-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-1">
<Logo />
<p className="text-description font-bold">Nebula</p>
</div>
<button className="flex items-center gap-1">
<AISummary />
<p className="text-xs font-semibold">Nebula AI</p>
</button>
<header className="flex items-center gap-1">
<Logo />
<p className="text-description font-bold">Nebula</p>
</header>
{id && (
<section>
Expand All @@ -220,6 +217,17 @@ const CreateBookmark = () => {
value={bookmark.summaryAI}
onChange={onChangeText}
placeholder="북마크에 대한 요약을 작성할 수 있어요."
rightElement={
<button
className={cn("flex items-center gap-1", isAISummaryPending && "animate-pulse")}
onClick={() => setIsAISummaryPending(true)}
>
<AISummary />
<p className="text-xs font-semibold">
{isAISummaryPending ? "요약 중..." : "인공지능 요약"}
</p>
</button>
}
/>
<Textarea
id="userMemo"
Expand All @@ -239,11 +247,11 @@ const CreateBookmark = () => {
onUpdateKeyword={onUpdateKeyword}
/>
</section>
<section className="flex gap-3">
<RectangleButton variation="outline">취소</RectangleButton>
<RectangleButton disabled={saveDisabled} onClick={onClickSave}>
저장
<section className="flex gap-3 pb-5">
<RectangleButton variation="outline" onClick={onClickCancel}>
취소
</RectangleButton>
<RectangleButton onClick={onClickSave}>저장</RectangleButton>
</section>
</main>
</Loading>
Expand Down
Loading