Something went wrong. Try again.
CMU Coding Bootcamp
Something went wrong. Try again.
7.7 kB · 255 lines
TSX
at commit abd8d9c0
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256import { posts, type BlogPost } from "../lib/post";import { useRef, useState } from "react";import { useNavigate } from "react-router";import { useSearchParams } from "react-router";import { ContentState, convertFromRaw, convertToRaw, Editor, EditorState, RichUtils,} from "draft-js";import "draft-js/dist/Draft.css";
export function BlogPostForm({ post, onSubmit,}: { post: BlogPost | null; onSubmit: (post: BlogPost) => void;}) { const [postState, setPostState] = useState({ id: post?.id ?? posts.length, title: post?.title ?? "", summary: post?.summary ?? "", content: post?.content ?? "", author: post?.author ?? "", datePosted: post?.datePosted ?? new Date().toISOString().split("T")[0], }); const [missing, setMissing] = useState<string[]>([]); const [contentState, setContentState] = useState<EditorState>(() => { if (post?.content) { try { const rawContent = JSON.parse(post.content); return EditorState.createWithContent(convertFromRaw(rawContent)); } catch { // Fallback to plain text if JSON parsing fails return EditorState.createWithContent( ContentState.createFromText(post.content), ); } } return EditorState.createEmpty(); });
const editorRef = useRef<Editor>(null);
const navigate = useNavigate();
const handleChange = ( event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, ) => { const { name, value } = event.target; setPostState((prevState) => ({ ...prevState, [name]: value })); };
const handleMissing = () => { const missingFields = Object.entries(postState) .map(([key, value]) => (value === "" ? key : null)) .filter((key) => key !== null); setMissing(missingFields); };
const handleContentChange = (content: EditorState) => { setContentState(content); const rawContent = convertToRaw(content.getCurrentContent()); setPostState((prevState) => ({ ...prevState, content: JSON.stringify(rawContent), })); };
const handleInlineStyle = (style: string) => { setContentState((prevState) => RichUtils.toggleInlineStyle(prevState, style), ); };
const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => { event.preventDefault();
if ( !postState.title || !postState.summary || !postState.content || !postState.author ) { handleMissing(); return; }
onSubmit(postState); navigate("/"); };
return ( <form className="flex flex-col gap-4 dark:bg-slate-600 p-10 rounded-lg md:w-4xl w-md"> <label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2"> Title: <input type="text" name="title" className="border-gray-400 md:col-start-2 md:col-span-5 border rounded h-8 py-1 px-2 w-full" value={postState.title} onChange={handleChange} required /> </label> {missing.includes("title") && ( <p className="text-red-500">Title is required</p> )} <label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2"> Summary: <textarea name="summary" className="border-gray-400 md:col-start-2 md:col-span-5 border rounded min-h-16 h-auto py-1 px-2 w-full" value={postState.summary} onChange={handleChange} required /> </label> {missing.includes("summary") && ( <p className="text-red-500">Summary is required</p> )} <label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2"> Content: </label> <div className="md:grid md:grid-cols-6"> <div className="md:col-start-2 md:col-span-5"> <div className="flex gap-2 mb-2 border-b pb-2"> <button type="button" onMouseDown={(e) => { e.preventDefault(); handleInlineStyle("BOLD"); }} className={`px-3 py-1 border rounded ${ contentState.getCurrentInlineStyle().has("BOLD") ? "bg-blue-500 text-white" : "bg-gray-500" }`} > <strong>B</strong> </button> <button type="button" onMouseDown={(e) => { e.preventDefault(); handleInlineStyle("ITALIC"); }} className={`px-3 py-1 border rounded ${ contentState.getCurrentInlineStyle().has("ITALIC") ? "bg-blue-500 text-white" : "bg-gray-500" }`} > <em>I</em> </button> <button type="button" onMouseDown={(e) => { e.preventDefault(); handleInlineStyle("UNDERLINE"); }} className={`px-3 py-1 border rounded ${ contentState.getCurrentInlineStyle().has("UNDERLINE") ? "bg-blue-500 text-white" : "bg-gray-500" }`} > <u>U</u> </button> </div>
{/* Editor */} <div className="border-gray-400 border rounded p-2 cursor-text min-h-48 pointer-events-auto select-text" onMouseDown={(e) => { if (e.target === e.currentTarget) { e.preventDefault(); editorRef.current?.focus(); } }} > <Editor ref={editorRef} editorState={contentState} onChange={handleContentChange} placeholder="Write your content here..." /> </div> </div> </div> {missing.includes("content") && ( <p className="text-red-500">Content is required</p> )} <label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2"> Author: <input type="text" name="author" className="border-gray-400 md:col-start-2 md:col-span-5 border rounded h-8 py-1 px-2 w-full" value={postState.author} onChange={handleChange} required /> </label> {missing.includes("author") && ( <p className="text-red-500">Author is required</p> )} <label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2"> Date Posted: <input type="date" name="datePosted" value={postState.datePosted} onChange={handleChange} required /> </label> <button type="button" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded cursor-pointer" onClick={handleSubmit} > {post ? "Save Post" : "Create Post"} </button> </form> );}
export function NewPostLayout() { const searchParams = useSearchParams()[0]; const postId = parseInt(searchParams.get("postId") ?? "-1"); const post = postId < 0 || postId >= posts.length ? null : posts.find((p) => p.id === postId)!;
return ( <div className="flex flex-col gap-4 items-center justify-center dark:bg-slate-700 p-10 h-screen"> <h1 className="text-2xl font-bold">New Post</h1> <BlogPostForm post={post} onSubmit={(post) => { if (post.id < posts.length) { posts[post.id] = post; } else { posts.push(post); } }} /> </div> );}