浏览代码

HWJS18 (shop) done

DimaBondarenko 3 年之前
父节点
当前提交
b79f68f7ec
共有 5 个文件被更改,包括 857 次插入0 次删除
  1. 2 0
      HWJS18/css/style.min.css
  2. 9 0
      HWJS18/css/style.min.css.map
  3. 69 0
      HWJS18/index.html
  4. 531 0
      HWJS18/index.js
  5. 246 0
      HWJS18/sass/style.scss

文件差异内容过多而无法显示
+ 2 - 0
HWJS18/css/style.min.css


文件差异内容过多而无法显示
+ 9 - 0
HWJS18/css/style.min.css.map


+ 69 - 0
HWJS18/index.html

@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html lang="en">
+<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>Document</title>
+    <link rel="stylesheet" href="css/style.min.css">
+</head>
+<body>
+    <header>
+        <nav>
+            <div>Поиск</div>
+            <div id="register-wrap">
+                <a href="#/login/id" id="signin">Войти</a>
+                <a href="#/register/id" id="register">Регистрация</a>
+            </div>
+            <div id="userlogout-wrap">
+                <a href="#/cart/id" id="cart-btn">Cart</a>
+                <a href="#/dashboard/id" id="dashboard-btn">Dashboard</a>
+                <div id="user"></div>
+                <button id="logout">Выйти</button>
+            </div>
+            
+        </nav>
+    </header>
+
+    <div id="mainContainer">
+        <aside id="aside">Категории</aside>
+        <main id="main">
+            Контент
+        </main>
+
+        <div class="overlay">
+            <form id="signup-form">
+                <div class="close">&times;</div>
+                <input id="signup" type="text" name="log-register" placeholder="login-register"><br>
+                <input id="signup-password" type="password" name="password" placeholder="password"><br>
+                <input id="btn-register" class="form-btn" type="submit">
+            </form>
+            <form id="login-form">
+                <div class="close">&times;</div>
+                <input id="login" type="text" name="login" placeholder="login"><br>
+                <input id="login-password" type="password" name="password" placeholder="password"><br>
+                <input id="btn-login" class="form-btn" type="submit">
+            </form>
+        
+        
+            <div id="cart-wrap">
+                <div class="close">&times;</div>
+                <ul id="cart">
+                   
+                </ul>
+                <button id="btn-buy" class="btn-buy">Купить</button>
+            </div> 
+            <div id="dashboard-wrap">
+                <div class="close">&times;</div>
+                <div >
+                    
+                    <ul id="dashboard">
+                        
+                    </ul>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script src="index.js"></script>
+</body>
+</html>

+ 531 - 0
HWJS18/index.js

@@ -0,0 +1,531 @@
+let signin = document.querySelector('#signin');
+let register = document.querySelector('#register');
+let cartBtn = document.querySelector('#cart-btn');
+
+let logRegisterInput = document.querySelector('#signup');
+let passRegisterInput = document.querySelector('#signup-password');
+let btnRegister = document.querySelector('#btn-register');
+
+let loginInput = document.querySelector('#login');
+let passwordInput = document.querySelector('#login-password');
+let btnLogin = document.querySelector('#btn-login');
+
+let overlay = document.querySelector(".overlay");
+let registerForm = document.querySelector('#signup-form');
+let loginForm = document.querySelector('#login-form');
+let cartWrap = document.querySelector('#cart-wrap');
+let dashboardWrap = document.querySelector('#dashboard-wrap');
+let Closes = document.querySelectorAll(".close");
+
+let registerWrap = document.querySelector("#register-wrap");
+let userLogoutWrap = document.querySelector("#userlogout-wrap");
+let user = document.querySelector("#user");
+
+let dashboardUl = document.querySelector("#dashboard")
+let cartUl = document.querySelector("#cart");
+let btnBuy = document.querySelector("#btn-buy");
+
+let logout = document.querySelector('#logout');
+let dashboardBtn = document.querySelector('#dashboard-btn');
+
+function showAndHideElem(element, value){
+    element.style.display = value;
+    overlay.style.display = value
+}
+
+signin.addEventListener("click", () => {
+    showAndHideElem(loginForm, 'block');
+});
+
+register.addEventListener("click", () => {
+    showAndHideElem(registerForm, 'block');
+})
+
+cartBtn.addEventListener("click", () => {
+    showAndHideElem(cartWrap, 'block')
+})
+
+dashboardBtn.addEventListener("click", () => {
+    showAndHideElem(dashboardWrap, 'block')
+})
+
+Closes[0].addEventListener("click", () => {
+    showAndHideElem(registerForm, 'none');
+    registerForm.reset();
+})
+Closes[1].addEventListener("click", () => {
+    showAndHideElem(loginForm, 'none');
+    loginForm.reset();
+})
+Closes[2].addEventListener("click", () => {
+    showAndHideElem(cartWrap, 'none')
+})
+
+Closes[3].addEventListener("click", () => {
+    showAndHideElem(dashboardWrap, 'none')
+})
+
+
+
+function createStore(reducer){                                     
+    let state       = reducer(undefined, {}) 
+    let cbs         = []                     
+    
+    const getState  = () => state            
+    const subscribe = cb => (cbs.push(cb),   
+                             () => cbs = cbs.filter(c => c !== cb)) 
+                             
+    const dispatch  = action => { 
+        if (typeof action === 'function'){ 
+            return action(dispatch, getState) 
+        }
+        const newState = reducer(state, action) 
+        if (newState !== state){ 
+            state = newState 
+            for (let cb of cbs)  cb() 
+        }
+    }
+    
+    return {
+        getState,
+        dispatch,
+        subscribe 
+    }
+}
+
+const getGQL = url =>
+    (query, variables) => fetch(url, {
+        method: 'POST',
+        headers: {
+            "Content-Type": "application/json",
+            // 'Accept' : 'application/json',
+            ...(localStorage.authToken ? {"Authorization": "Bearer " + localStorage.authToken} : {})
+        },
+        body: JSON.stringify({query, variables})
+    }).then(res => res.json())
+        .then(data => {
+            if (data.data){
+                return Object.values(data.data)[0] 
+            } 
+            else throw new Error(JSON.stringify(data.errors))
+        })
+const backendURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
+const gql = getGQL(backendURL + '/graphql');
+
+const jwtDecode = token => {
+    try{
+        return JSON.parse(atob(token.split('.')[1]));
+        
+    }
+    catch(e){
+        console.log(e.name, e.message);
+    }
+}
+
+function promiseReducer(state={}, {type, name, status, payload, error}){
+    if (type === 'PROMISE'){
+        return {
+            ...state,
+            [name]:{status, payload, error}
+        }
+    }
+    return state
+}
+
+function authReducer(state, {type, token}){                                 
+    if (state === undefined){
+        if(localStorage.authToken){
+            type = 'AUTH_LOGIN';
+            token = localStorage.authToken
+        }
+    }
+    if(type === 'AUTH_LOGIN'){
+        let payload = jwtDecode(token);
+        if (payload){
+            localStorage.authToken = token
+             return {token, payload}
+        }       
+    } 
+    if(type === 'AUTH_LOGOUT'){
+        localStorage.removeItem("authToken")
+        return {}
+    } 
+    return state || {}
+}
+
+const combineReducers = (reducers) => (state={}, action) => {
+    let newState = {}
+    for (const [reducerName, reducer] of Object.entries(reducers)){
+            let subNewState = reducer(state[reducerName],action)
+            if(subNewState !== state[reducerName]){
+                newState = {
+                    ...newState, [reducerName] : subNewState
+                }
+            }        
+    }
+    if(Object.keys(newState).length > 0){
+            return {
+                ...state,...newState
+            }
+    }
+    return state
+}
+
+function cartReducer(state = {}, {type, good, count=1}){
+    //каков state:
+    //{
+    //  _id1: {count:1, good: {_id1, name, price, images}}
+    //  _id2: {count:1, good: {_id2, name, price, images}}
+    //}
+    //каковы действия по изменению state
+    
+    if (type === 'CART_ADD'){
+        return {
+            ...state,
+            [good._id]: {count: count+(state[good._id]?.count || 0), good : good}
+            //копируем старое и подменяем один ключ на новое, однако если
+                    //ключ был, берем count из старого и прибавляем к count из action. 
+        }
+    }
+    if (type === 'CART_CHANGE'){
+        return { 
+            ...state, 
+            [good._id] : {count: count, good : good}
+        //         ///!меняем полностью
+        //         //копируем старое и подменяем один ключ на новое. аналогично ларьку
+        //         //и promiseReducer
+        }
+    }
+    if (type === 'CART_DELETE'){
+        let {[good._id]: id1, ...newState} = state;
+        return {
+            ...newState
+        }
+        //смочь скопировать объект state без одного ключа 
+        //(для этого есть хитрая деструктуризация, например
+        //вернуть копию без этого ключа
+    }
+    if (type === 'CART_CLEAR'){
+        return {}
+    }
+    return state
+}
+
+const actionCartAdd    = (good, count=1) => ({type: 'CART_ADD', good, count})
+const actionCartChange = (good, count=1) => ({type: 'CART_CHANGE', good, count})             ///oninput меняяем полностью
+const actionCartDelete = (good)          => ({type: 'CART_DELETE', good})
+const actionCartClear  = ()              => ({type: 'CART_CLEAR'})
+
+
+
+const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}));
+store.subscribe(() => console.log(store.getState()))
+
+const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token});
+const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'});
+
+const actionPending             = name => ({type:'PROMISE',name, status: 'PENDING'})
+const actionFulfilled = (name,payload) => ({type:'PROMISE',name, status: 'FULFILLED', payload})
+const actionRejected  = (name,error)   => ({type:'PROMISE',name, status: 'REJECTED', error})
+const actionPromise = (name, promise) =>
+    async (dispatch) => {
+        
+        dispatch(actionPending(name))
+        try {
+            let payload = await promise;
+            dispatch(actionFulfilled(name, payload));
+            
+            return payload
+        }
+        catch(error){
+            dispatch(actionRejected(name, error))
+        }
+    }
+
+
+
+
+
+const actionRootCats = () => 
+    actionPromise('rootCats', gql(`query {
+        CategoryFind(query: "[{\\"parent\\":null}]"){
+            _id name
+        }
+    }`))
+
+const actionCatById = (_id) =>  //добавить подкатегории
+    actionPromise('catById', gql(`query catById($q: String){
+        CategoryFindOne(query: $q){
+            _id name subCategories {
+                name _id
+            }
+            goods {
+                _id name price images {
+                    url
+                }
+            }
+            
+        }
+    }`, {q: JSON.stringify([{_id}])}))
+
+const actionGoodById = (_id) => 
+    actionPromise('goodById', gql(`query goodByid($goodId: String) {
+        GoodFindOne(query: $goodId) {
+          name
+          price
+          description
+          images {
+            url
+          }
+        }
+      }`, {goodId: JSON.stringify([{_id}])}))
+
+const actionFullRegister = (log,  pass) => 
+    async dispatch => {
+        let user = await dispatch(
+            actionPromise('register', gql( `mutation register($login: String, $password: String) {
+                UserUpsert(user: {login: $login, password: $password}) {
+                   _id
+                   login
+                 }
+               }`, {login : log, password : pass}))
+        )
+        if(user){
+            dispatch(actionFullLogin(log, pass));
+        }
+    }
+
+const actionFullLogin = (log, pass) => 
+      async dispatch => {
+          let token = await dispatch(
+            actionPromise('login', gql(`query login($login: String, $password: String) {
+            login(login: $login, password: $password)
+            }`, {login: log, password: pass}))
+        )
+        if(token){
+            dispatch(actionAuthLogin(token))
+        }
+      }
+
+const actionNewOrder = () => 
+    async (dispatch, getState) => {
+        const {cart} = getState();
+        let order = {orderGoods : []}    
+        for(let [key, value] of Object.entries(cart)){
+            let newValue = {...value}
+    
+            let {name,price,images, ...id} = newValue.good;
+            newValue.good = id;
+            order.orderGoods.push({...newValue})
+        }
+
+        let newOrder = await dispatch(
+            actionPromise('newOrder', gql(`mutation newOrder($order: OrderInput) {
+                OrderUpsert(order: $order) {
+                _id
+                total
+                }
+            }`, {order: order}))
+        )
+        if(newOrder){
+            dispatch(actionCartClear())
+        }
+
+    }
+
+const actionOrders = () => 
+actionPromise('orders', gql(`query findOrder($q: String) {
+    OrderFind(query: $q) {
+      _id
+      total
+      createdAt
+      orderGoods {
+        count
+        good {
+          name
+          price
+        }
+      }
+    }
+  }`, {q: JSON.stringify([{}])}));
+
+store.dispatch(actionRootCats())
+
+store.subscribe(() => {
+    const {rootCats} = store.getState().promise
+    if (rootCats?.payload){
+        aside.innerHTML = ''
+        for (const {_id, name} of rootCats.payload){
+            const link      = document.createElement('a')
+            link.href       = `#/category/${_id}`
+            link.innerText  = name
+            aside.append(link)
+        }
+    }
+})
+
+window.onhashchange = () => {
+    const [, route, _id] = location.hash.split('/');
+    console.log()
+
+    const routes = {
+        category(){
+            store.dispatch(actionCatById(_id));
+            console.log('work')
+
+        },
+        good(){ //задиспатчить actionGoodById
+            store.dispatch(actionGoodById(_id))
+            
+        },
+        login(){
+            //отрисовка тут
+            btnLogin.onclick = (e) => {e.preventDefault() ;store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))};
+        },
+        register(){
+            btnRegister.onclick = (e) => {e.preventDefault(); store.dispatch(actionFullRegister(logRegisterInput.value, passRegisterInput.value))}
+            
+        },
+        dashboard(){ //#/dashboard
+             //задиспатчить actionOrders
+            store.dispatch(actionOrders())
+            console.log('заказостраница')
+        }
+        
+    }
+    if (route in routes)
+        routes[route]()
+}
+window.onhashchange()
+
+
+store.subscribe(() => {
+    const {catById} = store.getState().promise
+    const [,route, _id] = location.hash.split('/')                                           
+    if (catById?.payload && route === 'category'){
+        
+        const {name, subCategories} = catById.payload 
+        main.innerHTML = `<h1 class="category-name">${name}</h1>`
+        if(subCategories){
+            for(let {name, _id} of subCategories){
+                const link      = document.createElement('a')
+                link.href       = `#/category/${_id}`
+                link.innerText  = name;
+                main.append(link)
+            }
+        }
+        for (const {_id, name, price, images} of catById.payload.goods){
+            const card      = document.createElement('div')
+            card.innerHTML = `<h2>${name}</h2>
+                              <img src="${backendURL}/${images[0].url}" />
+                              <strong>${price}</strong><br>
+                              <a href="#/good/${_id}">посмотреть на ${name}</a>
+                            `
+            main.append(card);
+            let btnAddToCart = document.createElement('button');
+            btnAddToCart.classList.add('btn-buy');
+            btnAddToCart.innerText = 'Добавить в корзину'
+            card.append(btnAddToCart);
+            btnAddToCart.onclick = () => store.dispatch(actionCartAdd({_id: _id, name: name, price: price, images: images}))
+        }
+    }
+})
+
+store.subscribe(() => {
+    const {goodById} = store.getState().promise
+    const [,route, _id] = location.hash.split('/') 
+    if(goodById?.payload && route === 'good'){
+        const {name, price, description, images} = goodById.payload;
+        main.innerHTML = `<h1>${name}</h1>`
+        const card = document.createElement('div');
+        card.innerHTML = `<img src="${backendURL}/${images[0].url}" />
+                          <strong>${price}</strong><br>
+                          <div>${description}</div>
+                          `
+        main.append(card);
+        let btnAddToCart = document.createElement('button');
+            btnAddToCart.classList.add('btn-buy');
+            btnAddToCart.innerText = 'Добавить в корзину'
+            card.append(btnAddToCart);
+            btnAddToCart.onclick = () => store.dispatch(actionCartAdd({_id: _id, name: name, price: price, images: images}))
+    }
+    //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
+    //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
+    //в таком случае очищаем main и рисуем информацию про товар с подробностями
+    //....А ТАК ЖЕ КНОПКА Купить, которая диспатчит actionCartAdd
+})
+
+store.subscribe(() => {           //если залогинен отрисовать юзернейм и кнопку логаут
+    const {payload} = store.getState().auth
+    if(payload?.sub){
+        registerWrap.style.display = 'none';
+        userLogoutWrap.style.display = 'block';
+        const {login} = payload.sub;
+        user.innerHTML = login;
+
+    } else {
+        registerWrap.style.display = 'block';
+        userLogoutWrap.style.display = 'none';
+    }
+})
+
+store.subscribe(() => {
+    cartUl.innerHTML = ''
+    const {cart} = store.getState();
+    for (let value of Object.values(cart)){
+        const {count, good} = value;
+        const li = document.createElement("li");
+        li.innerHTML = `<img src="${backendURL}/${good.images[0].url}"/>
+                        <strong>${good.name}</strong>
+                        <strong>${count}</strong>
+                        `
+        cartUl.append(li);
+        const input = document.createElement("input");
+        li.append(input);
+        input.value = count
+        input.oninput = () => store.dispatch(actionCartChange(good, +input.value));
+        const button = document.createElement("button");
+        button.innerText = 'удалить';
+        li.append(button);
+        button.onclick = () => store.dispatch(actionCartDelete(good))
+    }
+})
+
+store.subscribe(() => {
+    const {cart} = store.getState();
+    Object.keys(cart).length > 0 ? 
+        btnBuy.style.display='block' : 
+        btnBuy.style.display='none';
+
+    
+    btnBuy.onclick = () => {
+        store.dispatch(actionNewOrder());
+    }
+})
+
+
+store.subscribe(() => {
+    dashboardUl.innerHTML = ''
+    const {orders} = store.getState().promise;
+    const [,route, _id] = location.hash.split('/');
+    if(orders?.payload && route === 'dashboard'){
+        for(let {createdAt, total, orderGoods} of orders.payload){
+            let date = new Date(+createdAt);
+            let li = document.createElement("li");
+            for(let {count, good} of orderGoods){
+                let div = document.createElement("div");
+                div.innerHTML = `<strong>${good.name}</strong>
+                                <span>${count} &#10006; ${good.price}</span>
+                                `
+                li.append(div);
+            }
+            li.innerHTML += `<div>${total}</div>
+            <div>${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}</div>
+            <hr>`
+            dashboardUl.append(li)
+        }
+    }
+    
+})
+
+logout.onclick = () => store.dispatch(actionAuthLogout() )

+ 246 - 0
HWJS18/sass/style.scss

@@ -0,0 +1,246 @@
+body{
+    box-sizing: border-box;
+    margin: 0;
+}
+
+header{
+    position: sticky;
+    top: 0;
+    left: 0;
+    width: 100%;
+    z-index: 1;
+}
+
+
+#mainContainer {
+    display: flex;
+    position: relative;
+    
+}
+
+#aside {
+    min-width: 15%;
+    height: 100vh;
+    position: sticky;
+    top: 0;
+    left: 0;
+    background-color: rgb(252, 252, 252);
+    overflow-y: auto;
+    &::-webkit-scrollbar { width: 0 !important }
+    
+}
+
+a{
+    display: block;
+    padding: 5px 10px ;
+    text-decoration: none;
+    color: black;
+    &:hover{
+        background-color: rgb(184, 184, 184);
+    }
+}
+#main{
+    padding: 20px;
+    overflow-y: auto;
+    a{
+        display: block;
+        margin: 0 auto;
+        height: 35px;
+        width: 300px;
+        border-radius: 3px;
+        background-color: rgb(236, 236, 235);
+        box-shadow: 0px 0.5px 6px 1px rgba(0, 0, 0, .4);
+    }
+}
+
+nav{
+    padding: 10px 30px;
+    height: 50px;
+    background-color: rgb(236, 236, 235);
+    box-shadow: 0px 0.5px 6px 1px rgba(0, 0, 0, .4);
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    #signin, #register, #logout{
+        display: inline;
+        padding: 10px;
+        border-radius: 3px;
+        width: 150px;
+        height: 30px;
+        background-color: rgb(245, 245, 245);
+        box-shadow: 0px 0.5px 6px 1px rgba(0, 0, 0, .4);
+        text-decoration: none;
+        color: black;
+    }
+    #logout{
+        width: 70px;
+        height: 25px;
+        padding: 0;
+    }
+}
+
+#user{
+    display: inline;
+    margin-right: 15px;
+}
+
+#cart-btn, #dashboard-btn{
+    display: inline;
+    // padding: 10px;
+    border-radius: 3px;
+    width: 80px;
+    height: 30px;
+    background-color: rgb(245, 245, 245);
+    box-shadow: 0px 0.5px 6px 1px rgba(0, 0, 0, .4);
+    text-decoration: none;
+    color: black;
+    margin-right: 20px;
+}
+
+#userlogout-wrap{
+    display: none;
+}
+
+.overlay{
+    position: fixed;
+    height: 100%;
+    width: 100%;
+    top: 0;
+    padding: 40px;
+    z-index: 200;
+    display: none;
+    background-color: rgba(0, 0, 0, .55);
+    
+    
+}
+#signup-form, #login-form{
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    display: none;
+    width: 350px;
+    height: 250px;
+    padding: 25px 15px;
+    margin: 0 auto;
+    background-color: rgb(255, 255, 255);
+    box-shadow: 1px 1.5px 4.8px 0.3px rgba(0, 0, 0, 0.4);
+    border-radius: 5px;
+    
+}
+
+#signup, #signup-password, #login, #login-password{
+    display: block;
+    margin: 0 auto;
+    border: none;
+    width: 85%;
+    height: 30px;
+    font-size: 16px;
+    box-shadow: 1px 1.5px 4.8px 0.3px rgba(0, 0, 0, 0.4);
+    border-radius: 4px;
+    margin-bottom: 0px;
+    padding: 5px 10px;
+}
+
+.form-btn{
+    border: none;
+    width: 120px;
+    height: 30px;
+    display: block;
+    margin: 15px auto;
+    box-shadow: 1px 1.5px 4.8px 0.3px rgba(0, 0, 0, 0.4);
+    background-color:aliceblue;
+    color:rgb(134, 134, 134);
+
+}
+
+.form-error{
+    width: 100%;
+    margin-top: 30px;
+    color: rgb(207, 109, 109);
+    text-align: center;
+}
+
+.close{
+    position: absolute;
+    font-size: 35px;
+    color:rgb(202, 202, 202);
+    top: -5px;
+    right: -25px;
+    cursor: pointer;
+}
+
+.category-name{
+    // background-color: darkgray;
+    font-size: 30px;
+    padding: 5px;
+    border-radius: 3px;
+    background-color: rgb(236, 236, 235);
+    box-shadow: 0px 0.5px 6px 1px rgba(0, 0, 0, .4);
+}
+
+.btn-buy{
+    display: block;
+    margin: 0 auto;
+    height: 35px;
+    width: 100px;
+    border-radius: 3px;
+    background-color: rgb(236, 236, 235);
+    box-shadow: 0px 0.5px 6px 1px rgba(0, 0, 0, .4);
+}
+
+#btn-buy{
+    display: none;
+}
+
+#cart-wrap, #dashboard-wrap{
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    display: none;
+    width: 70%;
+    min-height: 250px;
+    max-height: 300px;
+    padding: 25px 15px;
+    margin: 0 auto;
+    background-color: rgb(255, 255, 255);
+    box-shadow: 1px 1.5px 4.8px 0.3px rgba(0, 0, 0, 0.4);
+    border-radius: 5px;
+    div{
+        max-height: 300px;
+        overflow-y: auto;
+        &::-webkit-scrollbar { width: 0}
+    }
+    
+    
+}
+
+
+
+
+#cart, #dashboard{
+    list-style-type: none;
+    padding: 0;
+    
+    li{
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        img{
+            height: 60px;
+            width: 60px;
+        }
+        input{
+            height: 20px;
+            width: 40px;
+        }
+    }
+}
+#dashboard{
+    display: flex;
+    flex-direction: column-reverse;
+    li{
+        display: block;
+    }
+}