Finished with the tutorial

This commit is contained in:
Turingon 2024-08-06 20:01:23 +02:00
parent 409bc2c3cc
commit b871cd1a54
11 changed files with 264 additions and 127 deletions

View file

@ -2,7 +2,7 @@
const nextConfig = { const nextConfig = {
images: { images: {
domains: domains:
["lh3.googleusercontent.com"] ["lh3.googleusercontent.com", "firebasestorage.googleapis.com"]
} }
} }

BIN
public/external.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
public/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
public/video.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -9,8 +9,9 @@ export const GET = async (req, {params})=>{
try{ try{
const post = await prisma.post.findUnique({ const post = await prisma.post.update({
where: {slug}, where: {slug},
data: {views:{increment:1}},
include: {user: true}, include: {user: true},
}); });
return new NextResponse(JSON.stringify(post, {status: 200})); return new NextResponse(JSON.stringify(post, {status: 200}));

View file

@ -1,34 +1,58 @@
import { getAuthSession } from "@/utils/auth";
import prisma from "@/utils/connect"; import prisma from "@/utils/connect";
import { NextResponse } from "next/server"; 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 page = searchParams.get("page"); const cat = searchParams.get("cat");
const cat = searchParams.get("cat");
const POST_PER_PAGE = 2;
const query = { const POST_PER_PAGE = 2;
take: POST_PER_PAGE,
skip: POST_PER_PAGE * (page - 1),
where: {
...(cat && {catSlug: cat}),
},
};
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( // CREATE A POST
[ export const POST = async (req) => {
prisma.post.findMany(query), const session = await getAuthSession();
prisma.post.count({where:query.where}),
]
);
return new NextResponse(JSON.stringify({posts, count}, {status:200}));
if (!session) {
return new NextResponse(
JSON.stringify({ message: "Not Authenticated!" }, { status: 401 })
);
}
} catch(err) { try {
console.log(err); const body = await req.json();
return new NextResponse(JSON.stringify({message: "Something went wrong!"}, {status:500})); 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 })
);
}
}; };

View file

@ -5,7 +5,7 @@ import Comments from "@/components/comments/Comments";
const getData = async (slug) => { const getData = async (slug) => {
const res = await fetch( const res = await fetch(
`http://localhost:3000/api/posts/${slug}`, `http://localhost:3000/api/posts/${slug}?popular=true`,
{ {
cache: "no-store", cache: "no-store",
} }

View file

@ -1,71 +1,154 @@
"use client"; "use client";
import styles from "./writePage.module.css";
import Image from "next/image"; import Image from "next/image";
import { useState } from "react"; import styles from "./writePage.module.css";
import ReactQuill from "react-quill"; import { useEffect, useState } from "react";
import "react-quill/dist/quill.bubble.css"; import "react-quill/dist/quill.bubble.css";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation"; 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 WritePage = () => {
const { status } = useSession();
const router = useRouter();
const {status} = useSession(); const [open, setOpen] = useState(false);
const router = useRouter(); const [file, setFile] = useState(null);
const [file, setFile] = useState(null); const [media, setMedia] = useState("");
const [open, setOpen] = useState(false); const [value, setValue] = useState("");
const [value, setValue] = useState(""); const [title, setTitle] = useState("");
const [catSlug, setCatSlug] = useState("");
if (status === "loading") { useEffect(() => {
return <div className={styles.loading}>Loading...</div> const storage = getStorage(app);
} const upload = () => {
if (status === "authenticated") { const name = new Date().getTime() + file.name;
router.push("/"); 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 <div className={styles.loading}>Loading...</div>;
}
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 ( return (
<div className={styles.container}> <div className={styles.container}>
<input type="text" placeholder="Title" className={styles.input}/> <input
<div className={styles.editor}> type="text"
<button className={styles.button} onClick={()=> setOpen(!open)}> placeholder="Title"
<Image src="/plus.png" alt="add" width={16} height={16}/> className={styles.input}
</button> onChange={(e) => setTitle(e.target.value)}
{open && ( />
<div className={styles.add}> <select className={styles.select} onChange={(e) => setCatSlug(e.target.value)}>
<input <option value="style">style</option>
type="file" <option value="fashion">fashion</option>
id="image" <option value="food">food</option>
onChange={e=>setFile(e.target.files[0])} <option value="culture">culture</option>
style={ { display: "none" }} <option value="travel">travel</option>
/> <option value="coding">coding</option>
</select>
<button className={styles.addButton}> <div className={styles.editor}>
<label htmlFor="image"> <button className={styles.button} onClick={() => setOpen(!open)}>
<Image src="/image.png" alt="" width={16} height={16} /> <Image src="/plus.png" alt="" width={16} height={16} />
</label> </button>
</button> {open && (
<div className={styles.add}>
<input
<button className={styles.addButton}> type="file"
<Image src="/external.png" alt="" width={16} height={16} /> id="image"
</button> onChange={(e) => setFile(e.target.files[0])}
style={{ display: "none" }}
<button className={styles.addButton}> />
<Image src="/video.png" alt="" width={16} height={16} /> <button className={styles.addButton}>
</button> <label htmlFor="image">
</div> <Image src="/image.png" alt="" width={16} height={16} />
)} </label>
<ReactQuill </button>
className={styles.textArea} <button className={styles.addButton}>
theme="bubble" <Image src="/external.png" alt="" width={16} height={16} />
value={value} </button>
onChange={setValue} <button className={styles.addButton}>
placeholder="Tell your story..." <Image src="/video.png" alt="" width={16} height={16} />
/> </button>
</div> </div>
<button className={styles.publish}>Publish</button> )}
</div> <ReactQuill
) className={styles.textArea}
} theme="bubble"
value={value}
onChange={setValue}
placeholder="Tell your story..."
/>
</div>
<button className={styles.publish} onClick={handleSubmit}>
Publish
</button>
</div>
);
};
export default WritePage; export default WritePage;

View file

@ -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 { .editor {
display: flex; display: flex;
gap: 20px; gap: 20px;
height: 700px; height: 700px;
position: relative; position: relative;
} }
.button, .addButton{ .button,
width: 36px; .addButton {
height: 36px; width: 36px;
border-radius: 50%; height: 36px;
background-color: transparent; border-radius: 50%;
border: 1px solid var(--textColor); background-color: transparent;
display: flex; border: 1px solid var(--textColor);
align-items: center; display: flex;
justify-content: center; align-items: center;
cursor: pointer; justify-content: center;
cursor: pointer;
} }
.addButton{ .addButton {
border-color: #1a8917; border-color: #1a8917;
} }
.add{ .add {
display: flex; display: flex;
gap: 20px; gap: 20px;
background-color: var(--bg); background-color: var(--bg);
position: absolute; position: absolute;
z-index: 999; z-index: 999;
width: 100%; width: 100%;
left: 50px; left: 50px;
} }
.input{ .input{
padding: 50px; padding: 50px;
font-size: 64px; font-size: 64px;
border: none; border: none;
outline: none; outline: none;
background-color: transparent; background-color: transparent;
color: var(--textColor);
} }
.input::placeholder{ .input::placeholder{
color: #b3b3b1; color: #b3b3b1;
} }
.textArea{
width: 100%; .textArea {
width: 100%;
} }
.publish{ .publish{
position: absolutee; position: absolute;
top: 30px; top: 0px;
right: 20px; right: 0px;
padding: 10px 20px; padding: 10px 20px;
border: none; border: none;
background-color: #1a8917; background-color: #1a8917;
color: white; color: white;
cursor: pointer; cursor: pointer;
border-radius: 20px; border-radius: 20px;
} }

17
src/utils/firebase.js Normal file
View file

@ -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);