From b871cd1a54f4cbca8eeeeca21ccb10ab6c8d0197 Mon Sep 17 00:00:00 2001 From: Turingon Date: Tue, 6 Aug 2024 20:01:23 +0200 Subject: [PATCH] Finished with the tutorial --- next.config.js | 2 +- public/external.png | Bin 0 -> 2042 bytes public/image.png | Bin 0 -> 2793 bytes public/plus.png | Bin 0 -> 1627 bytes public/video.png | Bin 0 -> 2322 bytes src/app/api/posts/[slug]/route.js | 3 +- src/app/api/posts/route.js | 72 +++++++---- src/app/posts/[slug]/page.jsx | 2 +- src/app/write/page.jsx | 199 ++++++++++++++++++++--------- src/app/write/writePage.module.css | 96 ++++++++------ src/utils/firebase.js | 17 +++ 11 files changed, 264 insertions(+), 127 deletions(-) create mode 100644 public/external.png create mode 100644 public/image.png create mode 100644 public/plus.png create mode 100644 public/video.png create mode 100644 src/utils/firebase.js diff --git a/next.config.js b/next.config.js index 2b46123..225ad4b 100644 --- a/next.config.js +++ b/next.config.js @@ -2,7 +2,7 @@ const nextConfig = { images: { domains: - ["lh3.googleusercontent.com"] + ["lh3.googleusercontent.com", "firebasestorage.googleapis.com"] } } diff --git a/public/external.png b/public/external.png new file mode 100644 index 0000000000000000000000000000000000000000..a244b5dd8b819445efe6784d5fc15d9b41d6011b GIT binary patch literal 2042 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 VNe`T!Ewz0`hKF#2&jyLM{TmrtBrN~{ literal 0 HcmV?d00001 diff --git a/public/plus.png b/public/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..19c093a76156c26f4d295672dcf66fdb2fc2bc80 GIT binary patch literal 1627 zcmb_dU1-x#6pqq~4kvq3a4!Xw3YzAx?V6az)pgU#95SoUJ$c*Qc9As6lG`@Ei3%!$ zg5ZNN(5ERr3?CHVgfU+fe70hxJsAe8U-Li%GDN1%Fd|QSHgx#`kIFg%+cpehIComIHTEq39Y^z0SAk$lS z#ZRfoFhZ8ZFtu7OS`(tCU1B&<6d9Ihc%H@{v~%1*z@-gm6J`ohWuQ~AH4AB`5hm0i zZ&pw$67jtjLQ)IOay+hOj19eDraTqSj1)@|764Hb*+KHLxQBtZ;tld8Zup`2x)|DE$<6qI8ops z6{21g7nT7kk%A3Kv9L=@t4>4UB$EShqzS=d5Q7D52j-4jFcleqj#;q_u(1Q`gxxj- z2&xLt@&e5!X+9}4O(wsHa^2A5qO7U_K9PV}Xj+W%DjnmvA}!{HBAr)(*a_l!P~=&k zYhlY0rrtazit}+k5f`}_*D1!6i+eodk}Wy2QWz#_DZJ0Vh%LJRq0B+Kex(|e^*2n!BCtyYRX^(4LvPc4*?fC{K)s#`N2AHru1&_+@-gQ@buxS zmhanQC)Q6bXUX&TuFnjGW?#14m=v7Hk2Yni-(F6eZ)RsUOi@E!&zG?#SAP0(_|nDA eGarS~%{%X$?YJ_wb_@AvP}!dT%=oUOCw~H&%JeA! literal 0 HcmV?d00001 diff --git a/public/video.png b/public/video.png new file mode 100644 index 0000000000000000000000000000000000000000..f0d8b431ecdc357bc783c0042128fde02c64594f GIT binary patch literal 2322 zcmb_ec~BE~6yAVT3<{2bbdVw)t5G3r*c^~0Ajlyog2>TuRM})V5D7`h?vhnI5UCD2 zYP~=OJX%E|PC;}~rVb$3);sFNt5vBG2RmY;R9g`h>?UGL(DskEnc3`b-|@ZozW4Uc zu3MBiZvxYs2>`%^__*lB^gY6IIe$R^ACedTOy9<-<1%mnaQ)tLF)I9Br_jPsB1TL^ zsR{}(B>_+x7=@!FV{t7rIcf9GMl?8n(sit^ZS_)g#_2OP8&$tEIoR zg6=QZuD`SljwFdZl_op^DM0+9u_BSh@U3uEVgG#$>1AOzgSPA_ME%b6(Z;ZKQdF8$ zl}ZsFg(!;<5ja}>Pf6NVkCk0|_l%fY(6+n6oAc*Z0)S(Ad~{?gwaZ+cw026G&zU(H zRl#d6MhtaqCzH6o<-L*1kd74#B2=YWP33!<)|Hl-yF2{Z7uz>vk4j>Tw`=F zqE2xxaoarUMX@kl*QDbKfu8$&^@0mGb-}trb;(bD*WGTtzB_-$hp)G%D2tEW3b;Eq zk)d6+6dRoMOGcMZV`o;UusSQxP~y@ug)|HluWbA{UY3;Khx92 zFiBe-11>XSy4KZg8NX?jX;;%4UIybm@mWS^J#W4$bI*(M)V|Q}G_zYt!ZfY-q`i#u z1}}C}y`w;VNq;-5BKARk&;9lBxk*tbCvn4U2Nuc7zPh__dV90zk0r-4h7K5Kwr(Sa zPHyS-{c5m#|J^yS_WP0sryO+ovVyPqC71P{muemy@!GPm@viTk-ug$PsicAI>Y+03 zY!*+JIeH$=(tylR(ZHA zYJijFb;{ try{ - const post = await prisma.post.findUnique({ + const post = await prisma.post.update({ where: {slug}, + data: {views:{increment:1}}, include: {user: true}, }); return new NextResponse(JSON.stringify(post, {status: 200})); diff --git a/src/app/api/posts/route.js b/src/app/api/posts/route.js index dd3ba74..97aec74 100644 --- a/src/app/api/posts/route.js +++ b/src/app/api/posts/route.js @@ -1,34 +1,58 @@ +import { getAuthSession } from "@/utils/auth"; import prisma from "@/utils/connect"; import { NextResponse } from "next/server"; -export const GET = async (req)=>{ +export const GET = async (req) => { + const { searchParams } = new URL(req.url); - const {searchParams} = new URL(req.url); - const page = searchParams.get("page"); - const cat = searchParams.get("cat"); - const POST_PER_PAGE = 2; + const page = searchParams.get("page"); + const cat = searchParams.get("cat"); - const query = { - take: POST_PER_PAGE, - skip: POST_PER_PAGE * (page - 1), - where: { - ...(cat && {catSlug: cat}), - }, - }; + const POST_PER_PAGE = 2; - try{ + const query = { + take: POST_PER_PAGE, + skip: POST_PER_PAGE * (page - 1), + where: { + ...(cat && { catSlug: cat }), + }, + }; + + try { + const [posts, count] = await prisma.$transaction([ + prisma.post.findMany(query), + prisma.post.count({ where: query.where }), + ]); + return new NextResponse(JSON.stringify({ posts, count }, { status: 200 })); + } catch (err) { + console.log(err); + return new NextResponse( + JSON.stringify({ message: "Something went wrong!" }, { status: 500 }) + ); + } +}; - const [posts,count] = await prisma.$transaction( - [ - prisma.post.findMany(query), - prisma.post.count({where:query.where}), - ] - ); - return new NextResponse(JSON.stringify({posts, count}, {status:200})); +// CREATE A POST +export const POST = async (req) => { + const session = await getAuthSession(); + if (!session) { + return new NextResponse( + JSON.stringify({ message: "Not Authenticated!" }, { status: 401 }) + ); + } - } catch(err) { - console.log(err); - return new NextResponse(JSON.stringify({message: "Something went wrong!"}, {status:500})); - } + try { + const body = await req.json(); + const post = await prisma.post.create({ + data: { ...body, userEmail: session.user.email }, + }); + + return new NextResponse(JSON.stringify(post, { status: 200 })); + } catch (err) { + console.log(err); + return new NextResponse( + JSON.stringify({ message: "Something went wrong!" }, { status: 500 }) + ); + } }; \ No newline at end of file diff --git a/src/app/posts/[slug]/page.jsx b/src/app/posts/[slug]/page.jsx index 820dc1d..be4b2b1 100644 --- a/src/app/posts/[slug]/page.jsx +++ b/src/app/posts/[slug]/page.jsx @@ -5,7 +5,7 @@ import Comments from "@/components/comments/Comments"; const getData = async (slug) => { const res = await fetch( - `http://localhost:3000/api/posts/${slug}`, + `http://localhost:3000/api/posts/${slug}?popular=true`, { cache: "no-store", } diff --git a/src/app/write/page.jsx b/src/app/write/page.jsx index a912a1d..3630df0 100644 --- a/src/app/write/page.jsx +++ b/src/app/write/page.jsx @@ -1,71 +1,154 @@ "use client"; -import styles from "./writePage.module.css"; import Image from "next/image"; -import { useState } from "react"; -import ReactQuill from "react-quill"; +import styles from "./writePage.module.css"; +import { useEffect, useState } from "react"; import "react-quill/dist/quill.bubble.css"; -import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; +import { useSession } from "next-auth/react"; +import { + getStorage, + ref, + uploadBytesResumable, + getDownloadURL, +} from "firebase/storage"; +import { app } from "@/utils/firebase"; +import ReactQuill from "react-quill"; const WritePage = () => { + const { status } = useSession(); + const router = useRouter(); - const {status} = useSession(); - const router = useRouter(); - const [file, setFile] = useState(null); - const [open, setOpen] = useState(false); - const [value, setValue] = useState(""); + const [open, setOpen] = useState(false); + const [file, setFile] = useState(null); + const [media, setMedia] = useState(""); + const [value, setValue] = useState(""); + const [title, setTitle] = useState(""); + const [catSlug, setCatSlug] = useState(""); - if (status === "loading") { - return
Loading...
- } - if (status === "authenticated") { - router.push("/"); + useEffect(() => { + const storage = getStorage(app); + const upload = () => { + const name = new Date().getTime() + file.name; + const storageRef = ref(storage, name); + + const uploadTask = uploadBytesResumable(storageRef, file); + + uploadTask.on( + "state_changed", + (snapshot) => { + const progress = + (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log("Upload is " + progress + "% done"); + switch (snapshot.state) { + case "paused": + console.log("Upload is paused"); + break; + case "running": + console.log("Upload is running"); + break; + } + }, + (error) => {}, + () => { + getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { + setMedia(downloadURL); + }); + } + ); + }; + + file && upload(); + }, [file]); + + if (status === "loading") { + return
Loading...
; + } + + if (status === "unauthenticated") { + router.push("/"); + } + + const slugify = (str) => + str + .toLowerCase() + .trim() + .replace(/[^\w\s-]/g, "") + .replace(/[\s_-]+/g, "-") + .replace(/^-+|-+$/g, ""); + + const handleSubmit = async () => { + const res = await fetch("/api/posts", { + method: "POST", + body: JSON.stringify({ + title, + desc: value, + img: media, + slug: slugify(title), + catSlug: catSlug || "style", //If not selected, choose the general category + }), + }); + + if (res.status === 200) { + const data = await res.json(); + router.push(`/posts/${data.slug}`); } + }; - return ( -
- -
- - {open && ( -
- setFile(e.target.files[0])} - style={ { display: "none" }} - /> - - - - - - - -
- )} - -
- -
- ) -} + return ( +
+ setTitle(e.target.value)} + /> + +
+ + {open && ( +
+ setFile(e.target.files[0])} + style={{ display: "none" }} + /> + + + +
+ )} + +
+ +
+ ); +}; export default WritePage; \ No newline at end of file diff --git a/src/app/write/writePage.module.css b/src/app/write/writePage.module.css index 811fc16..4ca4895 100644 --- a/src/app/write/writePage.module.css +++ b/src/app/write/writePage.module.css @@ -1,63 +1,75 @@ -.container { +.container{ +position: relative; +display: flex; +flex-direction: column; +} +.select{ +margin-bottom: 50px; +padding: 10px 20px; +margin-left: 50px; +width: max-content; } .editor { - display: flex; - gap: 20px; - height: 700px; - position: relative; +display: flex; +gap: 20px; +height: 700px; +position: relative; } -.button, .addButton{ - width: 36px; - height: 36px; - border-radius: 50%; - background-color: transparent; - border: 1px solid var(--textColor); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; +.button, +.addButton { +width: 36px; +height: 36px; +border-radius: 50%; +background-color: transparent; +border: 1px solid var(--textColor); +display: flex; +align-items: center; +justify-content: center; +cursor: pointer; } -.addButton{ - border-color: #1a8917; +.addButton { +border-color: #1a8917; } -.add{ - display: flex; - gap: 20px; - background-color: var(--bg); - position: absolute; - z-index: 999; - width: 100%; - left: 50px; +.add { +display: flex; +gap: 20px; +background-color: var(--bg); +position: absolute; +z-index: 999; +width: 100%; +left: 50px; } .input{ - padding: 50px; - font-size: 64px; - border: none; - outline: none; - background-color: transparent; +padding: 50px; +font-size: 64px; +border: none; +outline: none; +background-color: transparent; +color: var(--textColor); } .input::placeholder{ - color: #b3b3b1; +color: #b3b3b1; } -.textArea{ - width: 100%; + +.textArea { +width: 100%; } .publish{ - position: absolutee; - top: 30px; - right: 20px; - padding: 10px 20px; - border: none; - background-color: #1a8917; - color: white; - cursor: pointer; - border-radius: 20px; +position: absolute; +top: 0px; +right: 0px; +padding: 10px 20px; +border: none; +background-color: #1a8917; +color: white; +cursor: pointer; +border-radius: 20px; } \ No newline at end of file diff --git a/src/utils/firebase.js b/src/utils/firebase.js new file mode 100644 index 0000000..e1bde38 --- /dev/null +++ b/src/utils/firebase.js @@ -0,0 +1,17 @@ +// Import the functions you need from the SDKs you need +import { initializeApp } from "firebase/app"; +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries + +// Your web app's Firebase configuration +const firebaseConfig = { + apiKey: process.env.FIREBASE, + authDomain: "blog-62dc6.firebaseapp.com", + projectId: "blog-62dc6", + storageBucket: "blog-62dc6.appspot.com", + messagingSenderId: "261518728912", + appId: "1:261518728912:web:c02c5f459f32f23eb948d5" +}; + +// Initialize Firebase +export const app = initializeApp(firebaseConfig); \ No newline at end of file