Browse Source

HW <SHOP done>

Vadym Shakhmatenko 1 year ago
parent
commit
c8449e9f5e

BIN
Homework Shop/Loading_icon.gif


+ 47 - 0
Homework Shop/index.html

@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <title>GQL</title>
+        <meta charset='utf8' />
+        <link rel="stylesheet" href="style.css">
+    </head>
+    <body>
+        <header>
+            <nav class="navbar header" style="padding: 10px;">
+                <a class="logo" href="#">BEST SALLER</a>
+                <div class="menu" id="navbarNav">
+                  <ul class="ul-menu">
+                    <li class="item">
+                      <a class="" href="index.html">Главная</a>
+                    </li>
+                    <li class="item">
+                        <a class="" href="#/car" id='CountCar'><b>Корзина</b></a>
+                    </li>
+                    
+                  </ul>
+                  <form id="form_no">
+                    <a href="#/register" type="submit">Регистрация</a>
+
+                    <a href="#/login">Вход</a>
+                  </form>
+                  <form id="form_yes">
+                    
+                    <a href="#/orderFind" id="UserNick">В</a>
+                    <a onclick="store.dispatch(actionAuthLogout());display()" class="log-btn" type="submit">Выход</a>
+                  </form>
+                </div>
+              </nav>
+        </header>
+        <div id='mainContainer'>
+            <aside>
+                <ul id='aside'>
+                </ul>
+            </aside>
+            <main id='main'>
+            </main>
+            
+        </div>
+        <script src='index2.js'></script>
+       
+    </body>
+</html>

+ 591 - 0
Homework Shop/index2.js

@@ -0,0 +1,591 @@
+
+// !store
+function createStore(reducer){
+    let state       = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
+    let cbs         = []                     //массив подписчиков
+    
+    const getState  = () => state            //функция, возвращающая переменную из замыкания
+    const subscribe = cb => (cbs.push(cb),   //запоминаем подписчиков в массиве
+                             () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
+                             
+    const dispatch  = action => { 
+        if (typeof action === 'function'){ //если action - не объект, а функция
+            return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
+        }
+        const newState = reducer(state, action) //пробуем запустить редьюсер
+        if (newState !== state){ //проверяем, смог ли редьюсер обработать action
+            state = newState //если смог, то обновляем state 
+            for (let cb of cbs)  cb() //и запускаем подписчиков
+        }
+    }
+    
+    return {
+        getState, //добавление функции getState в результирующий объект
+        dispatch,
+        subscribe //добавление subscribe в объект
+    }
+}
+
+
+// ! decoding token
+function jwtDecode(token){
+    try {
+        return JSON.parse(atob(token.split('.')[1]))
+    }
+    catch(e){
+    }
+}
+
+
+// ! reducers
+function authReducer(state, {type, token}){
+    if(state === undefined){
+        if(localStorage.authToken){
+            token = localStorage.authToken
+            type = 'AUTH_LOGIN'
+        }
+        else{
+            type = 'AUTH_LOGOUT'
+        } 
+    }
+    if(type === 'AUTH_LOGIN'){
+        state = jwtDecode(token)
+        localStorage.authToken = token
+    }
+    if(type === 'AUTH_LOGOUT'){
+        localStorage.authToken = ''
+        state = {}
+    }
+    return state || {}
+}
+
+function promiseReducer(state={}, {type, name, status, payload, error}){
+    if (type === 'PROMISE'){
+        return {
+            ...state,
+            [name]:{status, payload, error}
+        }
+    }
+    return state
+}
+
+
+function cartReducer(state={}, {type, good, count=1}){
+    if (type === 'CART_ADD'){
+        return { 
+            ...state, 
+            [good._id]: {count: (state[good._id]?.count || 0) + count, good:good}
+        }
+    }
+    if (type === 'CART_CHANGE'){
+        return { 
+            ...state, 
+            [good._id]: {count, good}
+        }
+    }
+    if (type === 'CART_DELETE'){
+        delete state[good._id]
+        return { 
+            ...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})
+const actionCartDelete = (good)          => ({type: 'CART_DELETE', good})
+const actionCartClear  = ()              => ({type: 'CART_CLEAR'})
+
+function combineReducers(reducers){ //пачку редьюсеров как объект {auth: authReducer, promise: promiseReducer}
+    function combinedReducer(combinedState={}, action){ //combinedState - типа {auth: {...}, promise: {....}}
+        const newCombinedState = {}
+        for (const [reducerName, reducer] of Object.entries(reducers)){
+            const newSubState = reducer(combinedState[reducerName], action)
+            if (newSubState !== combinedState[reducerName]){
+                newCombinedState[reducerName] = newSubState
+            }
+        }
+        if (Object.keys(newCombinedState).length === 0){
+            return combinedState
+        }
+        return {...combinedState, ...newCombinedState}
+    }
+
+    return combinedReducer //нам возвращают один редьюсер, который имеет стейт вида {auth: {...стейт authReducer-а}, promise: {...стейт promiseReducer-а}}
+}
+
+ const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart:cartReducer}))
+ store.subscribe(() => console.log(store.getState()))
+ 
+// ! GQL
+const getGQL = url =>
+    (query, variables) => fetch(url, {
+        method: 'POST',
+        headers: {
+            "Content-Type": "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.node.ed.asmer.org.ua/graphql'
+
+const gql = getGQL(backendURL + '/graphql')
+
+// !PROMISE
+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 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})
+
+// ! ACTIONS
+const actionFullRegister = (login, password) => 
+    actionPromise('fullRegister', gql(`mutation UserUpsert($login: String, $password: String){UserUpsert(user: {login:$login,password:$password}){_id}}`, {login: login, password:password}))
+
+
+const actionAuthLogin = (token) =>
+    (dispatch, getState) => {
+        const oldState = getState()
+        dispatch({type: 'AUTH_LOGIN', token})
+        const newState = getState()
+        if (oldState !== newState)
+            localStorage.authToken = token
+    }
+
+
+const actionFullLogin = (login, password) =>  //вход 
+actionPromise('fullLogin', gql(`query login($login:String,$password:String){login(login:$login,password:$password)}`, {login: login, password:password}))
+
+
+const actionAuthLogout = () => 
+    dispatch => {
+        dispatch({type: 'AUTH_LOGOUT'})
+        localStorage.removeItem('authToken')
+    }
+
+const orderFind = () =>  //история заказов
+actionPromise('orderFind', gql(`query orderFind{
+    OrderFind(query: "[{}]"){
+        _id createdAt total orderGoods {_id price count good{name price images{url}}}
+    }
+}`, {q: JSON.stringify([{}])}))
+
+const actionAddOrder = (cart) =>  //оформ. заказа
+actionPromise('actionAddOrder', gql(`mutation newOrder($cart: [OrderGoodInput])
+{OrderUpsert(order: {orderGoods: $cart})
+{_id total}}`, {cart: cart}))
+
+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 goods {
+                _id name price images {
+                    url
+                }
+                
+            }
+            subCategories{_id name}
+            
+        }
+    }`, {q: JSON.stringify([{_id}])}))
+
+const actionGoodById = (_id) =>  
+    actionPromise('goodById', gql(`query goodById($q: String){
+        GoodFindOne(query: $q){
+            _id name price description images {
+                url
+            }
+        }
+    }`, {q: JSON.stringify([{_id}])}))
+
+
+store.dispatch(actionRootCats())
+
+
+// !SUBSCRIBE
+
+store.subscribe(() => {
+    
+    const {rootCats} = (store.getState()).promise 
+    if (rootCats?.payload){
+        aside.innerHTML = `<li class="list-group-item"><b>Категории</b></li>`
+
+        for (const {_id, name} of rootCats?.payload){
+            const categories      = document.createElement('li')
+            categories.innerHTML  = `<a href='#/category/${_id}'>${name}</a>`
+            categories.style = ' padding-left: 30px ; '
+         
+            aside.append(categories)
+        }
+    }
+})
+
+
+store.subscribe(() => {
+    const {catById} = (store.getState()).promise 
+    const [,route, _id] = location.hash.split('/')
+    if (catById?.payload && route === 'category'){
+
+        main.innerHTML = ``
+        const {name} = catById.payload
+        
+        const card      = document.createElement('div')
+        card.style     = 'height: auto;width: 100%;border-style: groove;border-color: #ced4da17;padding: 10px;border-radius: 10px;margin: 5px;' 
+        card.innerHTML = `<h4><b>${name}</b></h4><br>`
+        
+        if(catById.payload.subCategories){
+            for (const {_id, name} of catById.payload?.subCategories){
+                card.innerHTML +=`<a href='#/category/${_id}' class='subcategories'>${name}</a>`
+            }
+        }
+
+        main.append(card)
+        for (const {_id, name, price, images} of catById.payload?.goods){
+            const card      = document.createElement('div')
+            card.style     = 'height: auto;width: 30%;border-style: groove;border-color: #ced4da17;padding: 10px;border-radius: 10px;margin: 5px; display: flex ; flex-direction: column ; justify-content: space-between' 
+            card.innerHTML = `<h5><b>${name}</b></h5>
+                              <img src="http://shop-roles.node.ed.asmer.org.ua/${images[0].url}" style="max-width: 100%; max-height: 300px;"/><br>
+                              <strong>Цена: ${price} грн.</strong><br><br>
+                              <a class="" style="width: 100%;" href='#/good/${_id}'>Подробнее</a><br><br>`
+            let button = document.createElement('button')
+            button.innerText = 'Купить' 
+            button.className = 'btn-buy'
+            button.style = 'width: 100%; font-family: Impact; letter-spacing : 1px'
+            button.onclick = async () => {
+                await store.dispatch(actionCartAdd({_id: _id, name: name, price: price, images: images})) 
+                console.log('tap')
+            }
+            card.append(button)
+            main.append(card)
+        }
+    }
+})
+
+store.subscribe(() => {
+    const {goodById} = (store.getState()).promise 
+    const [,route, _id] = location.hash.split('/')
+    if (goodById?.payload && route === 'good'){
+        const {name,description,images,price} = goodById.payload
+        main.innerHTML = `<h1>${name}</h1>`
+        
+        const card      = document.createElement('div')
+        card.innerHTML = `<img src="http://shop-roles.node.ed.asmer.org.ua/${images[0].url}" /><br>
+                          <b>Цена: ${price} грн.</b><br>
+                          <p><b>Описание:</b> ${description}</p>`
+        main.append(card)
+        
+    }
+})
+
+store.subscribe(() => {
+    const {orderFind} = (store.getState()).promise 
+    const [,route, _id] = location.hash.split('/')
+    
+    if (orderFind?.payload && route === 'orderFind'){
+        main.innerHTML='<h1>История заказов</h1>'
+
+        for (const {_id, createdAt, total,orderGoods} of orderFind.payload.reverse()){
+            const card      = document.createElement('div')
+            card.style     = 'width: 100%;border-style: groove;border-color: #ced4da17;padding: 10px;border-radius: 10px;margin: 5px;' 
+            card.innerHTML = `<h3>Заказ: ${createdAt}</h3>`
+            for (const {count, good} of orderGoods){2
+                const divGood      = document.createElement('div')
+                divGood.style= "display:flex;margin-bottom: 20px;"
+                
+                divGood.innerHTML += `<div>Товар: <b>${good.name}</b><br> Цена: <b>${good.price} грн.</b><br> Количество: <b>${count} шт.</b></b></div><img style="max-width: 80px;margin-right: 20px;display: block;margin-left: auto;" src="http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}"/><br><br>`
+                card.append(divGood)
+            }
+            card.innerHTML += 'Дата: <b>'+new Date(+createdAt).toLocaleString().replace(/\//g, '.')+'</b>'
+            card.innerHTML += `<br>Всего: <b style="color:red;">${total} грн.</b>`
+            main.append(card)
+        }
+    }
+})
+
+// !WINDOW
+
+function display(){
+    let token = localStorage.authToken
+    if(token){
+        form_yes.style.display = 'block'
+       form_no.style.display = 'none'
+        UserNick.innerText=JSON.parse(window.atob(localStorage.authToken.split('.')[1])).sub.login
+    }else{
+        form_yes.style.display = 'none'
+       form_no.style.display = 'block'
+    }   
+}
+display()
+
+window.onhashchange = () => {
+    const [, route, _id] = location.hash.split('/')
+    mainContainer.scrollTo(0,0);
+
+    const routes = {
+        category(){
+            store.dispatch(actionCatById(_id))
+        },
+        good(){ 
+            store.dispatch(actionGoodById(_id))
+        },
+        login(){
+
+            main.innerHTML = ''
+            let form = document.createElement('div')
+            let div = document.createElement('div')
+
+            div.innerHTML += `<h1>Вход</h1>`
+            let inputLogin = document.createElement('input')
+            inputLogin.placeholder="Login"
+            inputLogin.name = "login"
+            div.append(inputLogin)
+
+            form.append(div)
+
+            let div2 = document.createElement('div')
+            div.style.display = 'flex'
+            div.style.flexDirection = 'column'
+
+            let inputPassword = document.createElement('input')
+            inputPassword.placeholder = "Password"
+            inputPassword.name = "password"
+
+            
+            div2.append(inputPassword)
+            
+            form.append(div2)
+
+            let button = document.createElement('button')
+            button.innerText="Войти"
+            button.style.padding = '15px 35px'
+            button.style.marginTop = '20px'
+            button.style.backgroundColor = 'yellowgreen'
+            button.style.textTransform = 'uppercase'
+            button.style.fontFamily = 'Impact'
+            button.style.fontSize = '15px'
+            
+            button.onclick =  async () => {
+                
+                let tokenPromise = async () => await store.dispatch(actionFullLogin(inputLogin.value, inputPassword.value))
+                let token = await tokenPromise()
+                
+                if(token!==null){
+                    store.dispatch(actionAuthLogin(token))
+                    console.log(token)
+                    display()
+                    document.location.href = "#/orderFind";
+                }
+                else{
+                    inputLogin.value = ''
+                    inputPassword.value = ''
+                    alert("Введен неверный логин или пароль !")
+                    store.dispatch(actionAuthLogout())
+                }
+            }
+
+            form.append(button)
+            main.append(form)
+
+        },
+        register(){
+            main.innerHTML = ''
+            let form = document.createElement('div')
+            let div = document.createElement('div')
+
+            div.innerHTML += `<h1>Регистрация</h1>`
+            let inputLogin = document.createElement('input')
+            inputLogin.placeholder="Login"
+            div.append(inputLogin)
+
+            form.append(div)
+
+            let div2 = document.createElement('div')
+
+            let inputPassword = document.createElement('input')
+            inputPassword.placeholder="Password"
+            
+            div2.append(inputPassword)
+            
+            form.append(div2)
+
+            let button = document.createElement('button')
+            button.innerText="Зарегистрироваться"
+            button.style.padding = '15px 35px'
+            button.style.marginTop = '20px'
+            button.style.backgroundColor = 'yellowgreen'
+            button.style.textTransform = 'uppercase'
+            button.style.fontFamily = 'Impact'
+            button.style.fontSize = '15px'
+
+
+            let textAlert = document.createElement('div')
+            let textAlert2 = document.createElement('div')
+
+
+
+            let putInText = "Введите данные!"
+            let userAlready = "Пользователь с таким логином уже зарегистрирован!"
+            textAlert.append(userAlready)
+            textAlert2.append(putInText)
+
+            textAlert2.style = 'display : none; color : red'
+            textAlert.style = 'display : none; color : red'
+            
+
+            button.onclick =  async () => {
+                let register = await store.dispatch(actionFullRegister(inputLogin.value, inputPassword.value))
+                let tokenPromise = async () => await store.dispatch(actionFullLogin(inputLogin.value, inputPassword.value))
+
+                if(inputLogin.value == '' || inputPassword.value == ''){
+                    textAlert2.style.display = 'block'
+
+                }else{
+                    if(register !==null){
+                        let token = await tokenPromise()
+                        store.dispatch(actionAuthLogin(token))
+                        console.log(token)
+                        display()
+                        document.location.href = "#/orderFind";
+
+                    }else{
+                    textAlert.style.display = 'block'
+                    textAlert2.style.display = 'none'
+                    }
+                }
+
+            }
+            form.append(textAlert , textAlert2)
+            form.append(button)
+            main.append(form)
+        },
+        orderFind(){ 
+            store.dispatch(orderFind())
+        },
+        car(){ 
+                        main.innerHTML = '<h1>Корзина</h1>'
+                        for (const [_id, obj] of Object.entries(store.getState().cart)){
+                            const card      = document.createElement('div')
+                            card.style     = 'width: 33.33%;border-style: groove;border-color: #ced4da17;padding: 10px;border-radius: 10px;margin: 5px;display: flex; flex-direction: column ; align-items: center ; justify-content: space-between' 
+                            
+                            const {count, good} = obj
+                                
+                            card.innerHTML += `Товар: <b>${good.name}</b> <br><img src="http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}" style="width: 100px"/> <br> Цена: <b>${good.price} грн.</b><br><br>`
+            
+                            const calculation = document.createElement('div')
+            
+                            const buttonAdd = document.createElement('button')
+                            buttonAdd.innerHTML = '+'
+                            buttonAdd.style.width = '35px'
+                            buttonAdd.onclick = async () => {
+                                inputCount.value = +inputCount.value + 1 
+                                await store.dispatch(actionCartChange({_id: _id, name: good.name, price: good.price, images: good.images}, +inputCount.value)) 
+
+                                cardTotal.innerHTML = `<br>Всего: <b style="color:red;">${goodPrice()} грн.</b><br>`
+                            }
+                            calculation.append(buttonAdd)
+            
+                            const inputCount = document.createElement('input')
+                            inputCount.value = +count
+                            inputCount.disabled = 'disabled'
+                            inputCount.className = 'inputCount'
+                            calculation.append(inputCount)
+            
+                            const buttonLess = document.createElement('button')
+                            buttonLess.innerHTML = '-'
+                            buttonLess.style.width = '35px'
+                            buttonLess.onclick = async () => {
+                                if((+inputCount.value)>1){
+                                    inputCount.value = +inputCount.value - 1 
+                                    await store.dispatch(actionCartChange({_id: _id, name: good.name, price: good.price, images: good.images}, +inputCount.value)) 
+
+                                    cardTotal.innerHTML = `<br>Всего: <b style="color:red;">${goodPrice()} грн.</b><br>`
+                                }
+                                 
+                            }
+              
+                            calculation.append(buttonLess)
+            
+                            const buttonDelete = document.createElement('button')
+                            buttonDelete.innerText = 'Удалить'
+                            buttonDelete.className = 'buttonDelete'
+                            buttonDelete.onclick = async () => {
+                                await store.dispatch(actionCartDelete({_id: _id, name: good.name, price: good.price, images: good.images})) 
+
+                                card.style.display = 'none'
+                                cardTotal.innerHTML = `<br>Всего: <b style="color:red;">${goodPrice()} грн.</b><br>`
+                            }
+                            card.append(calculation)
+                            card.append(buttonDelete)
+                            main.append(card)
+                        }
+                        const cardTotalDiv      = document.createElement('div')
+                        const cardTotal      = document.createElement('div')
+                        
+                        cardTotalDiv.style = 'position : absolute; display : flex;right : 50px; bottom: 0px'
+                        cardTotal.innerHTML = `<br>Всего: <b style="color:red;">${goodPrice()} грн.</b>`
+                        
+                        cardTotalDiv.append(cardTotal)
+            
+                        if(localStorage.authToken!=''){
+                            const button      = document.createElement('button')
+                            button.innerHTML += 'ЗАКАЗАТЬ'
+                            button.style = ' background-color : yellowgreen; font-family: Impact; font-size : 40px'
+
+                            button.onclick =  async () => {
+                                await store.dispatch(actionAddOrder(Object.entries(store.getState().cart).map(([_id, count]) => ({count:count.count,good:{_id}}))));
+                                await store.dispatch(actionCartClear());
+                                document.location.href = "#/orderFind";
+                            } 
+                            button.className = 'btn btn-primary'
+                            cardTotalDiv.append(button)
+            
+                        }
+                        
+                        main.append(cardTotalDiv)
+                    
+
+        }    
+    }
+    if (route in routes)
+        routes[route]()
+}
+window.onhashchange()
+
+
+
+function goodPrice(){
+    return Object.entries(store.getState().cart).map(i=>x+=i[1].count*i[1].good.price, x=0).reverse()[0] || 0
+}
+
+
+

+ 135 - 0
Homework Shop/style.css

@@ -0,0 +1,135 @@
+/* *{padding: 0;margin: 0;border: 0;}
+*,*:before,*:after{-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}
+:focus,:active{outline: none;}
+a:focus,a:active{outline: none;}
+nav,footer,header,aside{display: block;}
+html,body{height: 100%;width: 100%;font-size: 100%;line-height: 1;font-size: 14px;-ms-text-size-adjust: 100%}
+input,button,textarea{font-family: inherit;}
+input::-ms-clear{display: none;}
+button{cursor: pointer;}
+button::-moz-focus-inner{padding: 0;border: 0;}
+a,a:visited{text-decoration: none;}
+a:hover{text-decoration: none;}
+ul li {list-style: none;}
+img{vertical-align: top;}
+h1,h2,h3,h4,h5,h6{font-size: inherit;font-weight: inherit;} */
+
+
+body{
+    font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
+}
+.header{
+    background-color: greenyellow;
+    display: flex;
+}
+
+.menu {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding-left: 50%;
+    position: absolute;
+    right: 0;
+}        
+.ul-menu {
+    display: flex;
+    margin-top: 27px;
+}    
+
+.logo{
+    font-size: 40px;
+    color: black;
+}
+
+a  , li{
+    text-decoration: none;
+    list-style-type: none;
+    color: black;
+    font-size: 25px;
+    padding: 0px 20px 10px 0;
+}
+
+.btn-buy {
+    background-color: yellowgreen;
+    font-size: 20px;
+}
+
+.log-btn {
+    background-color: whitesmoke;
+    color: black;
+    text-align: center;
+    border-radius: 5px;
+
+}
+
+
+
+
+#mainContainer {
+    display: flex;
+    max-height: 90%;
+    position: fixed;
+    overflow-y: auto;
+    width: 100%;
+}
+#aside {
+    width: 30%;
+    min-width: 300px;
+}
+#aside > a{
+    display: block;
+}
+#main{
+    display: flex;
+    flex-wrap: wrap;
+    width: 100%;
+    padding: 20px;
+}
+h1{
+    width: 100%;
+}
+
+h5 {
+    font-size: 1.25rem;
+    min-height: 50px;
+}
+.subcategories{
+    background-color: yellowgreen;
+    color: #fff;
+    text-decoration: none;
+    padding: 8px;
+    border-radius: 0.25rem;
+    margin-right: 10px;
+    border: 2px solid yellowgreen;
+}
+.subcategories:hover{
+    background-color: #ffffff;
+    color: yellowgreen;
+    text-decoration: none;
+    padding: 8px;
+    border-radius: 0.25rem;
+    margin-right: 10px;
+}
+#aside a{
+    text-decoration: none;
+    position: relative;
+    display: block;
+    margin: -0.5rem -1rem;
+    padding: 0.5rem 1rem;
+}
+#aside a:hover{
+    background-color: #0d6efd1c;
+    font-weight: 600;
+}
+
+.inputCount{
+    width: 35px;
+    text-align: center;
+    padding: 4px;
+    margin: 0 10px;
+}
+.buttonDelete{
+    width: 125px;
+    margin-top: 10px;
+    height: 20px;
+}

+ 8 - 0
Homework Shop/workspace.code-workspace

@@ -0,0 +1,8 @@
+{
+	"folders": [
+		{
+			"path": ".."
+		}
+	],
+	"settings": {}
+}