Jardel Osorio Duarte Portfólio

Projeto Web Ecommerce-SC

Acredito que todos os desenvolvedores passam pelo momento Ecommerce (aprendizagem ou mercado), justamente pela gama de serviços hospedados neste segmento atualmente, o que oferece praticidade aos adeptos (deliverys) e consequentemente gera empregos aos entregadores, neste projeto, busquei fazer algo funcional que ampliasse meu conhecimento com components do React, sendo assim, foquei na utilização do styled components e alguns Hooks, além do contato com o Redux e Redux Persist, razões: tokens persistentes para o carrinho de compras… O resultado realmente me surpreendeu, não costumo implementar full stack e concluir praticamente todas as etapas de construção, até em razão da demanda x tempo, entretanto este projeto me entusiasmou a este ponto. Inclusive, também foi implementado uma página administradora ao manager do site, que também inclui análises (analytics) de vendas em relação aos meses anteriores.

As etapas identidades foram selecionadas na seguinte lista:

Em todo ecommerce é necessário um método de pagamento, é o que caracteriza esse serviço. Pensando assim, adotei o Stripe como serviço, acreditando ser um dos mais seguros e com custos interessantes. Para que essa ferramenta funcione corretamente, criei uma conta na página, o que garantiu as minhas chaves (pública e secreta), da mesma forma o stripe fornece um código para integração do método checkout. Logo, meu código ficou da seguinte forma:

import StripeCheckout from 'react-stripe-checkout'
const KEY = process.env.REACT_APP_STRIPE //public key
const [stripeToken, setStripeToken] = useState(null)

const onToken = (token) => { //pegando o token gerado e atribuindo a variavel  
    setStripeToken(token); 
}

useEffect(()=> { // assim que o token foi gerando 
    const makeRequest = async () => { //função de envio ao banco de dados
    const localPrice = cart.total * 100;
    await publicRequest.post("/checkout/payment", { 
        tokenId: stripeToken.id, 
        amount: localPrice,
    }).then((res) => {
        history("/success", {state:{data:res.data}})  //se tudo deu certo com a compra, redireciona o cliente
        //console.log(res.data)  
    }).catch((err) => {
        history("/failed", {state:{data:err.response.data}})  //se algo deu errado com cartão, imprime o mal sucedido
    })
}
stripeToken && makeRequest(); //se o token existe chama a função de envio
}, [stripeToken, cart.total, history])


return ( // importando o método no meu front 
    <StripeCheckout
        name="Santa Colina" //nome site
        image="https://images.vexels.com/media/users/3/200093/isolated/preview/596f0d8cb733b17268752d044976f102-icone-de-sacola-de-compras.png" //avtar
        billingAddress
        shippingAddress
        description={`TOTAL IS $ ${cart.total}`} 
        amount={cart.total*100}
        token={onToken} //chamando a função onToken
        stripeKey={KEY} //utilizando a minha public
        >                       
        <Button>BUY NOW</Button>
    </StripeCheckout>
)

De outra forma, no backend, a implementação da rota payment ficou assim:

const dotenv = require('dotenv').config()
const router = require('express').Router()
const stripe = require('stripe')(process.env.STRIPE_KEY) //secret 

router.post("/payment", (req, res) => { 

stripe.charges.create({ //utilizando o método charges para conectar com o serviço na página
    source: req.body.tokenId,
    amount: req.body.amount,
    currency: "brl", //identificando a moéda corrente usd/ eur/ neste caso brl 
    }).then((stripeRes) => {
        res.status(200).json(stripeRes)  //se a resposta for ok, os dados estão aceito

    }).catch((stripeError) => {
        console.log(stripeError) //caso houver erro no pagamento
        res.status(500).json(stripeError)
    })
})

module.exports = router

Para construir o carrinho de compras, é fundamental que a responsividade ocorra, principalmente quando existe alteração na quantidade de itens ou na persistência dos itens, ou seja, quando o cliente sair da página os itens devem permanecer no carrinho. Para isso utilizei o redux, que atualizava as incrementações no frontend quando cada função era acionada. Sendo assim, a implementação consistiu em mais de um arquivo no fonte e está melhor detalhada no código abaixo:

//page product 

import { addProduct } from '../redux/cartRedux'

const [quantity, setQuantity] = useState(1);

const handleQuantity = (type) => { //função que verifica, incrementa or decrementa 
    if(type === 'dec'){
        setQuantity(quantity-1) //se remove decrementa
    if(quantity <= 1) setQuantity(1);
    }else{
        setQuantity(quantity+1) //se add incrementa
    }
}

const handleClick = async () => { //se foi clicado para enviar ao carrinho
    await userRequest.post("/cart", { //rota para criar um carrinho gerado ao banco de dados, independente de o cliente finalizar ou nao a compra, aqui temos dados para depois lançar promoçoes ou avisos ao cliente 
    userId: currentUser._id, 
    products: {productId:id, quantity:quantity},    
    }).then((res) => {
        const _idCart = res.data._id
        dispatch(addProduct({_idCart, ...product, quantity, color, size}), history("/cart")); //o dispatch para o redux acontece aqui, logo apos a linha ter sido adicionada ao banco de dados 
    }).catch((err) => {
        console.log("this error "+ err)
    }) 

}

<AmountContainer>
    <Remove cursor="pointer" onClick={()=>handleQuantity("dec")}/>
    <Amount>{quantity}</Amount>
    <Add cursor="pointer" onClick={()=>handleQuantity("inc")}/>
</AmountContainer>

A página cartRedux:

import {createSlice} from "@reduxjs/toolkit" //utilizando a lib toolkit para criar o slice
import storage from 'redux-persist/lib/storage'

const cartSlice = createSlice({ //minhas variaveis de estado no meu slice
    name:"cart",
    initialState: {
        products:[],
        quantity:0,
        total:0,
    },
    reducers:{ //eis os meus reducers, quando adicionar, ou remover
        addProduct:(state, action)=> {
            state.quantity += 1
            state.products.push(action.payload)
            state.total += action.payload.price * action.payload.quantity
        },
        resetSkill:(state, action) => { //esta função eu criei para limpar o carrinho
            storage.removeItem('root') //removendo o persist para limpar de verdade o carrinho
            state.quantity = 0
            state.products = []
            state.total = 0
        },
        removeProduct: (state, action) => { //remove o produto clicado, confesso que essa função me tomou tempo
            const {product} = action.payload
            let lexval = 0
            for (let i = 0; i < state.quantity; i++) {
                if(product._idCart === state.products[i]._idCart){
                    lexval = i
                }
            }
            let decrement = product.price * product.quantity
            state.products.splice(lexval, 1)
            state.products = [...state.products]
            state.quantity -= 1
            if(state.quantity <= 0) state.quantity = 0
            state.total -= decrement
            if(state.total <= 0) state.total = 0
        }
    },
});

export const { addProduct, resetSkill, removeProduct } = cartSlice.actions; //exportando para adicionar na Product.jsx
export default cartSlice.reducer;

Entretanto, para que essas variáveis e funções sejam úteis, é fundamental a criação de um store, onde o persistor vai ser iniciado, como mostra o fonte abaixo:


import { configureStore, combineReducers } from "@reduxjs/toolkit";
import cartReducer from "./cartRedux";
import userReducer from "./userRedux";
import {
    persistStore,
    persistReducer,
    FLUSH,
    REHYDRATE,
    PAUSE,
    PERSIST,
    PURGE,
    REGISTER,
} from 'redux-persist'; //funções nativas do persist
import storage from 'redux-persist/lib/storage'; //o storage deve ser iniciado para acessar a raiz em casos especificos, por exemplo: limpar carrinho.

const persistConfig = {
    key: 'root', //variavel raiz que acessei anteriormente no cartRedux
    version: 1,
    storage,
}
const rootReducer = combineReducers({ user: userReducer, cart: cartReducer }); //como foi iniciado o cart e o user é fundamental que o combineReducers aconteça, outra função nativa que achei legal
const persistedReducer = persistReducer(persistConfig, rootReducer); //subindo o persist com as configs 

export const store = configureStore({ // o store que sera importado na main, neste caso index.js
    reducer: persistedReducer, //definindo os reducers
    middleware:(getDefaultMiddleware) =>
        getDefaultMiddleware({
            serializableCheck: {
                ignoreActions: [
                    FLUSH, 
                    REHYDRATE, 
                    PAUSE, 
                    PERSIST, 
                    PURGE, 
                    REGISTER
                ], //ignorando as funções nativas
            },
        }),
});

export let persistor = persistStore(store); //exportando o persistor

E finalmente, utilizando esse store na index page, já torna possível acessar essas funções/variáveis em toda e qualquer parte do código, o que eu considerei vantajosamente diferencial.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { Provider } from "react-redux" //provider que passa a variavel store dentro
import {store, persistor} from './redux/store' //incluindo a store e o persistor
import { PersistGate } from 'redux-persist/integration/react' //PersistorGate que passa o persistor dentro

const root = ReactDOM.createRoot(document.getElementById('root'));

const AppView = () => (
    <Provider store={store}>  
        <PersistGate loading={null} persistor={persistor}>
            <App /> 
        </PersistGate>
    </Provider>
);

setTimeout(() => {
    root.render(<AppView />);
}, 2500);

No projeto para gestão do site, implementei para além das funcionalidades convencionais (população de dados no banco, exclusão de dados e atualizações). Nesta etapa, busquei dados como, função de crescimento de novos clientes no último ano (um exemplo: jan: 4 clientes; fev: 10 clientes… dez: 20 clientes); porcentagens de ganhos ou perdas dos últimos 2 meses, a média das vendas dos últimos 3 meses e o lucro acumulado do item por mês (esta última na página do determinado produto). Em razão de já ter incluído bastante código nesta publicação, abaixo algumas imagens dos resultados para cada uma dessas etapas elicitados acima.

Sales representa o crescimento das vendas no último mês;
Last month representa o crescimento das vendas no mês anterior em relação ao anterior dele;
Mean representa a média das vendas nos últimos três meses;
Users Analytics gráfico que representa o crescimento dos usuários durante uma linha do tempo de até 12 meses.

Neste exemplo, o gráfico apresenta o valor acumulado nos 3 meses qual foi comprado o item Barrow Teddy Green, sendo 300 em julho, 900 em agosto e 600 em setembro, ao aproximar o cursor até o ponto no gráfico, uma shadowbox com todas estas informações é lançada.

Enfim, para complementar a publicação, uma animação das páginas mais expressivas do site, optei por algo mais minimalista e baseando-me em um projeto do Youtube, o qual faço referências no Github.

O fonte deste projeto pode estar sendo acessado neste repositório.