diff --git a/app/(app)/create/[[...paramsArr]]/page.tsx b/app/(app)/create/[[...paramsArr]]/page.tsx
deleted file mode 100644
index 96ab2f28..00000000
--- a/app/(app)/create/[[...paramsArr]]/page.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { redirect } from "next/navigation";
-import Content from "./_client";
-import { getServerAuthSession } from "@/server/auth";
-
-export const metadata = {
- title: "New post - Codú",
-};
-
-export default async function Page() {
- const session = await getServerAuthSession();
- if (!session) {
- redirect("/get-started");
- }
-
- return ;
-}
diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx
index 16def67f..cdb45497 100644
--- a/app/(app)/layout.tsx
+++ b/app/(app)/layout.tsx
@@ -79,22 +79,13 @@ export default async function RootLayout({
return (
<>
-
-
-
-
-
-
- {children}
-
-
-
-
-
+
+ {children}
+
>
);
}
diff --git a/app/(app)/my-posts/_client.tsx b/app/(app)/my-posts/_client.tsx
index ad429ce0..ee9cfcd6 100644
--- a/app/(app)/my-posts/_client.tsx
+++ b/app/(app)/my-posts/_client.tsx
@@ -18,7 +18,7 @@ import { useSearchParams } from "next/navigation";
import { api } from "@/server/trpc/react";
import { Tabs } from "@/components/Tabs";
import { PromptDialog } from "@/components/PromptService";
-import { PostStatus, getPostStatus } from "@/utils/post";
+import { status, getPostStatus } from "@/utils/post";
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(" ");
@@ -135,7 +135,7 @@ const MyPosts = () => {
}) => {
const postStatus = published
? getPostStatus(new Date(published))
- : PostStatus.DRAFT;
+ : status.DRAFT;
return (
{
- {published && postStatus === PostStatus.SCHEDULED ? (
+ {published && postStatus === status.SCHEDULED ? (
<>
{renderDate("Scheduled to publish on ", published)}
>
- ) : published && postStatus === PostStatus.PUBLISHED ? (
+ ) : published && postStatus === status.PUBLISHED ? (
<>
{/*If updatedAt is greater than published by more than on minutes show updated at else show published
as on updating published updatedAt is automatically updated and is greater than published*/}
@@ -174,7 +174,7 @@ const MyPosts = () => {
<>{renderDate("Published on ", published)}>
)}
>
- ) : postStatus === PostStatus.DRAFT ? (
+ ) : postStatus === status.DRAFT ? (
<>{renderDate("Last updated on ", updatedAt)}>
) : null}
diff --git a/app/(app)/create/[[...paramsArr]]/_client.tsx b/app/(editor)/create/[[...paramsArr]]/_client.tsx
similarity index 92%
rename from app/(app)/create/[[...paramsArr]]/_client.tsx
rename to app/(editor)/create/[[...paramsArr]]/_client.tsx
index 1bd2a9fd..40118161 100644
--- a/app/(app)/create/[[...paramsArr]]/_client.tsx
+++ b/app/(editor)/create/[[...paramsArr]]/_client.tsx
@@ -22,16 +22,23 @@ import { useMarkdownHotkeys } from "@/markdoc/editor/hotkeys/hotkeys.markdoc";
import { useMarkdownShortcuts } from "@/markdoc/editor/shortcuts/shortcuts.markdoc";
import { markdocComponents } from "@/markdoc/components";
import { config } from "@/markdoc/config";
-import { useParams, useRouter } from "next/navigation";
+import { notFound, useParams, useRouter } from "next/navigation";
import { usePrompt } from "@/components/PromptService";
import { Switch } from "@/components/Switch/Switch";
import copy from "copy-to-clipboard";
-import { PostStatus, getPostStatus, isValidScheduleTime } from "@/utils/post";
+import {
+ type PostStatus,
+ getPostStatus,
+ isValidScheduleTime,
+ status,
+} from "@/utils/post";
import { ImageUp, LoaderCircle } from "lucide-react";
import { uploadFile } from "@/utils/s3helpers";
import { getUploadUrl } from "@/app/actions/getUploadUrl";
+import EditorNav from "./navigation";
+import { type Session } from "next-auth";
-const Create = () => {
+const Create = ({ session }: { session: Session | null }) => {
const params = useParams();
const router = useRouter();
@@ -52,6 +59,7 @@ const Create = () => {
const [uploadStatus, setUploadStatus] = useState<
"loading" | "error" | "success" | "default"
>("default");
+ const [postStatus, setPostStatus] = useState
(null);
const { unsavedChanges: _unsaved, setUnsavedChanges: _setUnsaved } =
usePrompt();
@@ -186,16 +194,10 @@ const Create = () => {
toast.error("Error saving");
}
- if (isSuccess) {
- toast.success("Saved");
- }
-
if (draftFetchError) {
- toast.error(
- "Something went wrong fetching your draft, refresh your page or you may lose data",
- );
+ notFound();
}
- }, [draftFetchError, isError, isSuccess]);
+ }, [draftFetchError, isError]);
useEffect(() => {
if (shouldRefetch) {
@@ -227,7 +229,6 @@ const Create = () => {
await create({ ...formData });
} else {
await save({ ...formData, id: postId });
- toast.success("Saved");
setSavedTime(
new Date().toLocaleString(undefined, {
dateStyle: "medium",
@@ -243,9 +244,9 @@ const Create = () => {
saveStatus === "loading" ||
dataStatus === "loading";
- const postStatus = data?.published
+ const currentPostStatus = data?.published
? getPostStatus(new Date(data.published))
- : PostStatus.DRAFT;
+ : status.DRAFT;
const onSubmit = async (inputData: SavePostInput) => {
// validate markdoc syntax
@@ -264,7 +265,7 @@ const Create = () => {
await savePost();
- if (postStatus === PostStatus.PUBLISHED) {
+ if (currentPostStatus === status.PUBLISHED) {
if (data) {
router.push(`/articles/${data.slug}`);
}
@@ -341,10 +342,21 @@ const Create = () => {
published: published ? published : undefined,
});
setIsPostScheduled(published ? new Date(published) > new Date() : false);
+ setPostStatus(
+ published ? getPostStatus(new Date(published)) : status.DRAFT,
+ );
}, [data]);
useEffect(() => {
- if (postStatus !== PostStatus.DRAFT) return;
+ if ((title + body).length < 5) {
+ setPostStatus(null);
+ } else if (postStatus === null) {
+ setPostStatus(status.DRAFT);
+ }
+ }, [title, body]);
+
+ useEffect(() => {
+ if (currentPostStatus !== status.DRAFT) return;
if ((title + body).length < 5) return;
if (debouncedValue === (data?.title || "") + data?.body) return;
if (allowUpdate) savePost();
@@ -374,8 +386,21 @@ const Create = () => {
}
}, [publishStatus, publishData, isPostScheduled, router]);
+ const handlePublish = () => {
+ if (isDisabled) return;
+ setOpen(true);
+ };
+
return (
<>
+
)}
diff --git a/app/(editor)/create/[[...paramsArr]]/navigation.tsx b/app/(editor)/create/[[...paramsArr]]/navigation.tsx
new file mode 100644
index 00000000..d1b5397d
--- /dev/null
+++ b/app/(editor)/create/[[...paramsArr]]/navigation.tsx
@@ -0,0 +1,160 @@
+"use client";
+
+import { api } from "@/server/trpc/react";
+import { Menu, Transition } from "@headlessui/react";
+import { BellIcon } from "@heroicons/react/20/solid";
+import { signOut } from "next-auth/react";
+import Link from "next/link";
+import { Fragment } from "react";
+import { type Session } from "next-auth";
+import Logo from "@/icons/logo.svg";
+import { type PostStatus, status } from "@/utils/post";
+
+type EditorNavProps = {
+ session: Session | null;
+ username: string | null;
+ postStatus: PostStatus | null;
+ unsavedChanges: boolean;
+ onPublish: () => void;
+ isDisabled: boolean;
+};
+
+const EditorNav = ({
+ session,
+ username,
+ postStatus,
+ unsavedChanges,
+ onPublish,
+ isDisabled,
+}: EditorNavProps) => {
+ const { data: count } = api.notification.getCount.useQuery(undefined, {
+ enabled: !!session,
+ });
+
+ const userNavigation = [
+ { name: "Your Profile", href: `/${username || "settings"}` },
+ { name: "Settings", href: "/settings" },
+ { name: "Sign out", onClick: () => signOut() },
+ ];
+
+ const hasNotifications = !!count && count > 0;
+
+ const getStatusText = () => {
+ if (postStatus === null) return null;
+
+ switch (postStatus) {
+ case status.DRAFT:
+ return unsavedChanges ? "Draft - Unsaved changes" : "Draft - Saved";
+ case status.PUBLISHED:
+ return unsavedChanges ? "Published - Unsaved changes" : "Published";
+ case status.SCHEDULED:
+ return unsavedChanges ? "Scheduled - Unsaved changes" : "Scheduled";
+ default:
+ return null;
+ }
+ };
+
+ const statusText = getStatusText();
+
+ return (
+
+
+
+
+
+
+
+
+ {statusText && (
+
+ {statusText}
+
+ )}
+
+
+
+
+ {postStatus === status.PUBLISHED ? "Save changes" : "Publish"}
+
+
+ {session && (
+ <>
+
+
View notifications
+ {hasNotifications && (
+
+ )}
+
+
+
+
+
+ Open user menu
+ {session.user?.image ? (
+
+ ) : (
+
+ {session.user?.name?.[0] || "U"}
+
+ )}
+
+
+
+
+ {userNavigation.map((item) => (
+
+ {({ active }) =>
+ item.onClick ? (
+
+ {item.name}
+
+ ) : (
+
+ {item.name}
+
+ )
+ }
+
+ ))}
+
+
+
+ >
+ )}
+
+
+
+
+ );
+};
+
+export default EditorNav;
diff --git a/app/(editor)/create/[[...paramsArr]]/not-found.tsx b/app/(editor)/create/[[...paramsArr]]/not-found.tsx
new file mode 100644
index 00000000..dae055d0
--- /dev/null
+++ b/app/(editor)/create/[[...paramsArr]]/not-found.tsx
@@ -0,0 +1,7 @@
+import NotFound from "@/components/NotFound/NotFound";
+
+const notfound = () => {
+ return ;
+};
+
+export default notfound;
diff --git a/app/(editor)/create/[[...paramsArr]]/page.tsx b/app/(editor)/create/[[...paramsArr]]/page.tsx
new file mode 100644
index 00000000..8fd8a385
--- /dev/null
+++ b/app/(editor)/create/[[...paramsArr]]/page.tsx
@@ -0,0 +1,37 @@
+import { redirect } from "next/navigation";
+import Content from "./_client";
+import { getServerAuthSession } from "@/server/auth";
+
+export const metadata = {
+ title: "Edit Post - Codú",
+ description:
+ "Create and edit your articles with Codú's powerful writing editor. Share your knowledge and insights with the developer community.",
+ icons: [{ rel: "icon", url: "/favicon.ico" }],
+ publisher: "Codú",
+ applicationName: "Codú",
+ keywords: [
+ "article editor",
+ "writing",
+ "blogging",
+ "tech articles",
+ "developer content",
+ "programming",
+ "web development",
+ "coding tutorials",
+ "technical writing",
+ "knowledge sharing",
+ ],
+ metadataBase: new URL("https://www.codu.co"),
+ openGraph: {
+ images: "/images/og/home-og.png",
+ },
+};
+
+export default async function Page() {
+ const session = await getServerAuthSession();
+ if (!session) {
+ redirect("/get-started");
+ }
+
+ return ;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 9c60b2de..0f0fbd37 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,11 +1,17 @@
-import "../styles/globals.css";
+import { headers } from "next/headers";
+import "@/styles/globals.css";
import Fathom from "@/components/Fathom/Fathom";
-import React from "react";
import A11yProvider from "@/components/A11yProvider/A11yProvider";
import { Toaster } from "sonner";
import { CSPostHogProvider } from "./providers";
import dynamic from "next/dynamic";
+import ThemeProvider from "@/components/Theme/ThemeProvider";
+import { TRPCReactProvider } from "@/server/trpc/react";
+import AuthProvider from "@/context/AuthProvider";
+import ProgressBar from "@/components/ProgressBar/ProgressBar";
+import { PromptProvider } from "@/components/PromptService";
+
const PostHogPageView = dynamic(
() => import("@/components/PageViews/PageViews"),
{
@@ -62,7 +68,16 @@ export default async function RootLayout({
- {children}
+
+
+
+
+
+ {children}
+
+
+
+