Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[36] Add post description #37

Merged
merged 8 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ export async function createPost(formData: FormData) {
const userId = String(formData.get("userId"));
const title = String(formData.get("title"));
const content = String(formData.get("content"));
const description = String(formData.get("description"));

try {
await prisma.$transaction(async (tx) => {
const post = await tx.post.create({
data: {
title,
content,
description,
ownerId: userId,
},
select: {
Expand Down Expand Up @@ -78,7 +80,38 @@ export async function updatePostTitle(
} catch (error) {
console.error(error);
return {
message: "Error updating title!",
message: "Error updating the title!",
postSlug: "",
error: true,
completed: false,
};
}
}

export async function updatePostDescription(
prevState: EditableStateProps,
formData: FormData
) {
const postId = String(formData.get("postId"));
const description = String(formData.get("description"));

try {
await prisma.post.update({
where: { id: postId },
data: {
description,
},
});

return {
message: "Description was successfully updated!",
error: false,
completed: true,
};
} catch (error) {
console.error(error);
return {
message: "Error updating the description!",
postSlug: "",
error: true,
completed: false,
Expand Down Expand Up @@ -115,7 +148,7 @@ export async function updatePostBody(
} catch (error) {
console.error(error);
return {
message: "Error updating content!",
message: "Error updating the content.",
postSlug: "",
error: true,
completed: false,
Expand Down
16 changes: 14 additions & 2 deletions components/PostForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ type PostFormProps = {
};

const initialState = {
message: '',
}
message: "",
};
export default function PostForm({ userId, action, children }: PostFormProps) {
const [state, formAction] = useActionState(
(state: { message: string }, formData: FormData) => action(formData),
Expand Down Expand Up @@ -49,6 +49,18 @@ export default function PostForm({ userId, action, children }: PostFormProps) {
/>
</label>
</p> */}
<p>
<label htmlFor="description" className="sr-only">
Description
</label>
<input
type="text"
id="description"
name="description"
className="block w-full"
placeholder="Description"
/>
</p>
<Editor />
{state?.message && <p className="text-red-500">{state.message}</p>}
{children}
Expand Down
4 changes: 2 additions & 2 deletions components/posts/EditableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function EditableBody({
) : (
<div className="flex items-center text-left gap-x-2 relative">
{isPending && (
<span className="absolute -left-8">
<span className="absolute -left-8 top-0">
<Spinner />
</span>
)}
Expand All @@ -64,7 +64,7 @@ export default function EditableBody({
disabled={isPending}
>
<div
className="line-clamp-3 text-lg/6 text-gray-600"
className="text-lg/6 text-gray-600 font-normal"
dangerouslySetInnerHTML={{
__html: postContent,
}}
Expand Down
75 changes: 75 additions & 0 deletions components/posts/EditableDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client";

import useEditableFields from "@/hooks/use-editableFields";
import Form from "next/form";
import Spinner from "../icons/spinner";
import { updatePostDescription as action } from "@/app/actions";

export default function EditableDescription({
content,
postId,
}: {
content: string;
postId: string;
}) {
const {
state,
isPending,
isEditing,
value,
getFormProps,
getFieldProps,
getButtonProps,
} = useEditableFields({ action, initialValue: content, type: "textarea" });

return (
<>
<div className="mt-3 mb-8 text-3xl/8 font-semibold text-gray-900">
{isEditing ? (
<Form {...getFormProps()}>
<p>
<span>
<label
htmlFor="description"
className="sr-only"
aria-label="Post description"
title="Post description"
>
Post description
</label>
<small className="field-tip">
TIP: To save, hit ENTER or to cancel, hit ESC
</small>
</span>
{/* TODO: Change to a textarea field */}
<input
{...getFieldProps({
className: "w-full mb-8 text-lg font-normal",
id: "description",
name: "description",
})}
/>
</p>
<input type="hidden" name="postId" value={postId} />
</Form>
) : (
<div className="flex items-center text-left gap-x-2 relative">
{isPending && (
<span className="absolute -left-6 top-2">
<Spinner />
</span>
)}
<button
{...getButtonProps({ className: "w-3/4" })}
disabled={isPending}
className="text-lg text-left font-normal"
>
{value}
</button>
</div>
)}
</div>
{state?.error && <p className="text-red-500">{state.message}</p>}
</>
);
}
31 changes: 31 additions & 0 deletions components/posts/PostFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { User } from "@prisma/client";
import Image from "next/image";

interface PostFooterProps {
user: Pick<User, "id" | "name" | "image">;
}

export default function PostFooter({ user }: PostFooterProps) {
return (
<div className="relative mt-8 flex items-center gap-x-4">
{user.image && (
<Image
alt=""
src={user.image}
className="size-10 rounded-full bg-gray-50"
width={40}
height={40}
/>
)}
<div className="text-sm/6">
<p className="font-semibold text-gray-900">
{/* TODO: Add functionality to link to user's profile */}
<a href="#">
<span className="absolute inset-0" />
{user.name}
</a>
</p>
</div>
</div>
);
}
53 changes: 6 additions & 47 deletions components/posts/PostFullView.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { formatDate, formatValue } from "@/utils/posts";
import { type User, type Post } from "@prisma/client";
import Image from "next/image";
import Link from "next/link";
import EditableTitle from "./EditableTitle";
import EditableBody from "./EditableBody";
import EditableDescription from "./EditableDescription";
import PostFooter from "./PostFooter";

interface PostFullProps {
post: Post;
Expand All @@ -16,30 +16,6 @@ export default function PostFullView({ post, user, slug }: PostFullProps) {
const content = formatValue(post.content);
const isEditable = !slug ? post.ownerId === user.id : false;

function Title() {
return slug ? (
<h3 className="mt-3 text-3xl/8 font-semibold text-gray-900">
<Link href={`/home/posts/${post.slug}`} className="hover:text-gray-600">
<span className="absolute inset-0" />
{post.title}
</Link>
</h3>
) : (
<EditableTitle title={post.title} postId={post.id} />
);
}

function Body() {
return slug ? (
<div
className="mt-5 line-clamp-3 text-lg/6 text-gray-600"
dangerouslySetInnerHTML={{ __html: content }}
/>
) : (
<EditableBody content={content} postId={post.id} />
);
}

return (
<div className="container">
{isEditable && (
Expand All @@ -56,29 +32,12 @@ export default function PostFullView({ post, user, slug }: PostFullProps) {
</time>
<div className="group relative">
<header>
<Title />
<EditableTitle title={post.title} postId={post.id} />
</header>
<Body />
</div>
<div className="relative mt-8 flex items-center gap-x-4">
{user.image && (
<Image
alt=""
src={user.image}
className="size-10 rounded-full bg-gray-50"
width={40}
height={40}
/>
)}
<div className="text-sm/6">
<p className="font-semibold text-gray-900">
<a href="#">
<span className="absolute inset-0" />
{user.name}
</a>
</p>
</div>
<EditableDescription content={post.description} postId={post.id} />
<EditableBody content={content} postId={post.id} />
</div>
<PostFooter user={user} />
</article>
</div>
);
Expand Down
47 changes: 47 additions & 0 deletions components/posts/PostPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { formatDate } from "@/utils/posts";
import { type User, type Post } from "@prisma/client";
import Link from "next/link";
import PostFooter from "./PostFooter";

interface PostPreviewProps {
post: Post;
user: Pick<User, "id" | "name" | "email" | "image">;
slug?: string;
}

export default function PostPreview({ post, user }: PostPreviewProps) {
const formattedDate = formatDate(post.updatedAt);

return (
<div className="container">
<article
key={post.id}
className="[&:not(:last-child)]:border-b-1 border-gray-200"
>
<time dateTime={formattedDate} className="text-gray-500 italic text-xs">
{formattedDate}
</time>
<div className="group relative">
<header>
<h3 className="mt-3 text-3xl/8 font-semibold text-gray-900">
<Link
href={`/home/posts/${post.slug}`}
className="hover:text-gray-600"
>
<span className="absolute inset-0" />
{post.title}
</Link>
</h3>
</header>
<textarea
className="mt-5 line-clamp-3 text-lg/6 text-black w-full"
readOnly
draggable={false}
value={post.description}
/>
</div>
<PostFooter user={user} />
</article>
</div>
);
}
10 changes: 2 additions & 8 deletions components/posts/allUserPosts.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import { sortPostsByDateDesc } from "@/utils/posts";
import PostFullView from "./PostFullView";
import { type User, type Post } from "@prisma/client";
import PostPreview from "./PostPreview";

interface AllUserPostsProps {
posts: Post[];
user: Pick<User, "id" | "name" | "email" | "image">;
}

export default function AllUserPosts({ user, posts }: AllUserPostsProps) {
// TODO: Update to use PostShortView component
return sortPostsByDateDesc(posts).map((post) => (
<PostFullView
key={post.id}
post={post}
user={user}
slug={post.slug as string}
/>
<PostPreview key={post.id} post={post} user={user} />
));
}
Loading