E-commerce website – adding/deleting product from database. Part 3

Hello, hope you’ll are good. In previous part, we have made login/log out feature and a seller dashboard, where user can apply as a seller and can get his/her dashboard to manage his products. In today’s blog, we’ll make a product adding feature, which…


This content originally appeared on DEV Community and was authored by Modern Web

Hello, hope you'll are good. In previous part, we have made login/log out feature and a seller dashboard, where user can apply as a seller and can get his/her dashboard to manage his products. In today's blog, we'll make a product adding feature, which is very big feature. We'll see how to validate forms before storing to database, we'll see how to upload image on aws from front end, and we also add delete product, edit product, and much more.

If you haven't watched previous parts. Watch now

To see demo or you want full coding tutorial video for better understanding. You can watch the tutorial below.

Video Tutorial

I appreciate if you can support me by subscribing my youtube channel.

Access Full Fullstack E-com Website video series, Source Code, Download Images

Code

Before start coding, you can see the folder structure, as we have a lot, a lot of code files, that I can't even make a file structure design as I do in my blogs. But you can see the screenshots below.

Capture

Capture2

Capture3

Capture4

So let, start coding. Let's first make /add-product route to serve addProduct.html page.

Server.js
// add product
app.get('/add-product', (req, res) => {
    res.sendFile(path.join(staticPath, "addProduct.html"));
})

Make sure you add this route before 404 route, as I said earlier, if you add any route after 404 route, you'll always get 404page.

After that, let's make add product page, where we can fill the product details.

Add Product Page - Design

First, start with HTML 5 template and make loader, alert element, link all the CSS and JS files.

<head>
    // other head tags
    <link rel="stylesheet" href="css/signup.css">
    <link rel="stylesheet" href="css/addProduct.css">
</head>
<body>
    <img src="img/loader.gif" class="loader" alt="">

    <div class="alert-box">
        <img src="img/error.png" class="alert-img" alt="">
        <p class="alert-msg"></p>
    </div>

    <script src="js/token.js"></script>
    <script src="js/addProduct.js"></script>
</body>

After done with basic structure make form, of course.

<img src="img/dark-logo.png" class="logo" alt="">

<div class="form">
    <input type="text" id="product-name" placeholder="product name">
    <input type="text" id="short-des" placeholder="short line about the product">
    <textarea id="des" placeholder="detail description"></textarea>

    <!-- product image -->
    <div class="product-info">
        <div class="product-image"><p class="text">product image</p></div>
        <div class="upload-image-sec">
            <!-- upload inputs -->
            <p class="text"><img src="img/camera.png" alt="">upload image</p>
            <div class="upload-catalouge">
                <input type="file" class="fileupload" id="first-file-upload-btn" hidden>
                <label for="first-file-upload-btn" class="upload-image"></label>
                <input type="file" class="fileupload" id="second-file-upload-btn" hidden>
                <label for="second-file-upload-btn" class="upload-image"></label>
                <input type="file" class="fileupload" id="third-file-upload-btn" hidden>
                <label for="third-file-upload-btn" class="upload-image"></label>
                <input type="file" class="fileupload" id="fourth-file-upload-btn" hidden>
                <label for="fourth-file-upload-btn" class="upload-image"></label>
            </div>
        </div>
        <div class="select-sizes">
            <p class="text">size available</p>
            <div class="sizes">
                <input type="checkbox" class="size-checkbox" id="xs" value="xs">
                <input type="checkbox" class="size-checkbox" id="s" value="s">
                <input type="checkbox" class="size-checkbox" id="m" value="m">
                <input type="checkbox" class="size-checkbox" id="l" value="l">
                <input type="checkbox" class="size-checkbox" id="xl" value="xl">
                <input type="checkbox" class="size-checkbox" id="xxl" value="xxl">
                <input type="checkbox" class="size-checkbox" id="xxxl" value="xxxl">
            </div>
        </div>
    </div>

    <div class="product-price">
        <input type="number" id="actual-price" placeholder="actual price">
        <input type="number" id="discount" placeholder="discount percentage">
        <input type="number" id="sell-price" placeholder="selling price">
    </div>

    <input type="number" id="stock" min="20" placeholder="item in sstocks (minimum 20)">

    <textarea id="tags" placeholder="Enter categories here, for example - Men, Jeans, Blue Jeans, Rough jeans (you sholud add men or women at start)"></textarea>

    <input type="checkbox" class="checkbox" id="tac" checked>
    <label for="tac">clothing take 30% from your total sell</label>

    <div class="buttons">
        <button class="btn" id="add-btn">add product</button>
        <button class="btn" id="save-btn">save draft</button>
    </div>
</div>

That's a lot of HTML at once, you can refer video for step by step guide, as we mainly focus on Javascript in the blog, but if you have any doubt with the any part feel free to ask me in discussions.

addProduct.css
body{
    display: block;
    padding: 0 10vw;
}

.logo{
    margin: 20px auto 50px;
}

input, textarea{
    font-weight: 500;
}

input:not(input[type="checkbox"]){
    width: 100%;
}

textarea{
    width: 100%;
    height: 270px;
    resize: none;
    padding: 10px 20px;
}

.product-info{
    width: 100%;
    height: 500px;
    display: grid;
    grid-template-columns: .75fr 1.25fr;
    grid-template-rows: repeat(2, 1fr);
    grid-gap: 20px;
    margin-bottom: 20px;
}

.product-image{
    display: flex;
    justify-content: center;
    align-items: center;
    background: #fff;
    background-size: cover;
    border-radius: 10px;
    grid-row: span 2;
    text-shadow: 0 0 10px #fff;
}

.text{
    text-transform: capitalize;
    color: #383838;
    font-size: 20px;
    font-weight: 500;
}

.upload-image-sec, .select-sizes{
    background: #fff;
    border-radius: 10px;
    padding: 20px;
}

.text img{
    height: 20px;
    margin-right: 10px;
}

.upload-catalouge{
    width: 100%;
    margin: 20px 0;
    display: grid;
    grid-template-columns: repeat(4, 100px);
    grid-gap: 10px;
}

.upload-image{
    width: 100%;
    height: 100px;
    background: #f5f5f5;
    cursor: pointer;
    background-size: cover;
}

.upload-image:hover{
    background: rgba(0, 0, 0, 0.2);
    background-size: cover;
}


.sizes{
    margin-top: 30px;
}

.size-checkbox{
    -webkit-appearance: none;
    width: 100px;
    height: 40px;
    border-radius: 5px;
    border: 1px solid #383838;
    cursor: pointer;
    margin-bottom: 10px;
    margin-right: 10px;
    position: relative;
    color: #383838;
}

.size-checkbox::after{
    content: attr(value);
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 16px;
    text-transform: uppercase;
}

.size-checkbox:checked{
    background: #383838;
    color: #fff;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button{
    -webkit-appearance: none;
    margin: 0;
}

.product-price{
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-gap: 20px;
}

.product-price input{
    margin: 0;
}

.buttons{
    margin: 20px 0 50px;
}

.btn{
    padding: 10px 30px;
    text-transform: capitalize;
    color: #fff;
    background: #383838;
    border-radius: 5px;
    border: none;
    outline: none;
    margin-right: 10px;
    cursor: pointer;
}

#save-btn{
    background: #a9a9a9;
}

You maybe notice one new CSS selector input::-webkit-outer-spin-button. If so, then this is simply select inputs arrow buttons, in this case, we want to hide our number input's arrows. That's why I used this.

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button{
    -webkit-appearance: none;
    margin: 0;
}

And if you are following the series from the start, make to make a little change in signpu.css file.

input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"], // add this new line
textarea{
    // properties
}

or you can simply replace whole selector with this.

input:not(input[type="checkbox"]),
textarea{
    // properties
}
Output

screenshot-localhost_3000-2021.10.04-19_18_16

Great! Now, make form functional.

Form Submission

Before submitting form to backend, we have to lot of JS to validate the form and to add specific triggers to the elements.

So first, as this is for only sellers, first check whether user is logged in or not on accessing the page. And of course if he/she is not logged in then redirect user to login page.

let user = JSON.parse(sessionStorage.user || null);
let loader = document.querySelector('.loader');

// checknig user is logged in or not
window.onload = () => {
    if(user){
        if(!compareToken(user.authToken, user.email)){
            location.replace('/login');
        }
    } else{
        location.replace('/login');
    }
}

accessing user from sessionStorage as I am storing user there.

After done with this, let's start with adding dynamic pricing. What do I mean? Means let's add the feature where user add actual price and a discounted price and automatically we fill the selling price with the exact discount. And also reversible.

addProduct.js
// price inputs

const actualPrice = document.querySelector('#actual-price');
const discountPercentage = document.querySelector('#discount');
const sellingPrice = document.querySelector('#sell-price');

Select all three inputs first, then add click event to discountPercentage and there perform calculation.

discountPercentage.addEventListener('input', () => {
    if(discountPercentage.value > 100){
        discountPercentage.value = 90;
    } else{
        let discount = actualPrice.value * discountPercentage.value / 100;
        sellingPrice.value = actualPrice.value - discount;
    }
})

In the above, code i am just checking if discount percentage is more then 100 then set that to 90, as nobody really want to sell free product, right? And after that just doing simple percentage to value calculation and setting up the sellingPrice value.

After that add reverse for sellingPrice also

sellingPrice.addEventListener('input', () => {
    let discount = (sellingPrice.value / actualPrice.value) * 100;
    discountPercentage.value = discount;
})

Great! We got the feature working. After done with this. Let's work with aws to store uploaded image online. To see the aws setup refer tutorial from this point

Well we'll just see here, how to config it in server side. Before that let's understand what we'll exactly do.

First we config the aws in our server, then we make request to aws to give us a secure link. After we got the link, we'll send that link to front end. From front end, when user upload image using file input. He/she make PUT request to the generated link we got from the server to upload the image.And at last, we store that link in an array, to keep track.

So, install these two packages first.

npm i aws-sdk dotenv

aws-sdk - for aws of course
dotenv - for environment variables to secure your credentials.

Server.js

AWS Config

// aws config
const aws = require('aws-sdk');
const dotenv = require('dotenv');

dotenv.config();

// aws parameters
const region = "ap-south-1";
const bucketName = "ecom-website-tutorial-2";
const accessKeyId = process.env.AWS_ACCESS_KEY;
const secretAccessKey = process.env.AWS_SECRET_KEY;

aws.config.update({
    region, 
    accessKeyId, 
    secretAccessKey
})

// init s3
const s3 = new aws.S3();

S3 is the aws service which we use to store the files.
After this, make a generate link function to generate a link.

// generate image upload link
async function generateUrl(){
    let date = new Date();
    let id = parseInt(Math.random() * 10000000000);

    const imageName = `${id}${date.getTime()}.jpg`;

    const params = ({
        Bucket: bucketName,
        Key: imageName,
        Expires: 300, //300 ms
        ContentType: 'image/jpeg'
    })
    const uploadUrl = await s3.getSignedUrlPromise('putObject', params);
    return uploadUrl;
}

Make a async function, as we don;t know how much time it will take to get the response, and our other codes are dependent on this. And getSignedUrlPromise is a aws method to get a put link. You can refer their documentation also.

Now just make a /s3url route, which will deliver the link to frontend.

// get the upload link
app.get('/s3url', (req, res) => {
    generateUrl().then(url => res.json(url));
})

Great! Now we have to access this in frontend. So, let's do it.

addProduct.js

Select upload inputs

// upload image handle
let uploadImages = document.querySelectorAll('.fileupload');
let imagePaths = []; // will store all uploaded images paths;

Now loo through each upload button and add change event to them. And access the uploaded file.

uploadImages.forEach((fileupload, index) => {
    fileupload.addEventListener('change', () => {
        const file = fileupload.files[0];
        let imageUrl;

        if(file.type.includes('image')){
            // means user uploaded an image

        } else{
            showAlert('upload image only');
        }
    })
})

After this just use fetch to get the url from server, and then again use fetch make PUT request to upload the image.

if(file.type.includes('image')){
    // means user uploaded an image
    fetch('/s3url').then(res => res.json())
    .then(url => {
        fetch(url,{
            method: 'PUT',
            headers: new Headers({'Content-Type': 'multipart/form-data'}),
            body: file
        }).then(res => {
            console.log(url)
        })
    })
}

We are done, we have successfully uploaded image. Now to make it visible to user. Just use style attribute to set element's background-image,

if(file.type.includes('image')){
    // means user uploaded an image
    fetch('/s3url').then(res => res.json())
    .then(url => {
        fetch(url,{
            method: 'PUT',
            headers: new Headers({'Content-Type': 'multipart/form-data'}),
            body: file
        }).then(res => {
            imageUrl = url.split("?")[0];
            imagePaths[index] = imageUrl;
            let label = document.querySelector(`label[for=${fileupload.id}]`);
            label.style.backgroundImage = `url(${imageUrl})`;
            let productImage = document.querySelector('.product-image');
            productImage.style.backgroundImage = `url(${imageUrl})`;
        })
    })
}
Output

Capture5

Now, whats left? I know a lot of things XD Now, make as we have custom checkbox, to track size stores, we have to make a function to keep track of it.

// store size function
const storeSizes = () => {
    sizes = [];
    let sizeCheckBox = document.querySelectorAll('.size-checkbox');
    sizeCheckBox.forEach(item => {
        if(item.checked){
            sizes.push(item.value);
        }
    })
}

Above code is very simple, I guess you got it. So now let's select all the form element which left.

// form submission

const productName = document.querySelector('#product-name');
const shortLine = document.querySelector('#short-des');
const des = document.querySelector('#des');

let sizes = []; // will store all the sizes

const stock = document.querySelector('#stock');
const tags = document.querySelector('#tags');
const tac = document.querySelector('#tac');

// buttons
const addProductBtn = document.querySelector('#add-btn');
const saveDraft = document.querySelector('#save-btn');

Now add click event to addProductBtn and class storeSizes function to store the size.

addProductBtn.addEventListener('click', () => {
    storeSizes();
    // validate form
})

Good, and to validate the form, we'll use separate function. But the function return true or false base on the validation.

const validateForm = () => {
    if(!productName.value.length){
        return showAlert('enter product name');
    } else if(shortLine.value.length > 100 || shortLine.value.length < 10){
        return showAlert('short description must be between 10 to 100 letters long');
    } else if(!des.value.length){
        return showAlert('enter detail description about the product');
    } else if(!imagePaths.length){ // image link array
        return showAlert('upload atleast one product image')
    } else if(!sizes.length){ // size array
        return showAlert('select at least one size');
    } else if(!actualPrice.value.length || !discount.value.length || !sellingPrice.value.length){
        return showAlert('you must add pricings');
    } else if(stock.value < 20){
        return showAlert('you should have at least 20 items in stock');
    } else if(!tags.value.length){
        return showAlert('enter few tags to help ranking your product in search');
    } else if(!tac.checked){
        return showAlert('you must agree to our terms and conditions');
    } 
    return true;
}

addProductBtn.addEventListener('click', () => {
    storeSizes();
    // validate form
    if(validateForm()){ // validateForm return true or false while doing validation

    }
})

Now if you notice, in validateForm instead of returning false. I am returning showAlert, why is that, as I don't want to write return false in each if else So I just wrote it inside showAlert function.

Token.js
// alert function
const showAlert = (msg) => {
    // previous code
    return false;
}

If you run the code, you'll get the alert. But, there is an issue. We'll get the alert on the top of the page. When submitting the form from the bottom, because I didn't set its position to fixed.

Signup.css
/* alert */
.alert-box{
    // previous code
    position: fixed;
    z-index: 2;
}

The same i did with loader.

Signup.css
.loader{
    position: fixed;
}

So, doing so far, we are also done, with validations. So now, just submit the data. But to submit the data, first we need the data, right? For that make another function productData() which will return the data.

addProduct.js
const productData = () => {
    return data = {
        name: productName.value,
        shortDes: shortLine.value,
        des: des.value,
        images: imagePaths,
        sizes: sizes,
        actualPrice: actualPrice.value,
        discount: discountPercentage.value,
        sellPrice: sellingPrice.value,
        stock: stock.value,
        tags: tags.value,
        tac: tac.checked,
        email: user.email
    }
}

Now once we got the data in front end, let's submit it using our sendData().

addProductBtn.addEventListener('click', () => {
    storeSizes();
    // validate form
    if(validateForm()){ // validateForm return true or false while doing validation
        loader.style.display = 'block';
        let data = productData();
        sendData('/add-product', data);
    }
})

It's great. But do we have a /add-product POST route in our server. I don't think so, let's make that.

server.js
// add product
app.post('/add-product', (req, res) => {
    let { name, shortDes, des, images, sizes, actualPrice, discount, sellPrice, stock, tags, tac, email } = req.body;

    // validation
    if(!draft){
        if(!name.length){
            return res.json({'alert': 'enter product name'});
        } else if(shortDes.length > 100 || shortDes.length < 10){
            return res.json({'alert': 'short description must be between 10 to 100 letters long'});
        } else if(!des.length){
            return res.json({'alert': 'enter detail description about the product'});
        } else if(!images.length){ // image link array
            return res.json({'alert': 'upload atleast one product image'})
        } else if(!sizes.length){ // size array
            return res.json({'alert': 'select at least one size'});
        } else if(!actualPrice.length || !discount.length || !sellPrice.length){
            return res.json({'alert': 'you must add pricings'});
        } else if(stock < 20){
            return res.json({'alert': 'you should have at least 20 items in stock'});
        } else if(!tags.length){
            return res.json({'alert': 'enter few tags to help ranking your product in search'});
        } else if(!tac){
            return res.json({'alert': 'you must agree to our terms and conditions'});
        } 
    }

    // add product
    let docName = `${name.toLowerCase()}-${Math.floor(Math.random() * 5000)};
    db.collection('products').doc(docName).set(req.body)
    .then(data => {
        res.json({'product': name});
    })
    .catch(err => {
        return res.json({'alert': 'some error occurred. Try again'});
    })
})

In the above route, I am just simply accessing the variables from the request, and performing validations on the data. Validation are same as front end, the difference is we were returning showAlert there, ans here we are returning JSON. And at last I am generating a random doc name following product's name. and adding the data to the firestore.

Now just to receive the confirmation of product add, we can edit the processData() little bit in token.js file.

token.js
const processData = (data) => {
    // previous conditions
    else if(data.product){
        location.href = '/seller';
    }
}

So, we are officially done, with doing validations to adding the product to the firsbase. Great job. Sing the blog is begin to be lengthy. I think that's enough for today guys. But of course to make delete, edit, and other features. Refer video tutorial

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. Best CSS Effect
  2. Infinte CSS loader
  3. Disney+ Clone
  4. Youtube API - Youtube Clone
  5. TMDB - Netflix Clone

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

Source Code, Donate me on Paypal

Your donation really motivates me to do more amazing tutorials like this. Support me on patreon, Buy me a coffee, Donate me on paypal

Thanks For reading.


This content originally appeared on DEV Community and was authored by Modern Web


Print Share Comment Cite Upload Translate Updates
APA

Modern Web | Sciencx (2021-10-05T13:11:54+00:00) E-commerce website – adding/deleting product from database. Part 3. Retrieved from https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/

MLA
" » E-commerce website – adding/deleting product from database. Part 3." Modern Web | Sciencx - Tuesday October 5, 2021, https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/
HARVARD
Modern Web | Sciencx Tuesday October 5, 2021 » E-commerce website – adding/deleting product from database. Part 3., viewed ,<https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/>
VANCOUVER
Modern Web | Sciencx - » E-commerce website – adding/deleting product from database. Part 3. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/
CHICAGO
" » E-commerce website – adding/deleting product from database. Part 3." Modern Web | Sciencx - Accessed . https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/
IEEE
" » E-commerce website – adding/deleting product from database. Part 3." Modern Web | Sciencx [Online]. Available: https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/. [Accessed: ]
rf:citation
» E-commerce website – adding/deleting product from database. Part 3 | Modern Web | Sciencx | https://www.scien.cx/2021/10/05/e-commerce-website-adding-deleting-product-from-database-part-3/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.