Skip to content

Commit

Permalink
Refactoring && Responsiveness
Browse files Browse the repository at this point in the history
  • Loading branch information
git-init-priyanshu committed Sep 9, 2024
1 parent 647e348 commit a02bacc
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 203 deletions.
30 changes: 22 additions & 8 deletions app/document/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import logo from "@/public/logo.svg";

import SearchBar from "./components/SearchBar";
import HeaderButtons from "./components/HeaderButtons";
import ProfileBtn from "./components/ProfileBtn";

const roboto = Montserrat({
weight: "500",
Expand All @@ -16,14 +17,27 @@ const roboto = Montserrat({
type HeaderPropType = Pick<SessionReturnType, "name" | "image">;
export default function Header({ image, name }: HeaderPropType) {
return (
<div className="flex border-b bg-white justify-between items-center py-2 px-4 sticky top-0 z-50">
<div className="flex gap-2 items-end justify-center">
<Image src={logo} width={45} alt="logo" />
<p className={`${roboto.className} text-lg text-neutral-600`}>DocX</p>
{/* <Image src={logo} width={45} alt="logo" /> */}
<>
<div className="hidden md:flex border-b bg-white justify-between items-center py-2 px-2 md:px-4 sticky top-0 z-50">
<div className="flex gap-2 items-end justify-center ">
<Image src={logo} width={45} alt="logo" />
<p className={`${roboto.className} text-lg text-neutral-600`}>DocX</p>
</div>
<SearchBar />
<div className="flex gap-4">
<HeaderButtons />
<ProfileBtn name={name} image={image} />
</div>
</div>
<SearchBar />
<HeaderButtons image={image} name={name} />
</div>

<div className="w-full p-4 flex gap-4 items-center md:hidden">
<div className="border rounded-full bg-white p-[8px]">
<Image src={logo} width={35} alt="logo" />
</div>
<SearchBar />
<HeaderButtons />
<ProfileBtn name={name} image={image} />
</div>
</>
);
}
54 changes: 6 additions & 48 deletions app/document/components/Header/components/HeaderButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,13 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { CloudUpload, LogOut, PlusIcon } from "lucide-react";
import { CloudUpload, PlusIcon } from "lucide-react";

import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { CreateNewDocument, LogoutAction } from "../actions";
import { CreateNewDocument } from "../actions";
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";

type HeaderBtnPropType = Pick<SessionReturnType, "name" | "image">;
export default function HeaderButtons({ image, name }: HeaderBtnPropType) {
export default function HeaderButtons() {
const router = useRouter();

const [isLoading, setIsLoading] = useState(false);
Expand All @@ -34,62 +28,26 @@ export default function HeaderButtons({ image, name }: HeaderBtnPropType) {
}
};

const logout = async () => {
const response = await LogoutAction();
if (response.success) {
toast.success("Successfully logged out");
router.push("/api/auth/signin");
} else {
toast.error(response.error);
}
};
return (
<div className="flex gap-4">
<div className="flex md:gap-4">
<div className="fixed z-10 bottom-4 right-4 md:static flex gap-4">
{process.env.NODE_ENV === "development" ?
<Button
variant="outline"
className="flex gap-2 text-blue-500 hover:text-blue-500 hover:border-blue-200 rounded-l-full md:rounded-full"
className="flex gap-2 text-blue-500 hover:text-blue-500 hover:border-blue-200 rounded-lg md:rounded-full"
>
<CloudUpload size={15} />
<p className="hidden md:block">Upload</p>
</Button>
: <></>
}
<LoaderButton
className="bg-blue-500 text-white hover:bg-blue-600 rounded-r-full md:rounded-full"
className="bg-blue-500 text-white hover:bg-blue-600 rounded-lg md:rounded-full"
onClickFunc={createDocument}
isLoading={isLoading}
icon={<PlusIcon size={20} />}
>
<p className="hidden md:block">Create New</p>
</LoaderButton>
</div>
<Popover>
<PopoverTrigger>
<div className="relative flex justify-center items-center bg-blue-50 size-8 rounded-full ring-blue-100 hover:ring">
<p>{getInitials(name ?? "X")}</p>
</div>
{image ? (
<Avatar className="size-8 absolute transform -translate-y-full">
<AvatarImage src={image} />
</Avatar>
) : (<></>)}
</PopoverTrigger>
<PopoverContent
className="flex flex-col p-0 py-1 text-left w-min bg-white shadow-md"
>
<Button
// id={item.title}
variant="ghost"
className="gap-2 justify-start"
onClick={logout}
>
<LogOut size={20} color="#48acf9" strokeWidth={1.5} />
<p className="text-neutral-600">Logout</p>
</Button>
</PopoverContent>
</Popover>
</div >
);
}
56 changes: 56 additions & 0 deletions app/document/components/Header/components/ProfileBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client"

import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { LogOut } from "lucide-react";
import { PopoverContent, PopoverTrigger } from "@radix-ui/react-popover";

import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { Popover } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { SessionReturnType } from "@/lib/customHooks/ReturnType";
import getInitials from "@/helpers/getInitials";

import { LogoutAction } from "../actions";

type ProfileBtnPropType = Pick<SessionReturnType, "name" | "image">
export default function ProfileBtn({ name, image }: ProfileBtnPropType) {
const router = useRouter();

const logout = async () => {
const response = await LogoutAction();
if (response.success) {
toast.success("Successfully logged out");
router.push("/api/auth/signin");
} else {
toast.error(response.error);
}
};
return (
<Popover>
<PopoverTrigger>
<div className="relative flex justify-center items-center bg-blue-50 size-8 rounded-full ring-blue-100 hover:ring">
<p>{getInitials(name ?? "X")}</p>
</div>
{image ? (
<Avatar className="size-8 absolute transform -translate-y-full">
<AvatarImage src={image} />
</Avatar>
) : (<></>)}
</PopoverTrigger>
<PopoverContent
className="flex flex-col p-0 py-1 text-left w-min bg-white shadow-md"
>
<Button
// id={item.title}
variant="ghost"
className="gap-2 justify-start"
onClick={logout}
>
<LogOut size={20} color="#48acf9" strokeWidth={1.5} />
<p className="text-neutral-600">Logout</p>
</Button>
</PopoverContent>
</Popover>
)
}
6 changes: 3 additions & 3 deletions app/document/components/Header/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function SearchBar() {

return (
<div
className={`transform relative hidden md:block lg:w-[30rem] lg:translate-x-1/4`}
className={`transform relative w-full md:w-fit lg:w-[30rem] translate-x-0 xl:translate-x-1/4`}
>
<div className="relative">
<Input
Expand All @@ -87,15 +87,15 @@ export default function SearchBar() {
<X
size={20}
onClick={() => setSearchValue("")}
className={`${!searchValue ? "hidden" : "block"
className={`${!searchValue ? "hidden" : ""
} absolute text-slate-500 right-0 top-1/2 transform -translate-y-1/2 mr-2 cursor-pointer`}
/>
</div>

<div
ref={searchedResponseRef}
className={`${isFocused && searchResponse ? "block" : "hidden"
} absolute shadow-md overflow-hidden border border-t-0 bg-white w-full rounded-b-3xl`}
} absolute shadow-md overflow-hidden border border-t-0 bg-white w-full md:w-full rounded-b-3xl`}
>
{isSearching ? (
<div className="p-3 flex items-center gap-2 justify-center text-center text-neutral-500">
Expand Down
2 changes: 2 additions & 0 deletions app/writer/[id]/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { useRouter } from "next/navigation";
import { ALargeSmall, Redo, Undo, X } from "lucide-react";
import { Editor } from "@tiptap/react";
Expand Down
146 changes: 136 additions & 10 deletions app/writer/[id]/editor/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,145 @@
"use client"

import { useEffect, useState, useCallback } from "react";
import { useParams } from "next/navigation";
import { useEditor } from "@tiptap/react";
import { toast } from "sonner";
import type { Document} from "@prisma/client";
import html2canvas from "html2canvas";
import Collaboration from "@tiptap/extension-collaboration";
import CollaborationCursor from "@tiptap/extension-collaboration-cursor";

import { extensions, props } from "./editorConfig";
import { getRandomColor } from "@/helpers/getRandomColor";
import useDebounce from "@/lib/customHooks/useDebounce";

type EditorType = {
docData?: string;
debouncedSaveDoc: (editor: any) => void;
};
import { ydoc, provider, extensions, props } from "./editorConfig";
import { GetDocDetails, UpdateDocData, UpdateThumbnail } from "../actions";

type EditorPropType = {
setIsSaving: React.Dispatch<React.SetStateAction<boolean>>;
}
export const Editor = ({ setIsSaving }: EditorPropType) => {
const params = useParams();

const [isFirstLoad, setIsFirstLoad] = useState(true);
const [name, setName] = useState("");
const [docData, setDocData] = useState<Document | undefined>(undefined);
const [status, setStatus] = useState("connecting");
console.log(status);

useEffect(() => {
setName(localStorage.getItem("name") || "");
setIsFirstLoad(false);
}, [])

useEffect(() => {
// Update status changes
const statusHandler = (event: any) => {
setStatus(event.status);
};

provider.on("status", statusHandler);

return () => {
provider.off("status", statusHandler);
};
}, []);

// Doc data fetching
useEffect(() => {
(async () => {
const response = await GetDocDetails(params.id);
if (response.success) {
setDocData(response.data);
} else {
toast.error(response.error);
}
})();
}, [params.id]);

export const Editor = ({ docData, debouncedSaveDoc }: EditorType) => {
return useEditor({
extensions: extensions,
const createDocThumbnail = useCallback(async () => {
try {
const page = document.getElementsByClassName("tiptap")[0];
if (!page) return;
// @ts-ignore
const canvas = await html2canvas(page, { scale: 1 });

const thumbnail = canvas
.toDataURL(`${docData?.id}thumbnail/png`)
.replace(/^data:image\/\w+;base64,/, "");

await UpdateThumbnail(params.id, thumbnail);
} catch (e) {
console.log(e);
toast.error("Something went wrong");
}
}, [docData?.id, params.id]);

const debounce = useDebounce(async (editor: any) => {
setIsSaving(true);

const response = await UpdateDocData(
params.id,
JSON.stringify(editor.getJSON()),
);
if (response.success) {
setIsSaving(false);
return createDocThumbnail();
}
setIsSaving(false);
toast.error(response.error);
}, 1000);

// Editor instance
const editor = useEditor({
onCreate: ({ editor: currentEditor }) => {
provider.on("synced", () => {
if (currentEditor.isEmpty) {
currentEditor.commands.setContent("");
}
});
},
extensions: [
...extensions,
Collaboration.configure({
document: ydoc,
}),
CollaborationCursor.configure({
provider,
user: {
name,
color: getRandomColor(),
},
}),
],
editorProps: props,
content: docData,
content: "",
onUpdate({ editor }) {
debouncedSaveDoc(editor);
if (!isFirstLoad) {
debounce(editor);
}
},
});

// Save current user to localStorage and emit to editor
useEffect(() => {
if (editor) {
editor
.chain()
.focus()
.updateUser({ name })
.run();
}
}, [editor, name]);

// Set content of the doc
useEffect(() => {
if (editor && docData) {
editor.commands.setContent(
docData?.data ? JSON.parse(docData?.data) : "",
);
}
}, [editor, docData, docData?.data]);

return { editor, docData };
};
Loading

0 comments on commit a02bacc

Please sign in to comment.