DEV Community

Cover image for FullStack - How to create a working blogging website with pure HTML, CSS and JS in 2021 - 2
Modern Web
Modern Web

Posted on

FullStack - How to create a working blogging website with pure HTML, CSS and JS in 2021 - 2

Hello, Today we will do working on some advance features of our blogging website that we made 2 week ago. I have added some advance features like -

  1. Google Authentication Login/Logout
  2. Dashboard
  3. User written blogs on dashboard
  4. Can read/edit/delete the blogs from the dashboard.
  5. Edit the blog from blog page itself.
  6. Storing/Displaying Author username.

So as this is a continuous part of previous one. I am using the previous code and file structure.

For demo, code tutorial with explanation and hosting on heroku. You can watch the video below.

Video Tutorial

Tutorial View Aim - If the Video tutorial hits 5K+ views I'll make tutorial on fully working e-commerce site including sellers page, product listing, placing order, paypal gateway, login/logout and much more.

So, without wasting more time let's see how to code this.

Code

So before starting make 4 files inside public folder.

  1. dashboard.html
  2. css/dashboard.css
  3. js/dashoboard.js
  4. js/nav.js

We need these 4 four files also.

So let's open server.js file and make /admin route.

app.get("/admin", (req, res) => {
    res.sendFile(path.join(initial_path, "dashboard.html"));
})
Enter fullscreen mode Exit fullscreen mode

Thing to notice here is this route will not work if you add this route below /:blog route. Because /:blog means anything after single "/", so it will consider /admin as a blog. So to server dashboard. Make the route above the /:blog route.

Once the route is done. Let's make dashboard page.

In dashboard page, add this css stylesheet.
https://www.gstatic.com/firebasejs/ui/4.8.1/firebase-ui-auth.css
And this JS script
https://www.gstatic.com/firebasejs/ui/4.8.1/firebase-ui-auth.js
From where I got these links. These links are firebase pre built login UI. You can easily find them in the documentation.

After that link all the firebase CDN we were using last time. And make a copy of firestore CDN. And replace the firestore with auth for auth CDN.

And link home.css & dashboard.css file.

<head>
    <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.8.1/firebase-ui-auth.css" />
    <link rel="stylesheet" href="css/home.css">
    <link rel="stylesheet" href="css/dashboard.css">
</head>
<body>
    <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-firestore.js"></script>
    <script src="https://www.gstatic.com/firebasejs/ui/4.8.1/firebase-ui-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js"></script>

    <script src="js/firebase.js"></script>
    <script src="js/dashboard.js"></script>
    <script src="js/nav.js"></script>

</body>
Enter fullscreen mode Exit fullscreen mode

You can watch tutorial for better understanding.

Now let's make a login feature. For that

Dashboard.html
<!-- login -->
<div class="login">
    <div id="loginUI"></div>
</div>
Enter fullscreen mode Exit fullscreen mode
Dashboard.css
body{
    background: #ececec;
}

.login{
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 99;
    background: #fff;
}

/* login hover effect */

.firebaseui-idp-button:hover{
    background: #000 !important;
}

.firebaseui-idp-button:hover .firebaseui-idp-text{
    color: #fff !important;
    transition: .5s;
}
Enter fullscreen mode Exit fullscreen mode

Before going to dashboard.js make sure you do this in firebase.js

let auth = firebase.auth();
Enter fullscreen mode Exit fullscreen mode
dashboard.js
let ui = new firebaseui.auth.AuthUI(auth);
let login = document.querySelector('.login');

auth.onAuthStateChanged((user) => {
    if(user){
        login.style.display = "none";
        getUserWrittenBlogs();
    } else{
        setupLoginButton();
    }
})

const setupLoginButton = () => {
    ui.start("#loginUI", {
        callbacks: {
            signInSuccessWithAuthResult: function(authResult, redirectURL) {
                location.reload();
                return false;
            }
        },
        signInFlow: "popup",
        signInOptions: [firebase.auth.GoogleAuthProvider.PROVIDER_ID]
    })
}
Enter fullscreen mode Exit fullscreen mode

Our Login feature is done.
Capture

So, after that let's make a navbar for the dashboard and blog cards.
These elements will be the same as we have home page. So just copy them from there.

<nav class="navbar">
    <img src="img/logo.png" class="logo" alt="">
    <ul class="links-container">
        <li class="link-item"><a href="/" class="link">home</a></li>
        <li class="link-item"><a href="/editor" class="link">editor</a></li>
    </ul>
</nav><!-- dashboard -->
<h1 class="heading">your blogs</h1>

<!-- blog section -->
<section class="blogs-section">
    <div class="blog-card">
        <img src="img/header.png" class="blog-image" alt="">
        <h1 class="blog-title">Lorem ipsum dolor sit amet consectetur.</h1>
        <p class="blog-overview">Lorem ipsum dolor sit amet consectetur adipisicing elit. Sunt incidunt fugiat quos porro repellat harum. Adipisci tempora corporis rem cum.</p>
        <a href="/" class="btn dark">read</a>
        <a href="/" class="btn grey">edit</a>
        <a href="/" class="btn danger">delete</a>
    </div>
</section>
Enter fullscreen mode Exit fullscreen mode

You can see some extra elements that we don't have in home section blog. For that give these styles.

.heading{
    color: rgba(0, 0, 0, 0.5);
    font-size: 60px;
    text-align: center;
    position: relative;
    margin: 100px 0 20px;
}

.btn.grey{
    background: #c5c5c5;
    color: #000;
    margin: 0 10px;
}

.btn.danger{
    background: #f00;
    color: #fff;
}
Enter fullscreen mode Exit fullscreen mode
Output

Capture2
You can comment the blog card now.
Now let's make navbar dynamic links. For this open nav.js and code this.

const ul = document.querySelector('.links-container');

auth.onAuthStateChanged((user) => {
    if (user) {
        ul.innerHTML += `
        <li class="link-item"><a href="/admin" class="link">Dashboard</a></li>
        <li class="link-item"><a href="#" onclick="logoutUser()" class="link">Logout</a></li>
        `
    } else {
        ul.innerHTML += `
        <li class="link-item"><a href="/admin" class="link">Login</a></li>
        `
    }
});
Enter fullscreen mode Exit fullscreen mode

Just add nav.js and firebase-auth CDN. All of your pages.

Output

Capture3

If you see the above code you'll see logout function. So, let's create that. Create this inside firebase.js

const logoutUser = () => {
    auth.signOut();
    location.reload();
}
Enter fullscreen mode Exit fullscreen mode

Now our login/logout is done with dynamic links. Let's save/retrieve the author to database now.

Open editor.js and first check for user is logged in or not.

auth.onAuthStateChanged((user) => {
    if (!user) {
        location.replace('/admin');
    }
});
Enter fullscreen mode Exit fullscreen mode

Then change the previous code like this.

db.collection("blogs").doc(docName).set({
            title: blogTitleField.value,
            article: articleFeild.value,
            bannerImage: bannerPath,
            publishedAt: `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`, // till here we made in last blog
            author: auth.currentUser.email.split('@')[0]
        })
Enter fullscreen mode Exit fullscreen mode

This way we can add author to the database. Now show the author on blog page.
For that open blog.js and add this inside setupBlog function after setting up the publishedAt element. Like this.

publish.innerHTML += data.publishedAt;
publish.innerHTML += ` -- ${data.author}`;
Enter fullscreen mode Exit fullscreen mode
Output

Capture4

Great! We are 50% done. Now let's fetch/make blog cards in dashboard. Open dashboard.js

// fetch user 
const getUserWrittenBlogs = () => {
    db.collection("blogs").where("author", "==", auth.currentUser.email.split('@')[0])
    .get()
    .then((blogs) => {
        blogs.forEach((blog) => {
            createBlog(blog);
        });
    })
    .catch((error) => {
        console.log("Error getting documents: ", error);
    });
}

const createBlog = (blog) => {
    let data = blog.data();
    blogSection.innerHTML += `
    <div class="blog-card">
        <img src="${data.bannerImage}" class="blog-image" alt="">
        <h1 class="blog-title">${data.title.substring(0, 100) + '...'}</h1>
        <p class="blog-overview">${data.article.substring(0, 200) + '...'}</p>
        <a href="/${blog.id}" class="btn dark">read</a>
        <a href="/${blog.id}/editor" class="btn grey">edit</a>
        <a href="#" onclick="deleteBlog('${blog.id}')" class="btn danger">delete</a>
    </div>
    `;
}

const deleteBlog = (id) => {
    db.collection("blogs").doc(id).delete().then(() => {
        location.reload();
    }).catch((error) => {
        console.error("Error removing document: ", error);
    });
}
Enter fullscreen mode Exit fullscreen mode

By this we are now getting user written blogs and able to delete them as well.

Output

Capture5

Now let's edit button on blog page. Add this button after published element.

<a class="btn" id="edit-blog-btn" href="">edit blog</a>
Enter fullscreen mode Exit fullscreen mode

And style this.

blog.css
#edit-blog-btn{
    background: rgba(0, 0, 0, 0.2);
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

I found some styles issue when I was developing it. So, I recommend you give these style to these elements also.

blog.css
.article *{
    word-break: break-word;
}
Enter fullscreen mode Exit fullscreen mode
home.css
.blogs-section{
    word-break: break-word;
}
Enter fullscreen mode Exit fullscreen mode

And now make edit button visible when the author visiting the page.

blog.js
try{
    if(data.author == auth.currentUser.email.split('@')[0]){
        let editBtn = document.getElementById('edit-blog-btn');
        editBtn.style.display = 'inline';
        editBtn.href = `${blogId}/editor`;
    }
} catch{
    // nothing
}
Enter fullscreen mode Exit fullscreen mode

Add this after publish.innerHTML += -- ${data.author}; in setupBlog function. Why we are using try here because if you don't use that it will give error when no one is log in.

Now everything is done. The last thing make blog edit. Open server.js and make route for editing existing blog.

app.get("/:blog/editor", (req, res) => {
    res.sendFile(path.join(initial_path, "editor.html"));
})
Enter fullscreen mode Exit fullscreen mode

After this is you open the route. You'll see the editor page but it will have no styles on it and no images. Why is that? I don;t know really but I know to fix it. If you know what we call this scenario in JS let me know in discussions.

To fix that open editor.html file. And add ../ before every link. Each and every link. After that your file should look like this.

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog : Editor</title>

    <link rel="stylesheet" href="../css/home.css">
    <link rel="stylesheet" href="../css/editor.css">

</head>
<body>

    <div class="banner">
        <input type="file" accept="image/*" id="banner-upload" hidden>
        <label for="banner-upload" class="banner-upload-btn"><img src="../img/upload.png" alt="upload banner"></label>
    </div>

    <div class="blog">
        <textarea type="text" class="title" placeholder="Blog title..."></textarea>
        <textarea type="text" class="article" placeholder="Start writing here..."></textarea>
    </div>

    <div class="blog-options">
        <button class="btn dark publish-btn">publish</button>
        <input type="file" accept="image/*" id="image-upload" hidden>
        <label for="image-upload" class="btn grey upload-btn">Upload Image</label>
    </div>

    <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-firestore.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js"></script>

    <script src="../js/firebase.js"></script>
    <script src="../js/editor.js"></script>

</body>
Enter fullscreen mode Exit fullscreen mode

Now it'll work. Great! Now let's setup the blog values and make edit functional.

editor.js
// already existing blog setup
let blogId = location.pathname.split("/");
blogId.shift(); // remove first item from the array because first item is empty 

if(blogId[0] != 'editor'){
    // means we are in existing blog route
    let docRef = db.collection("blogs").doc(decodeURI(blogId[0]));
    docRef.get().then((doc) => {
        console.log(doc);
        if(doc.exists){
            let data = doc.data();
            bannerPath = data.bannerImage;
            banner.style.backgroundImage = `url(${bannerPath})`;
            blogTitleField.value = data.title;
            articleFeild.value = data.article;
        } else{
            location.replace("/");
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

So this will setup the text fields value. And we can edit them
Capture7
Now inside the publishBtn click event. Change that like this.

if(articleFeild.value.length && blogTitleField.value.length){
    // generating id

    let docName;
    if(blogId[0] == 'editor'){
        let letters = 'abcdefghijklmnopqrstuvwxyz';
        let blogTitle = blogTitleField.value.split(" ").join("-");
        let id = '';
        for(let i = 0; i < 4; i++){
            id += letters[Math.floor(Math.random() * letters.length)];
        }
        docName = `${blogTitle}-${id}`;
    } else{
        docName = decodeURI(blogId[0]);
    }

    // setting up docName
    let date = new Date(); // for published at info

    //access firstore with db variable;
    db.collection("blogs").doc(docName).set({
        title: blogTitleField.value,
        article: articleFeild.value,
        bannerImage: bannerPath,
        publishedAt: `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`,
        author: auth.currentUser.email.split('@')[0]
    })
    .then(() => {
        location.href = `/${docName}`;
    })
    .catch((err) => {
        console.error(err);
    })
}
Enter fullscreen mode Exit fullscreen mode

This is the same as previous. Only docName value is changed here.

Output

Capture6

So we are done. We have all the advance features. You can check this to host the website online.

You can get my all project's source code in just 5$ membership on patreon. Support my work for more awesome and amazing website. This is just a beginning.Source Code

I hope you understood each and everything. If you have doubt or I missed something let me know in the comments.

Articles you may find Useful

  1. Infinte CSS loader
  2. Best CSS Effect
  3. Wave Button Hover Effect
  4. Youtube API - Youtube Clone
  5. TMDB - Netflix Clone

I really appreciate if you can subscribe my youtube channel. I create awesome web contents.

Thanks For reading.

Discussion (0)