diff --git a/backend/src/routes/posts.ts b/backend/src/routes/posts.ts index dc64c20..dd3ae81 100644 --- a/backend/src/routes/posts.ts +++ b/backend/src/routes/posts.ts @@ -49,15 +49,20 @@ postRouter.post("/", async (c) => { } const userId = c.get("userId"); - const blog = await prisma.post.create({ - data: { - title: body.title, - content: body.content, - author_id: userId, - }, - }); + try { + const blog = await prisma.post.create({ + data: { + title: body.title, + content: body.content, + author_id: userId, + }, + }); - return c.json({ id: blog.id }); + return c.json({ id: blog.id, msg: "done" }); + } catch (e) { + c.status(411); + return c.json({ msg: "error occured while publishing the blog", error: e }); + } }); postRouter.get("/bulk", async (c) => { @@ -86,6 +91,16 @@ postRouter.get("/:id", async (c) => { where: { id: id, }, + select: { + content: true, + title: true, + id: true, + author: { + select: { + name: true, + }, + }, + }, }); return c.json({ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5d63f23..f1c9c4f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@pushkar1713/week13-common": "^1.0.1", "axios": "^1.7.7", + "jodit-react": "^4.1.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.1" @@ -1632,6 +1633,15 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/autobind-decorator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", + "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==", + "engines": { + "node": ">=8.10", + "npm": ">=6.4.1" + } + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -2725,6 +2735,26 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jodit": { + "version": "4.2.27", + "resolved": "https://registry.npmjs.org/jodit/-/jodit-4.2.27.tgz", + "integrity": "sha512-cqqeunB3HMElnocVhs5Qq2bhgpMIT2vKQPBpKcOTWKvX6GJ0GYAIneMEf43lphJuo+119CvBE8YgljD5iTfsAQ==", + "dependencies": { + "autobind-decorator": "^2.4.0" + } + }, + "node_modules/jodit-react": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/jodit-react/-/jodit-react-4.1.2.tgz", + "integrity": "sha512-Hs1evpM1IK5zvy/5m5Gk819L8aC+9EmEdQvCoLHVUr/R3vtH4nYFD6wsMRj3ur3J4ZHhaSBjt0N3R7ggwP405Q==", + "dependencies": { + "jodit": "^4.2.10" + }, + "peerDependencies": { + "react": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index cd9dcc9..cbf8c4f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "@pushkar1713/week13-common": "^1.0.1", "axios": "^1.7.7", + "jodit-react": "^4.1.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.1" diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png new file mode 100644 index 0000000..2297ee7 Binary files /dev/null and b/frontend/src/assets/logo.png differ diff --git a/frontend/src/components/appbar.tsx b/frontend/src/components/appbar.tsx new file mode 100644 index 0000000..0379823 --- /dev/null +++ b/frontend/src/components/appbar.tsx @@ -0,0 +1,28 @@ +import { Avatar } from "./blogCard"; +import { Link } from "react-router-dom"; +import Logo from "../assets/logo.png"; + +export const Appbar = () => { + return ( +
+ + + +
+ + + + + +
+
+ ); +}; diff --git a/frontend/src/components/blogCard.tsx b/frontend/src/components/blogCard.tsx index 7fb7a44..b2f85b2 100644 --- a/frontend/src/components/blogCard.tsx +++ b/frontend/src/components/blogCard.tsx @@ -1,39 +1,72 @@ +import { Link } from "react-router-dom"; + interface blogCardProps { authorName: String; title: String; content: String; date: String; + id: String; } export const BlogCard = ({ + id, authorName, title, content, date, }: blogCardProps) => { return ( -
-
-
-
- + +
+
+
+
+ +
+
{authorName}
+ {date} +
+
+ {title} +
+
{`Reading time : ${Math.ceil( + content.length / 200 + )} minutes`}
+
+ {content.length > 100 ? ( +
+ ) : ( +
+ )}
-
{authorName}
- {date}
-
{title}
-
{content.length > 100 ? content.slice(0, 200) : content}...
-
{`Reading time : ${Math.ceil(content.length / 200)} minutes`}
-
-
+ ); }; -const Avatar = ({ name }: { name: String }) => { +export function Avatar({ + name, + size = "small", +}: { + name: String; + size?: "small" | "big"; +}) { return ( -
- {name[0]} +
+ + {name[0]} +
); -}; +} diff --git a/frontend/src/components/footer.tsx b/frontend/src/components/footer.tsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/components/fullBlog.tsx b/frontend/src/components/fullBlog.tsx new file mode 100644 index 0000000..e98c154 --- /dev/null +++ b/frontend/src/components/fullBlog.tsx @@ -0,0 +1,42 @@ +import { Blogs } from "../hooks/hooks"; +import { Avatar } from "./blogCard"; + +export const FullBlog = ({ blog }: { blog: Blogs }) => { + return ( +
+
+
+
+
+ {blog.title} +
+
+ Published on 2nd December 2023 +
+
+
+
+
Author
+
+
+ +
+
+
+ {blog.author.name || "Anonymous"} +
+
+ Your first blog posts won’t be perfect, but you just have to + do it. You have to start somewhere — Shane Barker +
+
+
+
+
+
+
+ ); +}; diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/hooks/useBlog.tsx b/frontend/src/hooks/hooks.tsx similarity index 57% rename from frontend/src/hooks/useBlog.tsx rename to frontend/src/hooks/hooks.tsx index fc6cca8..bd88f40 100644 --- a/frontend/src/hooks/useBlog.tsx +++ b/frontend/src/hooks/hooks.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import axios from "axios"; import { BACKEND_URL } from "../config"; -interface Blogs { +export interface Blogs { content: string; title: string; id: string; @@ -11,6 +11,28 @@ interface Blogs { }; } +export const userBlogs = ({ id }: { id: string }) => { + const [loading, setLoading] = useState(true); + const [blog, setBlog] = useState(); + + useEffect(() => { + axios + .get(`${BACKEND_URL}/api/v1/blog/${id}`, { + headers: { + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }) + .then((response) => { + setBlog(response.data.blog); + setLoading(false); + }); + }, [id]); + return { + loading, + blog, + }; +}; + export const useBlog = () => { const [loading, setLoading] = useState(true); const [blogs, setBlogs] = useState([]); diff --git a/frontend/src/pages/blog.tsx b/frontend/src/pages/blog.tsx index 1861607..d48b60d 100644 --- a/frontend/src/pages/blog.tsx +++ b/frontend/src/pages/blog.tsx @@ -1,8 +1,30 @@ +import { Appbar } from "../components/appbar"; +import { FullBlog } from "../components/fullBlog"; +import { userBlogs } from "../hooks/hooks"; +import { useParams } from "react-router-dom"; + +// atomFamilies/selectorFamilies const Blog = () => { + const { id } = useParams(); + const { loading, blog } = userBlogs({ + id: id || "", + }); + + if (loading || !blog) { + return ( +
+ +
+
loading...
+
+
+ ); + } return ( - <> -
blog
- +
+ + +
); }; diff --git a/frontend/src/pages/blogs.tsx b/frontend/src/pages/blogs.tsx index 9705c0f..8fd86e8 100644 --- a/frontend/src/pages/blogs.tsx +++ b/frontend/src/pages/blogs.tsx @@ -1,5 +1,6 @@ +import { Appbar } from "../components/appbar"; import { BlogCard } from "../components/blogCard"; -import { useBlog } from "../hooks/useBlog"; +import { useBlog } from "../hooks/hooks"; export const Blogs = () => { const { loading, blogs } = useBlog(); @@ -10,12 +11,17 @@ export const Blogs = () => { return (
+ +

+ Pens and Pixels Blog +

{blogs.map((blog) => ( ))}
diff --git a/frontend/src/pages/home.tsx b/frontend/src/pages/home.tsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/publish.tsx b/frontend/src/pages/publish.tsx new file mode 100644 index 0000000..813d3ee --- /dev/null +++ b/frontend/src/pages/publish.tsx @@ -0,0 +1,93 @@ +import axios from "axios"; +import { BACKEND_URL } from "../config"; +import { useNavigate } from "react-router-dom"; +import { useState, useRef } from "react"; +import JoditEditor from "jodit-react"; +import { Appbar } from "../components/appbar"; + +export const Publish = () => { + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const navigate = useNavigate(); + const editor = useRef(null); + + return ( +
+ +

+ Start Writing +

+
+
+ { + setTitle(e.target.value); + }} + type="text" + className=" bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 my-6" + placeholder="Title" + /> + {/* { + setContent(e.target.value); + }} + /> */} + { + setContent(newContent); + }} + /> + +
+
+
+ ); +}; + +// function TextEditor({ +// onChange, +// }: { +// onChange: (e: ChangeEvent) => void; +// }) { +// return ( +//
+//
+//
+//
+// +//