├── .eslintrc.json ├── .gitignore ├── README.md ├── components ├── Banner.js ├── BlogCard.js ├── EditModal.js ├── Footer.js ├── Header.js └── Layout.js ├── context └── AuthContext.js ├── firebase.js ├── images ├── Blogify.png ├── Me_pic.jpg ├── glogo.png ├── robot.png ├── sample.jpg └── shape.png ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js ├── api │ └── hello.js ├── blog │ └── [blogId].js ├── create.js ├── index.js ├── login.js ├── userDashboard.js └── yourBlogs.js ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── recoil └── modalAtom.js ├── styles └── globals.css └── tailwind.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /components/Banner.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import { HiUserCircle } from "react-icons/hi"; 4 | import Link from "next/link"; 5 | import { userAuth } from "../context/AuthContext"; 6 | 7 | const Banner = () => { 8 | const { user } = userAuth(); 9 | return ( 10 | <> 11 |
12 | 19 | {user ? ( 20 | 21 | User Profile pic 28 | 29 | ) : ( 30 | 31 | 32 | 33 | )} 34 |
35 | 36 | ); 37 | }; 38 | 39 | export default Banner; 40 | -------------------------------------------------------------------------------- /components/BlogCard.js: -------------------------------------------------------------------------------- 1 | import { BsThreeDots } from "react-icons/bs"; 2 | import Image from "next/image"; 3 | import { useState } from "react"; 4 | import { userAuth } from "../context/AuthContext"; 5 | import { deleteDoc, doc } from "firebase/firestore"; 6 | import { db } from "../firebase"; 7 | import { useRouter } from "next/router"; 8 | import { useRecoilState } from "recoil"; 9 | import { modalState } from "../recoil/modalAtom"; 10 | 11 | const BlogCard = ({ 12 | blogId, 13 | userId, 14 | username, 15 | content, 16 | image, 17 | profileImg, 18 | title, 19 | timestamp, 20 | }) => { 21 | const { user } = userAuth(); 22 | const [showOptions, setShowOptions] = useState(false); 23 | const router = useRouter(); 24 | const [openModal, setOpenModal] = useRecoilState(modalState); 25 | 26 | async function deleteBlog() { 27 | try { 28 | await deleteDoc(doc(db, "blogs", blogId)); 29 | } catch (error) { 30 | console.log(error); 31 | } 32 | } 33 | 34 | function handleClick() { 35 | router.push({ 36 | pathname: `/blog/${blogId}`, 37 | query: { 38 | title, 39 | content, 40 | username, 41 | profileImg, 42 | image, 43 | timestamp, 44 | }, 45 | }); 46 | } 47 | 48 | return ( 49 | <> 50 |
51 | {/* TOP SECTION */} 52 |
53 | 54 | User Profile pic 61 | {/* TO BE REPLACED WITH USERNAME FROM USER OBJECT */} 62 | {username} 63 | 64 |
65 | {userId === user?.uid && ( 66 | setShowOptions(!showOptions)} 69 | /> 70 | )} 71 | {showOptions && ( 72 |
73 | { 75 | // console.log(blogId); 76 | setOpenModal({ isOpen: true, blogId: blogId }); 77 | }} 78 | className="flex items-center space-x-1 active:scale-105 transition transform duration-150 ease-in-out" 79 | > 80 | 81 | Edit 82 | 83 | 87 | 88 | Delete 89 | 90 |
91 | )} 92 |
93 |
94 | {/* IMAGES SECTION */} 95 |
96 |
97 | pic 102 |
103 |
104 |
105 | 106 | {title} 107 | 108 |
109 |
110 |
111 |
112 | 113 | ); 114 | }; 115 | 116 | export default BlogCard; 117 | -------------------------------------------------------------------------------- /components/EditModal.js: -------------------------------------------------------------------------------- 1 | import { doc, getDoc, updateDoc, serverTimestamp } from "firebase/firestore"; 2 | import { getDownloadURL, ref, uploadString } from "firebase/storage"; 3 | import React, { useEffect, useRef, useState } from "react"; 4 | import { useRecoilState } from "recoil"; 5 | import { db, storage } from "../firebase"; 6 | import { modalState } from "../recoil/modalAtom"; 7 | import { AiFillCamera } from "react-icons/ai"; 8 | 9 | const EditModal = ({ blogId }) => { 10 | const [openModal, setOpenModal] = useRecoilState(modalState); 11 | const imagePickerRef = useRef(null); 12 | const [loading, setLoading] = useState(false); 13 | 14 | const [title, setTitle] = useState(""); 15 | const [newTitle, setNewTitle] = useState(""); 16 | 17 | const [content, setContent] = useState(""); 18 | const [newContent, setNewContent] = useState(""); 19 | 20 | const [selectedFile, setSelectedFile] = useState(null); 21 | const [newSelectedFile, setNewSelectedFile] = useState(null); 22 | 23 | const [message, setMessage] = useState(""); 24 | const [showCancel, setShowCancel] = useState(false); 25 | 26 | const handleClose = (e) => { 27 | if ( 28 | e.target.className === 29 | "h-full fixed top-0 left-0 w-full flex flex-col items-center justify-center bg-black bg-opacity-50" 30 | ) { 31 | setOpenModal(false); 32 | } 33 | }; 34 | 35 | // ! MAIN FUNCTION 36 | async function updatePost(e) { 37 | e.preventDefault(); 38 | if ( 39 | title === newTitle && 40 | content === newContent && 41 | selectedFile === newSelectedFile 42 | ) { 43 | setOpenModal(false); 44 | return; 45 | } 46 | try { 47 | setMessage("Updating Post"); 48 | if (loading) return; 49 | setLoading(true); 50 | // * UPDATE BLOG IN FIREBASE 51 | await updateDoc(doc(db, "blogs", blogId), { 52 | title: newTitle 53 | .split(" ") 54 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 55 | .join(" "), 56 | content: newContent, 57 | timestamp: serverTimestamp(), 58 | }); 59 | if (newSelectedFile !== selectedFile) { 60 | // * getting the image ref for the new image 61 | const imageRef = ref(storage, `blogs/${blogId}/image`); 62 | // * uploading new image to the imageRef 63 | await uploadString(imageRef, newSelectedFile, "data_url").then( 64 | async (snapshot) => { 65 | const downloadUrl = await getDownloadURL(imageRef); 66 | await updateDoc(doc(db, "blogs", blogId), { 67 | image: downloadUrl, 68 | }); 69 | } 70 | ); 71 | } else { 72 | await updateDoc(doc(db, "blogs", blogId), { 73 | image: selectedFile, 74 | }); 75 | } 76 | setLoading(false); 77 | setNewSelectedFile(null); 78 | setNewTitle(""); 79 | setNewContent(""); 80 | setOpenModal(false); 81 | } catch (error) { 82 | console.log(error); 83 | } 84 | } 85 | 86 | const addImageToBlog = (e) => { 87 | const reader = new FileReader(); 88 | if (e.target.files[0]) { 89 | reader.readAsDataURL(e.target.files[0]); 90 | } 91 | reader.onload = (readerEvent) => { 92 | setNewSelectedFile(readerEvent.target.result); 93 | }; 94 | }; 95 | 96 | useEffect(() => { 97 | async function getBlog() { 98 | const blogSnap = await getDoc(doc(db, "blogs", blogId)); 99 | const data = blogSnap.data(); 100 | setTitle(data.title); // ? original data 101 | setNewTitle(data.title); 102 | setSelectedFile(data.image); // ? original data 103 | setNewSelectedFile(data.image); 104 | setContent(data.content); // ? original data 105 | setNewContent(data.content); 106 | } 107 | getBlog(); 108 | }, []); 109 | 110 | return ( 111 | <> 112 |
116 |
117 | Edit Blog 118 |
119 | 129 | {newSelectedFile ? ( 130 |
131 | post image 136 | setNewSelectedFile(null)} 138 | className="bg-black bg-opacity-30 text-white text-center py-4 font-semibold absolute bottom-0 w-[300px]" 139 | > 140 | Select another one 141 | 142 |
143 | ) : ( 144 |
imagePickerRef.current.click()} 147 | > 148 | 149 | Upload Pic 150 |
151 | )} 152 | 161 | 175 |
176 | 188 | {!showCancel && ( 189 | 195 | )} 196 | {loading && ( 197 |
198 | 199 | {message &&
{message}
} 200 |
201 | )} 202 |
203 |
204 |
205 |
206 | 207 | ); 208 | }; 209 | 210 | export default EditModal; 211 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | 4 | function Footer() { 5 | return ( 6 |
7 | 11 | {" "} 12 | 13 | 14 | 15 | 16 | 17 | {" "} 18 | 19 |
20 | ); 21 | } 22 | 23 | export default Footer; 24 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import brand from "../images/Blogify.png"; 3 | import Link from "next/link"; 4 | import Image from "next/image"; 5 | 6 | const Header = () => { 7 | return ( 8 |
9 |
10 | 11 | 12 | 13 |
14 | 18 | Home 19 | 20 | 24 | Your Blogs 25 | 26 | 30 | Create 31 | 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default Header; 39 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Footer from "./Footer"; 3 | import Header from "./Header"; 4 | 5 | const Layout = ({ children }) => { 6 | return ( 7 | <> 8 |
9 |
10 |
{children}
11 |
13 | 14 | ); 15 | }; 16 | export default Layout; 17 | -------------------------------------------------------------------------------- /context/AuthContext.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useContext, createContext } from "react"; 3 | import { 4 | GoogleAuthProvider, 5 | signInWithRedirect, 6 | signOut, 7 | onAuthStateChanged, 8 | } from "firebase/auth"; // ? the logic for implementing login and signup feature is written in this context file 9 | import { auth } from "../firebase"; 10 | 11 | const AuthContext = createContext(); 12 | 13 | export const AuthContextProvider = ({ children }) => { 14 | const [user, setUser] = useState({}); 15 | 16 | // ? checking the login state of user via useeffect and onAuthStateChanged 17 | useEffect(() => { 18 | const unsubscribe = onAuthStateChanged(auth, async (currentUser) => { 19 | setUser(currentUser); 20 | }); 21 | return unsubscribe; 22 | }, []); 23 | 24 | // ? FUNCTION TO SIGN IN WITH GOOGLE 25 | const signIn = async () => { 26 | const provider = new GoogleAuthProvider(); 27 | await signInWithRedirect(auth, provider); 28 | }; 29 | 30 | // ? FUNCTION TO LOG OUT THE USER 31 | const logOut = async () => { 32 | await signOut(auth); 33 | }; 34 | 35 | return ( 36 | 37 | {children} 38 | 39 | ); 40 | }; 41 | 42 | export const userAuth = () => { 43 | return useContext(AuthContext); 44 | }; 45 | -------------------------------------------------------------------------------- /firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | import { getAuth } from "firebase/auth"; 4 | import { getFirestore } from "firebase/firestore"; 5 | import { getStorage } from "firebase/storage"; 6 | 7 | const firebaseConfig = { 8 | apiKey: process.env.NEXT_PUBLIC_APIKEY, 9 | authDomain: process.env.NEXT_PUBLIC_AUTHDOMAIN, 10 | projectId: process.env.NEXT_PUBLIC_PROJECTID, 11 | storageBucket: process.env.NEXT_PUBLIC_STORAGEBUCKET, 12 | messagingSenderId: process.env.NEXT_PUBLIC_MESSAGINGSENDERID, 13 | appId: process.env.NEXT_PUBLIC_APPID, 14 | }; 15 | 16 | // Initialize Firebase 17 | const app = initializeApp(firebaseConfig); 18 | export const auth = getAuth(app); 19 | // db 20 | export const db = getFirestore(); 21 | export const storage = getStorage(); 22 | -------------------------------------------------------------------------------- /images/Blogify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/images/Blogify.png -------------------------------------------------------------------------------- /images/Me_pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/images/Me_pic.jpg -------------------------------------------------------------------------------- /images/glogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/images/glogo.png -------------------------------------------------------------------------------- /images/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/images/robot.png -------------------------------------------------------------------------------- /images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/images/sample.jpg -------------------------------------------------------------------------------- /images/shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/images/shape.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | eslint: { 6 | ignoreDuringBuilds: true, 7 | }, 8 | images: { 9 | domains: ["lh3.googleusercontent.com", "firebasestorage.googleapis.com"], 10 | }, 11 | }; 12 | 13 | module.exports = nextConfig; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blogify", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.17.1", 13 | "next": "13.1.6", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0", 16 | "react-icons": "^4.7.1", 17 | "recoil": "^0.7.6" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^10.4.13", 21 | "eslint": "8.34.0", 22 | "eslint-config-next": "13.1.6", 23 | "postcss": "^8.4.21", 24 | "tailwind-scrollbar": "^2.1.0", 25 | "tailwindcss": "^3.2.7" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import Layout from "../components/Layout"; 2 | import { AuthContextProvider } from "../context/AuthContext"; 3 | import "../styles/globals.css"; 4 | import { RecoilRoot } from "recoil"; 5 | 6 | function MyApp({ Component, pageProps }) { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default MyApp; 19 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 | 13 | 17 | {/* font awesome */} 18 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/blog/[blogId].js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import { useRouter } from "next/router"; 4 | 5 | const BlogDetail = () => { 6 | const router = useRouter(); 7 | const { title, content, username, profileImg, image, timestamp } = 8 | router.query; 9 | return ( 10 | <> 11 |
12 | 13 | profile pic 20 | {username} 21 | 22 |
23 |
24 | pic 29 |
30 |
31 | 32 | {title} 33 | 34 | {content} 35 |
36 |
37 |
38 | 39 | ); 40 | }; 41 | 42 | export default BlogDetail; 43 | -------------------------------------------------------------------------------- /pages/create.js: -------------------------------------------------------------------------------- 1 | import { 2 | addDoc, 3 | collection, 4 | doc, 5 | serverTimestamp, 6 | updateDoc, 7 | } from "firebase/firestore"; 8 | import { getDownloadURL, ref, uploadString } from "firebase/storage"; 9 | import Head from "next/head"; 10 | import { useRouter } from "next/router"; 11 | import React, { useEffect, useRef, useState } from "react"; 12 | import { AiFillCamera } from "react-icons/ai"; 13 | import { userAuth } from "../context/AuthContext"; 14 | import { db, storage } from "../firebase"; 15 | 16 | const create = () => { 17 | const router = useRouter(); 18 | const imagePickerRef = useRef(null); 19 | const [loading, setLoading] = useState(false); 20 | const { user } = userAuth(); 21 | const [title, setTitle] = useState(""); 22 | const [content, setContent] = useState(""); 23 | const [selectedFile, setSelectedFile] = useState(null); 24 | const [message, setMessage] = useState(""); 25 | 26 | const uploadPost = async (e) => { 27 | e.preventDefault(); 28 | try { 29 | setMessage("Uploading Post"); 30 | if (loading) return; 31 | setLoading(true); // ? setting loading state to true the first time user clicks the post button 32 | // * create blog in firestore 33 | const docRef = await addDoc(collection(db, "blogs"), { 34 | uid: user.uid, 35 | username: user.displayName.toLowerCase().replace(/\s/g, ""), 36 | title: title 37 | .split(" ") 38 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 39 | .join(" "), 40 | content: content, 41 | profileImg: user.photoURL, 42 | timestamp: serverTimestamp(), 43 | }); 44 | // * get the image ref from the storage by using firebase url 45 | const imageRef = ref(storage, `blogs/${docRef.id}/image`); 46 | // * 3) upload the image to the firebase storage with the blog id 47 | // * 4) get a download URL from the fb storage and update original blog with the image 48 | await uploadString(imageRef, selectedFile, "data_url").then( 49 | async (snapshot) => { 50 | const downloadUrl = await getDownloadURL(imageRef); 51 | await updateDoc(doc(db, "blogs", docRef.id), { 52 | image: downloadUrl, 53 | }); 54 | } 55 | ); 56 | setLoading(false); 57 | setSelectedFile(null); 58 | setTitle(""); 59 | setContent(""); 60 | setMessage("Post Uploaded Successfully"); 61 | } catch (error) { 62 | console.log(error.message); 63 | setMessage("Post could not be uploaded!"); 64 | } 65 | }; 66 | 67 | const addImageToBlog = (e) => { 68 | const reader = new FileReader(); 69 | if (e.target.files[0]) { 70 | reader.readAsDataURL(e.target.files[0]); 71 | } 72 | reader.onload = (readerEvent) => { 73 | setSelectedFile(readerEvent.target.result); 74 | }; 75 | }; 76 | 77 | useEffect(() => { 78 | let timeoutId; 79 | timeoutId = setTimeout(() => { 80 | setMessage("Slow Internet Detected!"); 81 | }, 7000); 82 | return () => { 83 | clearTimeout(timeoutId); 84 | }; 85 | }, [message]); 86 | 87 | useEffect(() => { 88 | if (user) { 89 | return; 90 | } 91 | router.push("/login"); 92 | }, []); 93 | 94 | return ( 95 | <> 96 | 97 | Blogify - Create Blog 98 | 99 |
100 | Create Blog 101 |
102 | 112 | 113 | {selectedFile ? ( 114 |
115 | post image 120 | setSelectedFile(null)} 122 | className="bg-black bg-opacity-30 text-white text-center py-4 font-semibold absolute bottom-0 w-[298px] rounded-b-lg" 123 | > 124 | Select another one 125 | 126 |
127 | ) : ( 128 |
imagePickerRef.current.click()} 130 | className="flex items-center space-x-2 cursor-pointer" 131 | > 132 | 133 | Upload Pic 134 |
135 | )} 136 | 145 | 159 |
160 | 171 | {loading && ( 172 |
173 | 174 | {message &&
{message}
} 175 |
176 | )} 177 |
178 |
179 |
180 | 181 | ); 182 | }; 183 | 184 | export default create; 185 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { collection, onSnapshot, orderBy, query } from "firebase/firestore"; 2 | import Head from "next/head"; 3 | import { useEffect, useState } from "react"; 4 | import { useRecoilValue } from "recoil"; 5 | import Banner from "../components/Banner"; 6 | import BlogCard from "../components/BlogCard"; 7 | import EditModal from "../components/EditModal"; 8 | import { db } from "../firebase"; 9 | import { modalState } from "../recoil/modalAtom"; 10 | 11 | export default function Home() { 12 | const [blogs, setBlogs] = useState([]); 13 | const [loading, setLoading] = useState(false); 14 | const { isOpen, blogId } = useRecoilValue(modalState); 15 | 16 | useEffect(() => { 17 | setLoading(true); 18 | const unsubscribe = onSnapshot( 19 | query(collection(db, "blogs"), orderBy("timestamp", "desc")), 20 | (snapshot) => { 21 | setBlogs(snapshot.docs); 22 | setLoading(false); 23 | } 24 | ); 25 | return unsubscribe; 26 | }, [db]); 27 | 28 | return ( 29 | <> 30 | 31 | Blogify 32 | 33 | 34 | 35 |
36 | 37 |
38 |
45 | {loading ? ( 46 | 47 | ) : ( 48 | blogs?.map((blog) => { 49 | return ( 50 | 62 | ); 63 | }) 64 | )} 65 |
66 | {isOpen && } 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import Image from "next/image"; 3 | import { useRouter } from "next/router"; 4 | import brand from "../images/Blogify.png"; 5 | import glogo from "../images/glogo.png"; 6 | import { userAuth } from "../context/AuthContext"; 7 | 8 | function Login() { 9 | const router = useRouter(); 10 | const { signIn } = userAuth(); 11 | 12 | const handleSignIn = async () => { 13 | try { 14 | await signIn(); 15 | } catch (error) { 16 | console.log(error.message); 17 | } 18 | }; 19 | 20 | const { user } = userAuth(); 21 | useEffect(() => { 22 | if (user) { 23 | router.push("/"); 24 | } 25 | }, [user]); 26 | 27 | return ( 28 | <> 29 |
30 |
31 | router.push("/")}> 32 | 33 | 34 |
38 | 41 | 42 |
43 |
44 |
45 | 46 | ); 47 | } 48 | 49 | export default Login; 50 | -------------------------------------------------------------------------------- /pages/userDashboard.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { userAuth } from "../context/AuthContext"; 3 | import { useRouter } from "next/router"; 4 | import shape from "../images/shape.png"; 5 | import Image from "next/image"; 6 | 7 | const UserDashboard = () => { 8 | const router = useRouter(); 9 | const { logOut, user } = userAuth(); 10 | useEffect(() => { 11 | if (user) { 12 | return; 13 | } 14 | router.push("/"); 15 | }, [user]); 16 | return ( 17 | <> 18 |
19 |
20 | 21 | user pic 26 |
27 | {user?.email} 28 | 34 |
35 | 36 | ); 37 | }; 38 | 39 | export default UserDashboard; 40 | -------------------------------------------------------------------------------- /pages/yourBlogs.js: -------------------------------------------------------------------------------- 1 | import { 2 | collection, 3 | onSnapshot, 4 | orderBy, 5 | query, 6 | where, 7 | } from "firebase/firestore"; 8 | import Head from "next/head"; 9 | import Image from "next/image"; 10 | import { useRouter } from "next/router"; 11 | import React, { useEffect, useState } from "react"; 12 | import BlogCard from "../components/BlogCard"; 13 | import { userAuth } from "../context/AuthContext"; 14 | import { db } from "../firebase"; 15 | import robot from "../images/robot.png"; 16 | import EditModal from "../components/EditModal"; 17 | import { useRecoilState, useRecoilValue } from "recoil"; 18 | import { modalState } from "../recoil/modalAtom"; 19 | 20 | const YourBlogs = () => { 21 | const { user } = userAuth(); 22 | const [blogs, setBlogs] = useState([]); 23 | const [loading, setLoading] = useState(false); 24 | const { isOpen, blogId } = useRecoilValue(modalState); 25 | // ! authentication code 26 | const router = useRouter(); 27 | useEffect(() => { 28 | if (user) { 29 | return; 30 | } 31 | router.push("/login"); 32 | }, []); 33 | 34 | useEffect(() => { 35 | if (!user) { 36 | return; 37 | } 38 | setLoading(true); 39 | const unsubscribe = onSnapshot( 40 | query( 41 | collection(db, "blogs"), 42 | where("uid", "==", user.uid || ""), 43 | orderBy("timestamp", "desc") 44 | ), 45 | (snapshot) => { 46 | setBlogs(snapshot.docs); 47 | setLoading(false); 48 | } 49 | ); 50 | return unsubscribe; 51 | }, [db, user]); 52 | 53 | return ( 54 | <> 55 | 56 | Blogify - Your Blogs 57 | 58 |
0 61 | ? "flex flex-col items-center justify-center" 62 | : "flex flex-col items-center justify-center h-full" 63 | } 64 | > 65 | {blogs.length > 0 ? ( 66 | blogs.map((blog) => ( 67 | 78 | )) 79 | ) : loading ? ( 80 | 81 | ) : ( 82 |
83 | confused robot image 84 | 85 | No Blogs Yet! 86 | 87 |
88 | )} 89 |
90 | {isOpen && } 91 | 92 | ); 93 | }; 94 | 95 | export default YourBlogs; 96 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-ansh-007/Blogify/5ef9e9e48f85e72de622ff5b09bef398d9538830/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /recoil/modalAtom.js: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | 3 | export const modalState = atom({ 4 | key: "modalState", 5 | default: { 6 | isOpen: false, 7 | blogId: "", 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: "Montserrat", sans-serif; 7 | } 8 | 9 | * { 10 | padding: 0 1px 0 1px; 11 | } 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx}", 5 | "./pages/**/*.{js,ts,jsx,tsx}", 6 | "./components/**/*.{js,ts,jsx,tsx}", 7 | 8 | // Or if using `src` directory: 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [ 15 | // ... 16 | require("tailwind-scrollbar"), 17 | ], 18 | }; 19 | --------------------------------------------------------------------------------