E-commerce website - adding/deleting product from database. Part 3
Tags: html, css, javascript, webdev
Published: 2021-10-05 13:11:54

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](https://www.youtube.com/playlist?list=PLqm86YkewF6S45smPYeNv01aFFKWZSgYD)

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

#Video Tutorial

{% youtube V9lxabUi4Xo %}

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

[Access Full Fullstack E-com Website video series](https://www.youtube.com/playlist?list=PLqm86YkewF6S45smPYeNv01aFFKWZSgYD), [Source Code](https://www.patreon.com/posts/56979686), [Download Images](https://drive.google.com/drive/folders/1eIWchzFD2XELCwTwztOvYLYJsDDNvCQb?usp=sharing)

#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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7zpdw0t38whgfxu0a9dh.PNG)

![Capture2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5gjc2epeb7y5spook7l.PNG)

![Capture3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvol0il2kervmfhlyp16.PNG)

![Capture4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sadp41sr4hym7q2b231q.PNG)

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

######Server.js
```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 `404`page.

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. 
```HTML
<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.

```HTML
<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
```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.
```CSS
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.

```CSS
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.
```CSS
input:not(input[type="checkbox"]),
textarea{
    // properties
}
```
######Output
![screenshot-localhost_3000-2021.10.04-19_18_16](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxr20lqam5nic7ecpz61.png) 

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.

```JS
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
```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.
```JS
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
```JS
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](https://youtu.be/V9lxabUi4Xo?t=2061)

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
```JS
// 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.
```JS
// 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.

```JS
// 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
```JS
// 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.
```Js
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.

```JS
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`,

```JS
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqjxfj9mvmnum8prvvth.PNG)

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.

```JS
// 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.
```JS
// 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.
```JS
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.

```JS
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
```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
```CSS
/* alert */
.alert-box{
    // previous code
    position: fixed;
    z-index: 2;
}
```
The same i did with `loader`.
######Signup.css
```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
```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()`.

```JS
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
```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
```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](https://youtu.be/V9lxabUi4Xo)

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](https://dev.to/kunaal438/css-the-best-css-effects-of-all-time-most-underrated-web-ux-2chj)
2. [Infinte CSS loader](https://dev.to/kunaal438/quick-css-make-infinity-loading-animation-for-your-next-website-187k)
3. [Disney+ Clone](https://dev.to/kunaal438/how-to-create-disney-plus-clone-for-beginner-in-2021-html-css-js-m3p)
4. [Youtube API - Youtube Clone](https://dev.to/kunaal438/create-working-youtube-clone-with-search-box-youtube-api-2a6e)
5. [TMDB - Netflix Clone](https://dev.to/kunaal438/how-to-create-netflix-clone-netflix-clone-with-hmtl-css-js-989)

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

<a href="https://www.youtube.com/c/modern_web?sub_confirmation=1"><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jeitpawvax07cx7r9yoa.png"></a>

[Source Code](https://www.patreon.com/posts/56979686), [Donate me on Paypal](https://paypal.me/modernwebchannel)

Your donation really motivates me to do more amazing tutorials like this. Support me on [patreon](https://www.patreon.com/modernweb), [Buy me a coffee](https://ko-fi.com/modernweb), [Donate me on paypal](https://paypal.me/modernwebchannel)

Thanks For reading.