Finished with the tutorial
This commit is contained in:
parent
409bc2c3cc
commit
b871cd1a54
11 changed files with 264 additions and 127 deletions
|
@ -2,7 +2,7 @@
|
|||
const nextConfig = {
|
||||
images: {
|
||||
domains:
|
||||
["lh3.googleusercontent.com"]
|
||||
["lh3.googleusercontent.com", "firebasestorage.googleapis.com"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
BIN
public/external.png
Normal file
BIN
public/external.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
public/image.png
Normal file
BIN
public/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
public/plus.png
Normal file
BIN
public/plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
public/video.png
Normal file
BIN
public/video.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
|
@ -9,8 +9,9 @@ export const GET = async (req, {params})=>{
|
|||
|
||||
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}));
|
||||
|
|
|
@ -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 }),
|
||||
},
|
||||
};
|
||||
|
||||
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}));
|
||||
}
|
||||
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 })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// CREATE A POST
|
||||
export const POST = async (req) => {
|
||||
const session = await getAuthSession();
|
||||
|
||||
if (!session) {
|
||||
return new NextResponse(
|
||||
JSON.stringify({ message: "Not Authenticated!" }, { status: 401 })
|
||||
);
|
||||
}
|
||||
|
||||
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 })
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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 <div className={styles.loading}>Loading...</div>
|
||||
}
|
||||
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 <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 (
|
||||
<div className={styles.container}>
|
||||
<input type="text" placeholder="Title" className={styles.input}/>
|
||||
<div className={styles.editor}>
|
||||
<button className={styles.button} onClick={()=> setOpen(!open)}>
|
||||
<Image src="/plus.png" alt="add" width={16} height={16}/>
|
||||
</button>
|
||||
{open && (
|
||||
<div className={styles.add}>
|
||||
<input
|
||||
type="file"
|
||||
id="image"
|
||||
onChange={e=>setFile(e.target.files[0])}
|
||||
style={ { display: "none" }}
|
||||
/>
|
||||
|
||||
<button className={styles.addButton}>
|
||||
<label htmlFor="image">
|
||||
<Image src="/image.png" alt="" width={16} height={16} />
|
||||
</label>
|
||||
</button>
|
||||
|
||||
|
||||
<button className={styles.addButton}>
|
||||
<Image src="/external.png" alt="" width={16} height={16} />
|
||||
</button>
|
||||
|
||||
<button className={styles.addButton}>
|
||||
<Image src="/video.png" alt="" width={16} height={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<ReactQuill
|
||||
className={styles.textArea}
|
||||
theme="bubble"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
placeholder="Tell your story..."
|
||||
/>
|
||||
</div>
|
||||
<button className={styles.publish}>Publish</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Title"
|
||||
className={styles.input}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
<select className={styles.select} onChange={(e) => setCatSlug(e.target.value)}>
|
||||
<option value="style">style</option>
|
||||
<option value="fashion">fashion</option>
|
||||
<option value="food">food</option>
|
||||
<option value="culture">culture</option>
|
||||
<option value="travel">travel</option>
|
||||
<option value="coding">coding</option>
|
||||
</select>
|
||||
<div className={styles.editor}>
|
||||
<button className={styles.button} onClick={() => setOpen(!open)}>
|
||||
<Image src="/plus.png" alt="" width={16} height={16} />
|
||||
</button>
|
||||
{open && (
|
||||
<div className={styles.add}>
|
||||
<input
|
||||
type="file"
|
||||
id="image"
|
||||
onChange={(e) => setFile(e.target.files[0])}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
<button className={styles.addButton}>
|
||||
<label htmlFor="image">
|
||||
<Image src="/image.png" alt="" width={16} height={16} />
|
||||
</label>
|
||||
</button>
|
||||
<button className={styles.addButton}>
|
||||
<Image src="/external.png" alt="" width={16} height={16} />
|
||||
</button>
|
||||
<button className={styles.addButton}>
|
||||
<Image src="/video.png" alt="" width={16} height={16} />
|
||||
</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;
|
|
@ -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;
|
||||
}
|
17
src/utils/firebase.js
Normal file
17
src/utils/firebase.js
Normal 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);
|
Loading…
Reference in a new issue