Skip to content

Commit 23caa65

Browse files
committed
feat: 익스텐션 이미 저장한 콘텐츠면 수정 모드 진입하도록 구현
1 parent 8f70729 commit 23caa65

19 files changed

Lines changed: 214 additions & 36 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { useCheckToken } from "@/hooks/use-check-token";
2+
3+
export default function CheckToken() {
4+
useCheckToken();
5+
6+
return null;
7+
}

apps/extension/src/components/layout/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { useCheckToken } from "@/hooks/use-check-token";
21
import { useDetectPath } from "@/hooks/use-detect-path";
32
import { queryClient } from "@/lib/tanstack";
43
import { QueryClientProvider } from "@tanstack/react-query";
54

65
import { Outlet } from "react-router-dom";
76

7+
import CheckToken from "./check-token";
88
import { Toaster } from "../ui/sonner";
99

1010
function Layout() {
1111
useDetectPath();
12-
useCheckToken();
1312

1413
return (
1514
<QueryClientProvider client={queryClient}>
15+
<CheckToken />
1616
<Outlet />
1717
<Toaster position="top-center" duration={3000} closeButton />
1818
</QueryClientProvider>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const CONTENT = {
2+
GET_ALL_CONTENTS: "get-all-contents",
3+
GET_CONTENT_BY_ID: "get-content-by-id",
4+
};

apps/extension/src/hooks/use-check-token.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { useEffect } from "react";
22

3+
import { useGetAllContents } from "@/lib/tanstack/mutation/content";
34
import { useUserStore } from "@/lib/zustand/user";
45
import { getCookie } from "@/utils/cookie";
56

67
import { useReplaceNavigate } from "./use-replace-navigate";
78

89
export function useCheckToken() {
9-
const { isLoggedIn, setIsLoggedIn } = useUserStore();
10+
const { isLoggedIn, setIsLoggedIn, setContents } = useUserStore();
11+
const { mutateAsync: getAllContents } = useGetAllContents();
1012

1113
const navigate = useReplaceNavigate();
1214

@@ -19,6 +21,8 @@ export function useCheckToken() {
1921
return navigate("/");
2022
} else {
2123
setIsLoggedIn(true);
24+
const res = await getAllContents();
25+
setContents(res.result.content);
2226
return navigate("/search-content");
2327
}
2428
};

apps/extension/src/hooks/use-detect-path.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import { useEffect } from "react";
22

33
import { useTabStore } from "@/lib/zustand/tab";
4+
import { useUserStore } from "@/lib/zustand/user";
45
import { ChromeTabChangeInfoProps } from "@/types/chrome";
56
import { updateCurrentTab } from "@/utils/chrome";
67

8+
import { useLocation } from "react-router-dom";
9+
10+
import { useReplaceNavigate } from "./use-replace-navigate";
11+
712
export function useDetectPath() {
813
const { currentTab, setCurrentTab, setIsFindingExistPath } = useTabStore();
14+
const { isLoggedIn, contents } = useUserStore();
15+
16+
const navigate = useReplaceNavigate();
17+
const { pathname } = useLocation();
18+
const isCreateContentPage = pathname.includes("/create-content");
919

1020
useEffect(() => {
1121
updateCurrentTab(setCurrentTab);
@@ -32,8 +42,23 @@ export function useDetectPath() {
3242
}, []);
3343

3444
useEffect(() => {
45+
if (isCreateContentPage) return;
46+
47+
if (!isLoggedIn) {
48+
return navigate("/");
49+
}
50+
51+
const findContent = contents.find(
52+
(content) => decodeURI(content.url) === decodeURI(currentTab.url)
53+
);
54+
55+
if (findContent) {
56+
navigate(`/create-content?id=${findContent.id}`);
57+
} else {
58+
navigate("/search-content");
59+
}
3560
setIsFindingExistPath(false);
36-
}, [currentTab]);
61+
}, [currentTab, contents, isLoggedIn, isCreateContentPage]);
3762

3863
return { currentTab };
3964
}

apps/extension/src/lib/tanstack/mutation/content.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { successToast } from "@/utils/toast";
2-
import { useMutation } from "@tanstack/react-query";
3-
1+
import { CONTENT } from "@/constants/content";
42
import {
53
detailSaveWebContent,
64
detailSaveYoutubeContent,
75
finishDetailSaveWebContent,
86
finishDetailSaveYoutubeContent,
7+
getAllContents,
98
quickSaveWebContent,
109
quickSaveYoutubeContent,
11-
} from "../../../services/content";
10+
updateContent,
11+
} from "@/services/content";
12+
import { successToast } from "@/utils/toast";
13+
import { useMutation } from "@tanstack/react-query";
1214

1315
export const useQuickSaveContent = () => {
1416
return useMutation({
@@ -70,3 +72,22 @@ export const useFinishDetailSaveYoutubeContent = () => {
7072
},
7173
});
7274
};
75+
76+
export const useGetAllContents = () => {
77+
return useMutation({
78+
mutationKey: [CONTENT.GET_ALL_CONTENTS],
79+
mutationFn: getAllContents,
80+
onError: (error) => {
81+
console.error(error);
82+
},
83+
});
84+
};
85+
86+
export const useUpdateContent = () => {
87+
return useMutation({
88+
mutationFn: updateContent,
89+
onError: (error) => {
90+
console.error(error);
91+
},
92+
});
93+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { CONTENT } from "@/constants/content";
2+
import { getContentById } from "@/services/content";
3+
import { useQuery } from "@tanstack/react-query";
4+
5+
export const useGetContentById = (id: string | null) => {
6+
return useQuery({
7+
queryKey: [CONTENT.GET_CONTENT_BY_ID, id],
8+
queryFn: async () => await getContentById(id as string),
9+
enabled: !!id,
10+
});
11+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import type { CategoryContentDTO } from "@repo/types";
2+
13
import { create } from "zustand";
24

35
interface UserStoreprops {
46
isLoggedIn: boolean;
7+
contents: CategoryContentDTO[];
58
setIsLoggedIn: (isLoggedIn: boolean) => void;
9+
setContents: (contents: CategoryContentDTO[]) => void;
610
}
711

812
export const useUserStore = create<UserStoreprops>((set) => ({
913
isLoggedIn: false,
14+
contents: [],
1015
setIsLoggedIn: (isLoggedIn) => set({ isLoggedIn }),
16+
setContents: (contents) => set({ contents }),
1117
}));

apps/extension/src/pages/create-content.tsx

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
import { useRef, useState } from "react";
1+
import { useEffect, useRef, useState } from "react";
22

33
import Logo from "@/assets/logo.svg?react";
44
import { Button } from "@/components/ui/button";
55
import { Input } from "@/components/ui/input";
6+
import { CONTENT } from "@/constants/content";
67
import { useReplaceNavigate } from "@/hooks/use-replace-navigate";
8+
import { invalidateQueries } from "@/lib/tanstack";
79
import {
810
useFinishDetailSaveContent,
911
useFinishDetailSaveYoutubeContent,
12+
useUpdateContent,
1013
} from "@/lib/tanstack/mutation/content";
1114
import { useGetCategories } from "@/lib/tanstack/query/category";
15+
import { useGetContentById } from "@/lib/tanstack/query/content";
1216
import { contentSchema, type ContentSchemaType } from "@/schemas/content";
1317
import { infoToast, successToast } from "@/utils/toast";
1418
import { zodResolver } from "@hookform/resolvers/zod";
1519
import { useOutsideClick } from "@repo/ui/hooks/use-outside-click";
1620

1721
import { ArrowLeft, ChevronDown, Plus, Save, X } from "lucide-react";
1822
import { useForm } from "react-hook-form";
19-
import { Navigate, useLocation } from "react-router-dom";
23+
import { Navigate, useLocation, useSearchParams } from "react-router-dom";
2024

2125
interface CreateContentState {
2226
thumbnail: string;
@@ -29,11 +33,24 @@ interface CreateContentState {
2933
htmlFile: File;
3034
}
3135

36+
const DEFAULT_STATE: CreateContentState = {
37+
thumbnail: "",
38+
title: "",
39+
url: "",
40+
summary: "",
41+
memo: "",
42+
tags: [],
43+
category: "",
44+
htmlFile: new File([], ""),
45+
};
46+
3247
export default function CreateContent() {
3348
const [isCategoryOpen, setIsCategoryOpen] = useState<boolean>(false);
3449

3550
const { state } = useLocation() as { state: CreateContentState };
36-
const { htmlFile, ...restState } = state;
51+
const [searchParams] = useSearchParams();
52+
const id = searchParams.get("id");
53+
const { data: content, isLoading, isRefetching } = useGetContentById(id);
3754

3855
const tagInputRef = useRef<HTMLInputElement>(null);
3956

@@ -51,21 +68,47 @@ export default function CreateContent() {
5168
mutateAsync: mutateFinishDetailSaveYoutubeContent,
5269
isPending: isPendingFinishDetailSaveYoutubeContent,
5370
} = useFinishDetailSaveYoutubeContent();
71+
const { mutateAsync: mutateUpdateContent, isPending: isPendingUpdateContent } =
72+
useUpdateContent();
5473

5574
const { data: userCategories } = useGetCategories();
5675

76+
const isBadRequest = !id && !state;
77+
78+
// 기본값을 미리 계산
79+
const defaultValues = (() => {
80+
if (isBadRequest || isLoading) {
81+
return { ...DEFAULT_STATE, memo: "" };
82+
}
83+
84+
if (id && !isLoading && content?.result) {
85+
return content.result;
86+
}
87+
88+
return { ...state, memo: "" };
89+
})();
90+
5791
const {
5892
register,
5993
handleSubmit,
6094
watch,
6195
setValue,
96+
reset,
6297
formState: { errors },
6398
} = useForm<ContentSchemaType>({
6499
resolver: zodResolver(contentSchema),
65-
defaultValues: { ...restState, memo: "" },
100+
defaultValues,
66101
});
67102

68-
if (!state) {
103+
// defaultValues가 변경될 때 폼 값 업데이트
104+
useEffect(() => {
105+
if (!isLoading && !isRefetching) {
106+
reset(defaultValues);
107+
}
108+
}, [isLoading, isRefetching]);
109+
110+
// state가 없으면 리다이렉트
111+
if (isBadRequest) {
69112
return <Navigate to="/bad-request" replace />;
70113
}
71114

@@ -75,14 +118,36 @@ export default function CreateContent() {
75118
const watchedCategory = watch("category");
76119
const watchedThumbnail = watch("thumbnail");
77120

78-
const isYoutubeUrl = state.url.includes("youtube.com");
79-
const isPending = isPendingFinishDetailSaveContent || isPendingFinishDetailSaveYoutubeContent;
121+
const isYoutubeUrl = watch("url").includes("youtube.com");
122+
const isPending =
123+
isPendingFinishDetailSaveContent ||
124+
isPendingFinishDetailSaveYoutubeContent ||
125+
isPendingUpdateContent;
80126

81127
const onGoBack = () => {
82128
navigate("/search-content");
83129
};
84130

85131
const onSave = handleSubmit(async (data) => {
132+
if (id) {
133+
await mutateUpdateContent(
134+
{
135+
id: id as string,
136+
...data,
137+
thumbnail: data.thumbnail || "",
138+
summary: data.summary || "",
139+
memo: data.memo || "",
140+
},
141+
{
142+
onSuccess: () => {
143+
successToast("저장에 성공했어요.");
144+
navigate("/search-content");
145+
invalidateQueries([CONTENT.GET_CONTENT_BY_ID, id]);
146+
},
147+
}
148+
);
149+
return;
150+
}
86151
if (isYoutubeUrl) {
87152
await mutateFinishDetailSaveYoutubeContent(
88153
{
@@ -110,7 +175,7 @@ export default function CreateContent() {
110175
for (const tag of data.tags) {
111176
formData.append("tags", tag);
112177
}
113-
formData.append("htmlFile", htmlFile);
178+
formData.append("htmlFile", state.htmlFile);
114179

115180
await mutateFinishDetailSaveContent(formData, {
116181
onSuccess: () => {

apps/extension/src/pages/search-content.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ function LogoutDialogContent() {
7676

7777
export default function SearchContent() {
7878
const { currentTab, isFindingExistPath } = useTabStore();
79+
const { contents } = useUserStore();
7980

8081
const navigate = useNavigate();
8182

0 commit comments

Comments
 (0)