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 = {
|
const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
domains:
|
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{
|
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}));
|
||||||
|
|
|
@ -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 POST_PER_PAGE = 2;
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
take: POST_PER_PAGE,
|
take: POST_PER_PAGE,
|
||||||
skip: POST_PER_PAGE * (page - 1),
|
skip: POST_PER_PAGE * (page - 1),
|
||||||
where: {
|
where: {
|
||||||
...(cat && {catSlug: cat}),
|
...(cat && { catSlug: cat }),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try{
|
try {
|
||||||
|
const [posts, count] = await prisma.$transaction([
|
||||||
const [posts,count] = await prisma.$transaction(
|
|
||||||
[
|
|
||||||
prisma.post.findMany(query),
|
prisma.post.findMany(query),
|
||||||
prisma.post.count({where:query.where}),
|
prisma.post.count({ where: query.where }),
|
||||||
]
|
]);
|
||||||
);
|
return new NextResponse(JSON.stringify({ posts, count }, { status: 200 }));
|
||||||
return new NextResponse(JSON.stringify({posts, count}, {status:200}));
|
} catch (err) {
|
||||||
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return new NextResponse(JSON.stringify({message: "Something went wrong!"}, {status:500}));
|
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 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",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,136 @@
|
||||||
"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 {status} = useSession();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [file, setFile] = useState(null);
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [file, setFile] = useState(null);
|
||||||
|
const [media, setMedia] = useState("");
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [catSlug, setCatSlug] = useState("");
|
||||||
|
|
||||||
|
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") {
|
if (status === "loading") {
|
||||||
return <div className={styles.loading}>Loading...</div>
|
return <div className={styles.loading}>Loading...</div>;
|
||||||
}
|
}
|
||||||
if (status === "authenticated") {
|
|
||||||
|
if (status === "unauthenticated") {
|
||||||
router.push("/");
|
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
|
||||||
|
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}>
|
<div className={styles.editor}>
|
||||||
<button className={styles.button} onClick={()=> setOpen(!open)}>
|
<button className={styles.button} onClick={() => setOpen(!open)}>
|
||||||
<Image src="/plus.png" alt="add" width={16} height={16}/>
|
<Image src="/plus.png" alt="" width={16} height={16} />
|
||||||
</button>
|
</button>
|
||||||
{open && (
|
{open && (
|
||||||
<div className={styles.add}>
|
<div className={styles.add}>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
id="image"
|
id="image"
|
||||||
onChange={e=>setFile(e.target.files[0])}
|
onChange={(e) => setFile(e.target.files[0])}
|
||||||
style={ { display: "none" }}
|
style={{ display: "none" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button className={styles.addButton}>
|
<button className={styles.addButton}>
|
||||||
<label htmlFor="image">
|
<label htmlFor="image">
|
||||||
<Image src="/image.png" alt="" width={16} height={16} />
|
<Image src="/image.png" alt="" width={16} height={16} />
|
||||||
</label>
|
</label>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
<button className={styles.addButton}>
|
<button className={styles.addButton}>
|
||||||
<Image src="/external.png" alt="" width={16} height={16} />
|
<Image src="/external.png" alt="" width={16} height={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className={styles.addButton}>
|
<button className={styles.addButton}>
|
||||||
<Image src="/video.png" alt="" width={16} height={16} />
|
<Image src="/video.png" alt="" width={16} height={16} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -63,9 +144,11 @@ const WritePage = () => {
|
||||||
placeholder="Tell your story..."
|
placeholder="Tell your story..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className={styles.publish}>Publish</button>
|
<button className={styles.publish} onClick={handleSubmit}>
|
||||||
|
Publish
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default WritePage;
|
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 {
|
.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
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