diff --git a/app/components/Card/Card.tsx b/app/components/Card/Card.tsx
index 91f3a19..0c60c7f 100644
--- a/app/components/Card/Card.tsx
+++ b/app/components/Card/Card.tsx
@@ -2,7 +2,6 @@
import { useRef, useState } from "react";
import { useRouter } from "next/navigation";
-import { CircleCheck } from "lucide-react";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
@@ -13,6 +12,7 @@ import AvatarList from "@/components/AvatarList";
import CardOptions from "./components/Options";
import prettifyDate from "@/helpers/prettifyDates";
import useClientSession from "@/lib/customHooks/useClientSession";
+import useDebounce from "@/lib/customHooks/useDebounce";
type DocCardPropType = {
docId: string;
@@ -32,6 +32,11 @@ export default function DocCard({
}: DocCardPropType) {
const router = useRouter();
+ const debounce = useDebounce(async () => {
+ if (!inputRef.current || !session?.id) return;
+ await RenameDocument(docId, inputRef.current.value);
+ }, 1000)
+
const session = useClientSession();
localStorage.setItem("name", session.name as string);
@@ -52,15 +57,11 @@ export default function DocCard({
ref={inputRef}
value={name}
className="w-full text-md border-none focus-visible:bg-slate-50"
- onChange={(e) => setName(e.target.value)}
- />
- {
- if (!inputRef.current || !session?.id) return;
- await RenameDocument(docId, inputRef.current.value);
+ onChange={(e) => {
+ setName(e.target.value);
+ debounce(e.target.value);
}}
- className={`${title != name ? "" : "hidden"} size-4 text-slate-500 hover:cursor-pointer absolute right-3 top-1/2 transform -translate-y-1/2`}
- >
+ />
diff --git a/app/components/Card/components/Options.tsx b/app/components/Card/components/Options.tsx
index eb48258..7aead64 100644
--- a/app/components/Card/components/Options.tsx
+++ b/app/components/Card/components/Options.tsx
@@ -25,7 +25,6 @@ import {
import { Button } from "@/components/ui/button";
import { DeleteDocument } from "../actions";
-import useClientSession from "@/lib/customHooks/useClientSession";
import LoaderButton from "@/components/LoaderButton";
type CardOptionsPropType = {
diff --git a/app/components/Header/Header.tsx b/app/components/Header/Header.tsx
index 5cc78bb..679d44e 100644
--- a/app/components/Header/Header.tsx
+++ b/app/components/Header/Header.tsx
@@ -2,10 +2,8 @@ import Image from "next/image";
import { Montserrat_Alternates as Montserrat } from "next/font/google"
import { SessionReturnType } from "@/lib/customHooks/ReturnType";
-import getInitials from "@/helpers/getInitials";
import logo from "@/public/logo.svg";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import SearchBar from "./components/SearchBar";
import HeaderButtons from "./components/HeaderButtons";
@@ -18,25 +16,14 @@ const roboto = Montserrat({
type HeaderPropType = Pick
;
export default function Header({ image, name }: HeaderPropType) {
return (
-
+
-
- {process.env.NODE_ENV === "production" ? (
- <>>
- ) : (
-
- {image ? (
-
- ) : (
- {getInitials(name ?? "")}
- )}
-
- )}
+
);
}
diff --git a/app/components/Header/components/HeaderButtons.tsx b/app/components/Header/components/HeaderButtons.tsx
index 1e369b7..c15c16c 100644
--- a/app/components/Header/components/HeaderButtons.tsx
+++ b/app/components/Header/components/HeaderButtons.tsx
@@ -5,22 +5,24 @@ import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { CloudUpload, LogOut, PlusIcon } from "lucide-react";
-import { Button } from "@/components/ui/button";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { CreateNewDocument, LogoutAction } from "../actions";
-import useClientSession from "@/lib/customHooks/useClientSession";
+import { Button } from "@/components/ui/button";
+import { SessionReturnType } from "@/lib/customHooks/ReturnType";
import LoaderButton from "@/components/LoaderButton";
+import getInitials from "@/helpers/getInitials";
+import { Popover } from "@/components/ui/popover";
+import { PopoverContent, PopoverTrigger } from "@radix-ui/react-popover";
-export default function HeaderButtons() {
+type HeaderBtnPropType = Pick
;
+export default function HeaderButtons({ image, name }: HeaderBtnPropType) {
const router = useRouter();
- const session = useClientSession();
-
const [isLoading, setIsLoading] = useState(false);
const createDocument = async () => {
setIsLoading(true);
- if (!session?.id) return;
const response = await CreateNewDocument();
if (response.success) {
setIsLoading(false);
@@ -34,7 +36,6 @@ export default function HeaderButtons() {
const logout = async () => {
const response = await LogoutAction();
- console.log(response);
if (response.success) {
toast.success("Successfully logged out");
router.push("/api/auth/signin");
@@ -45,13 +46,16 @@ export default function HeaderButtons() {
return (
-
+ {process.env.NODE_ENV === "development" ?
+
+ : <>>
+ }
Create New
-
-
+
+
+
+
{getInitials(name ?? "X")}
+
+ {image ? (
+
+
+
+ ) : (<>>)}
+
+
+
+
+
+
);
}
diff --git a/app/components/Header/components/SearchBar.tsx b/app/components/Header/components/SearchBar.tsx
index 1a0b6fa..04054c6 100644
--- a/app/components/Header/components/SearchBar.tsx
+++ b/app/components/Header/components/SearchBar.tsx
@@ -1,14 +1,14 @@
"use client";
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { Search, X } from "lucide-react";
-import { debounce } from "lodash";
import Image from "next/image";
import { Input } from "@/components/ui/input";
import prettifyDate from "@/helpers/prettifyDates";
import doc from "@/public/output-onlinepngtools.svg";
+import useDebounce from "@/lib/customHooks/useDebounce";
import { SearchDocAction } from "../actions";
@@ -28,6 +28,14 @@ type SearchResponse = {
export default function SearchBar() {
const router = useRouter();
+ const debounce = useDebounce(
+ async (value: string) => {
+ if (!searchValue) return;
+ setIsSearching(true);
+ setSearchResponse(await SearchDocAction(value));
+ setIsSearching(false);
+ }, 500);
+
const searchedResponseRef = useRef(null);
const [searchResponse, setSearchResponse] = useState<
@@ -37,21 +45,6 @@ export default function SearchBar() {
const [isFocused, setIsFocused] = useState(false);
const [isSearching, setIsSearching] = useState(false);
- const search = useCallback(
- async (value: string) => {
- if (!searchValue) return;
- setIsSearching(true);
- setSearchResponse(await SearchDocAction(value));
- setIsSearching(false);
- },
- [searchValue],
- );
-
- const debouncedSearch = useMemo(
- () => debounce((value: string) => search(value), 500),
- [search],
- );
-
const handleDocumentClick = (e: any) => {
if (
searchedResponseRef.current &&
@@ -73,17 +66,17 @@ export default function SearchBar() {
>
setIsFocused(true)}
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
if (!e.target.value) return setSearchResponse(undefined);
- debouncedSearch(e.target.value);
+ // debouncedSearch(e.target.value);
+ debounce(e.target.value);
}}
placeholder="Search documents..."
/>
@@ -94,17 +87,15 @@ export default function SearchBar() {
setSearchValue("")}
- className={`${
- !searchValue ? "hidden" : "block"
- } absolute text-slate-500 right-0 top-1/2 transform -translate-y-1/2 mr-2 cursor-pointer`}
+ className={`${!searchValue ? "hidden" : "block"
+ } absolute text-slate-500 right-0 top-1/2 transform -translate-y-1/2 mr-2 cursor-pointer`}
/>
{isSearching ? (
diff --git a/app/layout.tsx b/app/layout.tsx
index 6f9842b..b61fee3 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -12,7 +12,7 @@ import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
- title: "Docx",
+ title: "DocX",
description:
"An open-source alternative to Google Docs, that lets you write and customize your docs collaboratively with others",
};
diff --git a/app/writer/[id]/actions.ts b/app/writer/[id]/actions.ts
index 017ecd9..9f90f15 100644
--- a/app/writer/[id]/actions.ts
+++ b/app/writer/[id]/actions.ts
@@ -104,6 +104,7 @@ export const UpdateThumbnail = async (id: any, thumbnail: string) => {
error: "User is not logged in",
};
+ console.log(process.env.BACKEND_SERVER_URL)
const response = await fetch(`${process.env.BACKEND_SERVER_URL}/push-to-quque`, {
method: "POST",
headers: {
diff --git a/app/writer/[id]/components/options/format/ColorHighlight.tsx b/app/writer/[id]/components/options/format/ColorHighlight.tsx
index 5ee2d57..3f52a89 100644
--- a/app/writer/[id]/components/options/format/ColorHighlight.tsx
+++ b/app/writer/[id]/components/options/format/ColorHighlight.tsx
@@ -13,6 +13,11 @@ import {
import { Input } from "@/components/ui/input";
export default function ColorHighlight({ editor }: { editor: Editor | null }) {
+ const colorPopoverRef = useRef
(null);
+ const bgPopoverRef = useRef(null);
+
+ const [isColorPopoverOpen, setIsColorPopoverOpen] = useState(false);
+ const [isBgPopoverOpen, setIsBgPopoverOpen] = useState(false);
const [fontColor, setFontColor] = useState("#000000");
const [highlightColor, setHighlightColor] = useState("#fdfb7a");
@@ -27,6 +32,25 @@ export default function ColorHighlight({ editor }: { editor: Editor | null }) {
editor.chain().focus().toggleHighlight({ color: hex }).run();
};
+ const handleDocumentClick = (e: any) => {
+ if (
+ (colorPopoverRef.current &&
+ !colorPopoverRef.current.contains(e.target))
+ ||
+ (bgPopoverRef.current &&
+ !bgPopoverRef.current.contains(e.target))
+ ) {
+ setIsColorPopoverOpen(false);
+ setIsBgPopoverOpen(false);
+ }
+ };
+ useEffect(() => {
+ document.addEventListener("mousedown", handleDocumentClick);
+ return () => {
+ document.removeEventListener("mousedown", handleDocumentClick);
+ };
+ }, []);
+
return (
diff --git a/app/writer/[id]/page.tsx b/app/writer/[id]/page.tsx
index a192538..cf1c255 100644
--- a/app/writer/[id]/page.tsx
+++ b/app/writer/[id]/page.tsx
@@ -1,10 +1,9 @@
"use client";
-import { useState, useMemo, useCallback, useEffect, useRef } from "react";
+import { useState, useCallback, useEffect, useRef } from "react";
import { useParams } from "next/navigation";
import { EditorContent } from "@tiptap/react";
import { useEditor } from "@tiptap/react";
-import { debounce } from "lodash";
import { toast } from "sonner";
import html2canvas from "html2canvas";
import Collaboration from "@tiptap/extension-collaboration";
@@ -13,6 +12,7 @@ import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getRandomColor } from "@/helpers/getRandomColor";
import type { Document } from "@prisma/client";
+import useDebounce from "@/lib/customHooks/useDebounce";
import { ydoc, provider, extensions, props } from "./editor/editorConfig";
import { FormatOptions, InsertOptions } from "./components/options";
@@ -24,14 +24,22 @@ import Loading from "./components/EditorLoading";
export default function Dashboard() {
const params = useParams();
+ const editorRef = useRef(null);
+
+ const [isFirstLoad, setIsFirstLoad] = useState(true);
+ const [name, setName] = useState("");
const [option, setOption] = useState(0);
const [isSaving, setIsSaving] = useState(false);
const [docData, setDocData] = useState(undefined);
const [status, setStatus] = useState("connecting");
- console.log(status);
+ // console.log(status);
- const editorRef = useRef(null);
+ useEffect(() => {
+ setName(localStorage.getItem("name") || "");
+ setIsFirstLoad(false);
+ }, [])
+ // Doc data fetching
useEffect(() => {
(async () => {
const response = await GetDocDetails(params.id);
@@ -61,7 +69,7 @@ export default function Dashboard() {
}
}, [docData?.id, params.id]);
- const saveDoc = useCallback(async (editor: any) => {
+ const debounce = useDebounce(async (editor: any) => {
setIsSaving(true);
const response = await UpdateDocData(
@@ -74,12 +82,7 @@ export default function Dashboard() {
}
setIsSaving(false);
toast.error(response.error);
- }, [params.id, createDocThumbnail]);
-
- const debouncedSaveDoc = useMemo(
- () => debounce((editor: any) => saveDoc(editor), 1000),
- [saveDoc],
- );
+ }, 1000);
const editor = useEditor({
onCreate: ({ editor: currentEditor }) => {
@@ -97,7 +100,7 @@ export default function Dashboard() {
CollaborationCursor.configure({
provider,
user: {
- name: localStorage.getItem("name"),
+ name,
color: getRandomColor(),
},
}),
@@ -105,7 +108,9 @@ export default function Dashboard() {
editorProps: props,
content: "",
onUpdate({ editor }) {
- debouncedSaveDoc(editor);
+ if (!isFirstLoad) {
+ debounce(editor);
+ }
},
});
@@ -128,10 +133,10 @@ export default function Dashboard() {
editor
.chain()
.focus()
- .updateUser({ name: localStorage.getItem("name") })
+ .updateUser({ name })
.run();
}
- }, [editor]);
+ }, [editor, name]);
// Set content of the doc
useEffect(() => {
diff --git a/components/AvatarList.tsx b/components/AvatarList.tsx
index 81dc676..8ddef72 100644
--- a/components/AvatarList.tsx
+++ b/components/AvatarList.tsx
@@ -1,4 +1,6 @@
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import React from "react";
+
+import { Avatar, AvatarImage } from "@/components/ui/avatar";
import type { User } from "@prisma/client";
import getInitials from "@/helpers/getInitials";
@@ -10,17 +12,20 @@ type AvatarListPropType = {
export default function AvatarList({ users }: AvatarListPropType) {
return (
-
+
{users.map((e, index) => {
return (
-
+
+
+
{getInitials(e.user.name ?? "X")}
+
{e.user.picture ? (
-
- ) : (
-
{getInitials(e.user.name)}
- )}
-
- );
+
+
+
+ ) : <>>}
+
+ )
})}
);
diff --git a/lib/customHooks/useDebounce.tsx b/lib/customHooks/useDebounce.tsx
new file mode 100644
index 0000000..bdc9c81
--- /dev/null
+++ b/lib/customHooks/useDebounce.tsx
@@ -0,0 +1,20 @@
+import { debounce } from "lodash";
+import { useEffect, useMemo, useRef } from "react";
+
+export default function useDebounce
(
+ callbackFunction: ((value: D) => void) | ((value: D) => Promise),
+ debounceDelay: number,
+) {
+ const callbackRef = useRef(callbackFunction);
+
+ useEffect(() => {
+ callbackRef.current = callbackFunction;
+ }, [callbackFunction ]);
+
+ const debounceFunc = useMemo(
+ () => debounce((value: D) => callbackRef.current(value), debounceDelay),
+ [debounceDelay]
+ );
+
+ return debounceFunc;
+}