Skip to content

natalia-sampaio/e-commerce

Repository files navigation

E-commerce

Status  JavaScript  TailwindCSS  Vue.js  Firebase 

Table of contents

Overview

Description

This project will be divided in three weeks:

  • Week 1:

    • Mobile and Desktop layout;
    • Responsive design;
    • Shopping cart with state management;
    • Product state management;
    • Transitions and animations.
  • Week 2:

    • Refactor last week's work;
    • Use LocalStorage for cart data persistance while in guest view;
    • Use DummyJson API to feed content to the page;
    • Create sign up and login;
  • Week 3:

    • Refactor last week's work;
    • Use Firebase Auth for user authentication;
    • Use JWT tokens for user auth;
  • Week 4:

    • Use Firebase Firestore to persist cart data;
    • Create user profile page;
    • Within user profile page:
      • User should be able to delete account;
      • User should be able to change name;
    • When logged in user should have access to "Favorites" button, similar to the cart;

Project Setup

npm install

Compile and Hot-Reload for Development

npm run dev

Compile and Minify for Production

npm run build

Screenshots

Links

My process

Built with

  • Vue.js 3
  • TailwindCSS
  • Responsive layout

What I learned

Week 1:

I used Vue's built in component Transitions for the first time to create a better user experience.

Reusable transition for the pages when they are loaded, it is reausable because I abstracted it to component named SlideDownFade.Vue:

<script>
import { Transition } from 'vue';
</script>

<template>
    <Transition name="slide-down-fade" appear>
        <slot></slot>
    </Transition>
</template>

<style>
.slide-down-fade-enter-from {
    opacity: 0;
    transform: translateY(-250px);
}

.slide-down-fade-enter-active {
    transition: all 2s ease;
}
</style>

Here is SlideDownFade.Vue being used:

<template>
    <SlideDownFade>
        <div class="lg:mx-8 lg:flex lg:flex-wrap lg:justify-center">
            <div>
                <ProductCard v-for="product in mensShoes" 
                :key="product.id" 
                :brandName="product.brand"
                :productName="product.title" 
                :finalCost="productsStore.getDiscountedPrice(product.id)"
                :discount="product.discountPercentage" 
                :originalPrice="product.price" 
                :product="product" />
            </div>
        </div>
    </SlideDownFade>
</template>

Week 2:

I learned how to synchronize data persistence (used localStorage) and state management, and it was very eazy once I had a store with all the logic I needed:

import { defineStore } from 'pinia';
import { useLocalStorage } from '@vueuse/core';

export const useCartStore = defineStore('cart', {
  state: () => {
    return {
      toggleCart: false,
      items: useLocalStorage('cart', [])
    }
  },
  getters: {
    cartItems(){
      return this.items.reduce((acc, item) => acc + item.amount, 0)
    }
  },
  actions: {
    addToCart(product) {
      const existingProduct = this.items.find(element => element.product.id === product.id);

      if(existingProduct) {
        existingProduct.amount = existingProduct.amount+1
      } else {
        this.items.push({product, amount: 1})
      }
    },
    deleteItem(id) {
      const productToBeDeleted = this.items.findIndex(element => element.product.id === id)
      this.items.splice(productToBeDeleted, 1)
    }
  }
})

To consume the DummyJSON API, I decided to abstract the URL's that I needed in the config.js file:

let domain = 'https://dummyjson.com/products/category/'

export const apiDomain = domain;
export const mensShoesUrl = apiDomain + 'mens-shoes';
export const womensShoesUrl = apiDomain + 'womens-shoes';
export const featuredUrl = apiDomain + 'womens-shoes?limit=1';

And then the functions that get the JSON objects in the products.js file in the services folder:

import { featuredUrl, mensShoesUrl, womensShoesUrl } from '../config.js';

export async function getMensShoes() {
    const request = mensShoesUrl;
    const fetchResponse = await fetch(request);
    const response = await fetchResponse.json();
    return response.products;
}

export async function getWomensShoes() {
    const request = womensShoesUrl;
    const fetchResponse = await fetch(request);
    const response = await fetchResponse.json();
    return response.products;
}

export async function getFeaturedShoes() {
    const request = featuredUrl;
    const fetchResponse = await fetch(request);
    const response = await fetchResponse.json();
    return response.products;
}

export function getDiscountedPrice(id, products) {
    const product = products.find(element => element.id === id);

    const discountedPrice = product.discountPercentage > 0.0 ? product.price - ((product.price / 100) * product.discountPercentage) : product.price;

    return discountedPrice.toFixed(2);
}

export function getImages(id, products) {
    const product = products.find(element => element.id === id);

    const images = product.images;
    
    return images;
}

Week 3:

I learned that with Firebase Authentication SDK I don't need to worry about JWT tokens because the library already handles user login state (YAY!). It is also possible to use social login or Federated Identity Providers, that means that I can easily implement login with Google, Facebook, Github and many other providers.

const submitForm = async () => {
    const result = await v$.value.$validate();
    if (result) {
        store.$patch({
            name: formData.name,
            email: formData.email
        });
        createUserWithEmailAndPassword(getAuth(), formData.email, formData.password)
            .then((data) => {
                router.push('/user-profile')
            })
            .catch((error) => {
                alert(error.message);
            })
    } else {
        animateButton();
        router.push('/sign-up')
    }
}
const signInWithGoogle = () => {
    const provider = new GoogleAuthProvider();
    signInWithPopup(getAuth(), provider)
        .then((result) => {
            router.push('/user-profile')
        })
        .catch((error) => {
            animateButton();
            if(error.code == 'auth/user-not-found') {
                $externalResults.value = { email: 'User not found, please register.' }
            }
            if (error.code == 'auth/wrong-password'){
                $externalResults.value = { email: 'User and/or password are incorrect' }
            }
        });
};

At first I wanted to use Firebase Realtime Database for the user data, but then a found out about a more recent solution called Firestore, so I decided to use that to store user information, such as cart, favorites and profile.

Week 4:

I learned how to use Firebase Storage to upload a profile picture.

import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";

export default function uploadProfilePicture(file, userId) {
    const storage = getStorage();

    // Create the file metadata
    /** @type {any} */
    const metadata = {
        contentType: 'image/*'
    };

    // Upload file and metadata to the object 'images/mountains.jpg'
    const storageRef = ref(storage, userId);
    const uploadTask = uploadBytesResumable(storageRef, file, metadata);

    // Listen for state changes, errors, and completion of the upload.
    uploadTask.on('state_changed',
        (snapshot) => {
            // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
            const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
            console.log('Upload is ' + progress + '% done');
            switch (snapshot.state) {
                case 'paused':
                    console.log('Upload is paused');
                    break;
                case 'running':
                    console.log('Upload is running');
                    break;
            }
        },
        (error) => {
            // A full list of error codes is available at
            // https://firebase.google.com/docs/storage/web/handle-errors
            switch (error.code) {
                case 'storage/unauthorized':
                    // User doesn't have permission to access the object
                    break;
                case 'storage/canceled':
                    // User canceled the upload
                    break;

                // ...

                case 'storage/unknown':
                    // Unknown error occurred, inspect error.serverResponse
                    break;
            }
        },
        () => {
            // Upload completed successfully, now we can get the download URL
            getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
                console.log('File available at', downloadURL);
            });
            location.reload()
        }
    );
}

Useful resources

Author

Acknowledgments

My husband @filipedanielski who is always supporting and encouraging me.

Releases

No releases published

Packages

No packages published

Languages