Skip to content

Commit

Permalink
🍊Add publish functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislovelock committed Mar 23, 2024
1 parent 02e88ae commit 5126840
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 13 deletions.
23 changes: 10 additions & 13 deletions app/(main)/_components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"use client"
"use client";

import { useQuery } from "convex/react";
import { useParams } from "next/navigation";
Expand All @@ -11,16 +11,14 @@ import { Title } from "./title";
import { Banner } from "./banner";
import { Menu } from "./menu";
import { ModeToggle } from "@/components/mode-toggle";
import { Publish } from "./publish";

interface NavbarProps {
isCollapsed: boolean;
onResetWidth: () => void;
};
}

export const Navbar = ({
isCollapsed,
onResetWidth
}: NavbarProps) => {
export const Navbar = ({ isCollapsed, onResetWidth }: NavbarProps) => {
const params = useParams();

const document = useQuery(api.documents.getById, {
Expand All @@ -35,7 +33,7 @@ export const Navbar = ({
<Menu.Skeleton />
</div>
</nav>
)
);
}

if (document === null) {
Expand All @@ -54,15 +52,14 @@ export const Navbar = ({
)}
<div className="flex items-center justify-between w-full">
<Title initialData={document} />
<div>
<div className="flex items-center gap-x-2">
<Publish initialData={document}/>
<ModeToggle />
<Menu documentId={document._id} />
</div>
</div>
</nav>
{document.isArchived && (
<Banner documentId={document._id} />
)}
{document.isArchived && <Banner documentId={document._id} />}
</>
)
}
);
};
151 changes: 151 additions & 0 deletions app/(main)/_components/publish.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"use client";

import { useState } from "react";
import { useMutation } from "convex/react";
import { toast } from "sonner";
import { Check, Copy, Globe } from "lucide-react";

import { Doc } from "@/convex/_generated/dataModel";
import {
PopoverTrigger,
Popover,
PopoverContent
} from "@/components/ui/popover"
import { useOrigin } from "@/hooks/use-origin";
import { api } from "@/convex/_generated/api";
import { Button } from "@/components/ui/button";

interface PublishProps {
initialData: Doc<"documents">;
};

export const Publish = ({
initialData
}: PublishProps) => {
const origin = useOrigin();
const update = useMutation(api.documents.update);

const [copied, setCopied] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);

const url = `${origin}/preview/${initialData._id}`;

const onPublish = () => {
setIsSubmitting(true);

const promise = update({
id: initialData._id,
isPublished: true,
})
.finally(() => setIsSubmitting(false));

toast.promise(promise, {
loading: "Publishing...",
success: "Note published",
error: "Failed to publish note.",
});
}

const onUnpublish = () => {
setIsSubmitting(true);

const promise = update({
id: initialData._id,
isPublished: false,
})
.finally(() => setIsSubmitting(false));

toast.promise(promise, {
loading: "Unpublishing...",
success: "Note unpublished",
error: "Failed to unpublish note.",
});
};

const onCopy = () => {
navigator.clipboard.writeText(url);
setCopied(true);

setTimeout(() => {
setCopied(false);
}, 1000);
}

return (
<Popover>
<PopoverTrigger asChild>
<Button size="sm" variant="ghost">
Publish
{initialData.isPublished && (
<Globe
className="text-sky-500 w-4 h-4 ml-2"
/>
)}
</Button>
</PopoverTrigger>
<PopoverContent
className="w-72"
align="end"
alignOffset={8}
forceMount
>
{initialData.isPublished ? (
<div className="space-y-4">
<div className="flex items-center gap-x-2">
<Globe className="text-sky-500 animate-pulse h-4 w-4" />
<p className="text-xs font-medium text-sky-500">
This note is live on web.
</p>
</div>
<div className="flex items-center">
<input
className="flex-1 px-2 text-xs border rounded-l-md h-8 bg-muted truncate"
value={url}
disabled
/>
<Button
onClick={onCopy}
disabled={copied}
className="h-8 rounded-l-none"
>
{copied ? (
<Check className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
<Button
size="sm"
className="w-full text-xs"
disabled={isSubmitting}
onClick={onUnpublish}
>
Unpublish
</Button>
</div>
) : (
<div className="flex flex-col items-center justify-center">
<Globe
className="h-8 w-8 text-muted-foreground mb-2"
/>
<p className="text-sm font-medium mb-2">
Publish this note
</p>
<span className="text-xs text-muted-foreground mb-4">
Share your work with others.
</span>
<Button
disabled={isSubmitting}
onClick={onPublish}
className="w-full text-xs"
size="sm"
>
Publish
</Button>
</div>
)}
</PopoverContent>
</Popover>
);
}
72 changes: 72 additions & 0 deletions app/(public)/(routes)/preview/[documentId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";

import { useMutation, useQuery } from "convex/react";
import dynamic from "next/dynamic";
import { useMemo } from "react";

import { api } from "@/convex/_generated/api";
import { Id } from "@/convex/_generated/dataModel";
import { Toolbar } from "@/components/toolbar";
import { Cover } from "@/components/cover";
import { Skeleton } from "@/components/ui/skeleton";
import { useParams } from "next/navigation";

// interface DocumentIdPageProps {
// params: {
// documentId: Id<"documents">;
// };
// };

const DocumentIdPage = () => {
const Editor = useMemo(() => dynamic(() => import("@/components/editor"), { ssr: false }) ,[]);
const params = useParams();

const document = useQuery(api.documents.getById, {
documentId: params.documentId as Id<"documents">
});

const update = useMutation(api.documents.update);

const onChange = (content: string) => {
update({
id: params.documentId as Id<"documents">,
content
});
};

if (document === undefined) {
return (
<div>
<Cover.Skeleton />
<div className="md:max-w-3xl lg:max-w-4xl mx-auto mt-10">
<div className="space-y-4 pl-8 pt-4">
<Skeleton className="h-14 w-[50%]" />
<Skeleton className="h-4 w-[80%]" />
<Skeleton className="h-4 w-[40%]" />
<Skeleton className="h-4 w-[60%]" />
</div>
</div>
</div>
);
}

if (document === null) {
return <div>Not found</div>
}

return (
<div className="pb-40">
<Cover preview url={document.coverImage} />
<div className="md:max-w-3xl lg:max-w-4xl mx-auto">
<Toolbar preview initialData={document} />
<Editor
editable={false}
onChange={onChange}
initialContent={document.content}
/>
</div>
</div>
);
}

export default DocumentIdPage;
13 changes: 13 additions & 0 deletions app/(public)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const PublicLayout = ({
children
}: {
children: React.ReactNode;
}) => {
return (
<div className="h-full dark:bg-[#1F1F1F]">
{children}
</div>
);
}

export default PublicLayout;
18 changes: 18 additions & 0 deletions hooks/use-origin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { url } from "inspector";
import { useEffect, useState } from "react";

// This hook is used to get the origin url of the current window location.
export const useOrigin = () => {
const [mounted, setMounted] = useState(false);
const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return "";
}

return origin;
}

0 comments on commit 5126840

Please sign in to comment.