function createStore(reducer){
let state = reducer(undefined, {})
let cbs = []
function dispatch(action){
if (typeof action === 'function'){
return action(dispatch)
}
const newState = reducer(state, action)
if (state !== newState){
state = newState
cbs.forEach(cb => cb())
}
}
return {
dispatch,
subscribe(cb){
cbs.push(cb)
return () => cbs = cbs.filter(c => c !== cb)
},
getState(){
return state
}
}
}
function promiseReducer(state={}, {type, status, payload, error, name}){
if (type === 'PROMISE'){
return {
...state,
[name]:{status, payload, error}
}
}
return state
}
//под товаром сделать кнопку "купить"
const actionPending = name => ({type: 'PROMISE', status: 'PENDING', name})
const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
const actionPromise = (name, promise) =>
async dispatch => {
dispatch(actionPending(name))
try{
let payload = await promise
dispatch(actionResolved(name, payload))
return payload
}
catch(error){
dispatch(actionRejected(name, error))
}
}
function cartReducer(state={},{type , count = 1, _id , name , price }) {
if(type === 'CART_ADD'){
return {
...state ,
[_id]:{count:(state[_id]?.count || 0) + count, name , price}
}
}
if (type === 'CART_CHANGE' ) {
return {
...state,
[_id]:{count:state[count] = count,price}
}
}
if(type === 'CART_REMOVE'){
let {[_id]:id , ...res} = state
return res
}
if (type === "CART_CLEAR"){
return {}
}
return state
}
// store.dispatch({type: 'CART_ADD', _id:id, count:num})
// store.dispatch({type: 'CART_CHANGE', _id:id, count:count})
// store.dispatch({type: 'CART_REMOVE', _id:id})
// store.dispatch({type: 'CART_CLEAR'})
let reducers = {
promise:promiseReducer,
cart:cartReducer,
auth:authReducer
}
function combineReducers(reducers){
function commonReducer(state = {} , action){
let commonState = {}
for(let reducerName in reducers){
const reducerState = reducers[reducerName](state[reducerName],action)
if (reducerState !== state[reducerName]){
commonState[reducerName] = reducerState
}
}
if (Object.keys(commonState).length == 0){
return state
}
return {...state,...commonState}
}
return commonReducer
}
const store = createStore(combineReducers(reducers))
const unsubscribe = store.subscribe(() => console.log(store.getState()))
// store.dispatch({type: 'CART_ADD', _id:'beer', count:2})
const getGQL = url =>
(query, variables={}) => fetch(url, {
method: 'POST',
headers: {
// Accept: "application/json",
"Content-Type": "application/json",
...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
},
body: JSON.stringify({query, variables})
}).then(res => res.json())
let shopGQL = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql')
function authReducer(state, action){ //....
if (state === undefined){
if (!localStorage.authToken){
return {}
}
action.token = localStorage.authToken
action.type = 'LOGIN'
// добавить в action token из localStorage, и проимитировать LOGIN
}
if (action.type === 'LOGIN'){
console.log('ЛОГИН')
localStorage.authToken = action.token
function jwt_decode (token) {
var start64Url = token.split('.')[1]
return JSON.parse(atob(start64Url))
}
return {token: action.token, payload: jwt_decode(action.token)}
}
if (action.type === 'LOGOUT'){
console.log('ЛОГАУТ')
localStorage.removeItem("authToken")
//вернуть пустой объект
return {}
}
return state
}
const actionAuthLogin = token => ({type:'LOGIN', token})
const actionAuthLogout = () => ({type:'LOGOUT'})
let reg = async(login,password) => {
let query = `mutation reg($l:String , $p:String) {
UserUpsert(user:{
login:$l ,
password:$p
}){
_id
}
}`
let qVariables = {
"l": login,
"p": password
}
let result = await shopGQL(query,qVariables)
return result
}
let log = async(login , password) => {
let query = ` query log($l:String , $p:String){
login(login:$l, password:$p)
}`
let qVariables = {
"l": login,
"p": password
}
let result = await shopGQL(query,qVariables)
return result
}
actionRegister = (login,password) => async dispatch => {
return await dispatch (actionPromise('register' , reg(login,password)))
}
const goodById = id => {
let query = `query goodById($query:String) {
GoodFindOne(query: $query ) {
_id
name
description
price
images {
url
}
}
}`
let variables = {
query: JSON.stringify([{_id: id}])
}
let res = shopGQL(query,variables)
return res
}
const actionGoodById = id =>
actionPromise('goodById', goodById(id))
const actionRootCategories = () =>
actionPromise('rootCategories', shopGQL(`
query cats($query:String){
CategoryFind(query:$query){
_id name
}
}
`, {query: JSON.stringify([{parent:null}])}))
const actionCategoryById = (_id) =>
actionPromise('catById', shopGQL(`query catById($query:String){
CategoryFindOne(query:$query){
_id name goods{
_id name price description images{
url
}
}
}
}`, {query: JSON.stringify([{_id}])}))
store.dispatch(actionRootCategories())
actionCartAdd = (id , num=1, name, price) =>
({type: 'CART_ADD', _id:id, count:num,name , price})
actionCartChange = (id,count,price) =>
({type: 'CART_CHANGE', _id:id, count,price})
actionCartRemove = (id) =>
({type: 'CART_REMOVE', _id:id})
actionCartClear = () =>
({type: 'CART_CLEAR'})
window.onhashchange = () => {
let {1: route, 2:id} = location.hash.split('/')
if (route === 'categories'){
main.innerHTML = ''
store.dispatch(actionCategoryById(id))
}
if (route === 'good'){
main.innerHTML = ''
store.dispatch(actionGoodById(id))
}
if (route === 'cart'){
drawCart()
}
if (route === 'registration'){
drawReg()
}
if (route === 'login'){
drawLog()
}
// if (route === 'cabinet') {
// console.log('aaaa')
// }
}
function drawMainMenu(){
let cats = store.getState().promise.rootCategories.payload
if (cats){ //каждый раз дорисовываются в body
aside.innerText = ''
for (let {_id, name} of cats.data.CategoryFind){
let catA = document.createElement('a')
catA.href = `#/categories/${_id}`
catA.innerText = name
aside.style.marginLeft = '20px'
aside.append(catA)
}
}
}
store.subscribe(drawMainMenu)
let aBasket = document.createElement('a')
aBasket.href = "#/cart/"
aBasket.style.color = '#008B8B'
let imgBasket = document.createElement('img')
imgBasket.style.float = 'right'
let header = document.getElementById('header')
header.style.height = '70px'
let countBasket = document.createElement('p')
let h1 = document.getElementById('h1')
h1.style.float = 'left'
h1.style.marginTop = '30px'
countBasket.style.float = 'right'
countBasket.style.marginLeft = '20px'
countBasket.style.marginRight = '20px'
countBasket.style.marginTop = '20px'
countBasket.style.fontWeight = 'bold'
countBasket.innerHTML = "Товаров в корзине:" + " " + 0
imgBasket.src = "basket.png"
imgBasket.style.width = '50px'
imgBasket.style.marginLeft = '30px'
aBasket.append(countBasket)
aBasket.append(imgBasket)
header.append(aBasket)
const unsubscribe1 = store.subscribe(() => {
let cartState = store.getState().cart
var result = []
for (key in cartState){
result.push(cartState[key].count)
if (result.length > 0) {
countBasket.innerHTML ="Товаров в корзине:" + " " + result.reduce(function(a,b){
return a+b
})
}
else {
countBasket.innerHTML ="Товаров в корзине:" + " " + 0
}
}
})
let aRegBtn = document.createElement('a')
let regBtn = document.createElement('button')
let aLogBtn = document.createElement('a')
let logBtn = document.createElement('button')
aLogBtn.href = '#/login'
logBtn.innerHTML = 'Вход'
aRegBtn.style.marginTop = '30px'
aLogBtn.style.marginTop = '30px'
aLogBtn.style.marginLeft = '10px'
aRegBtn.href = '#/registration'
regBtn.innerHTML = "Регистрация"
aRegBtn.style.float = 'right'
aLogBtn.style.float = 'right'
aLogBtn.append(logBtn)
header.append(aLogBtn)
aRegBtn.append(regBtn)
header.append(aRegBtn)
// let aCabinet = document.createElement('a')
// aCabinet.href = '#/cabinet'
let unlogBtn = document.createElement('button')
unlogBtn.style.marginTop = '20px'
unlogBtn.style.float = 'right'
unlogBtn.innerHTML = "Выйти"
unlogBtn.style.marginLeft = '10px'
unlogBtn.onclick = () => {
let question = confirm ('Вы уверены что хотите выйти?')
if (question) {
store.dispatch(actionAuthLogout())
location.reload()
}
}
function drawReg() {
main.innerHTML = ""
let h = document.createElement('h1')
h.innerHTML = 'Регистрация'
main.append(h)
function Password (parent , open) {
let passwordInput = document.createElement ('input')
let passwordCheckbox = document.createElement('input')
let passwordSpan = document.createElement('span')
let passwordContent = document.createElement('div')
parent.append(passwordContent)
passwordContent.append(passwordInput)
passwordContent.append(passwordCheckbox)
passwordContent.append(passwordSpan)
passwordContent.style.marginTop = "15px"
passwordContent.style.marginBottom = '20px'
passwordInput.placeholder = "Enter a password"
passwordCheckbox.type = 'checkbox'
passwordCheckbox.style.marginLeft = '10px'
passwordSpan.innerHTML = "Hide password"
passwordSpan.style.marginLeft = "10px"
passwordInput.onchange = () => {
if(typeof this.onChange === 'function'){
this.onChange(passwordInput.value)
}
}
function showOrHide() {
if (passwordCheckbox.checked) {
passwordInput.setAttribute('type' , 'password')
} else {
passwordInput.setAttribute('type','text')
}
}
passwordCheckbox.addEventListener('change' , showOrHide)
this.setValue = function (text) {
passwordInput.value = text
}
this.getValue = function () {
return passwordInput.value
}
this.setOpen = function (checker) {
showOrHide.call(this)
passwordCheckbox.checked = checker
}
passwordCheckbox.onclick = () => {
showOrHide()
this.onOpenChange("нажали чекбокс")
}
this.getOpen = function () {
return passwordCheckbox.checked
}
}
function LoginFormConstructor (parent , open) {
let passwordForm = document.createElement('div')
let loginForm = document.createElement('div')
let btnForm = document.createElement('div')
let loginInput = document.createElement('input')
loginInput.type = 'text'
loginInput.style.marginBottom = '10px'
loginInput.placeholder = "Enter a login"
let passwordInput = document.createElement('input')
passwordInput.type = 'password'
passwordInput.placeholder = "Enter a password"
let checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.style.marginLeft = '7px'
let btn = document.createElement('button')
btn.style.marginLeft = '130px'
btn.style.marginTop = '10px'
btn.innerHTML = 'Log in'
parent.append(loginForm)
parent.append(passwordForm)
parent.append(btnForm)
loginForm.append(loginInput)
passwordForm.append(passwordInput)
passwordForm.append(checkbox)
btnForm.append(btn)
btn.onclick = () => {
store.dispatch(actionFullRegister((loginInput.value), (passwordInput.value)))
}
function showOrHide() {
if (checkbox.checked) {
passwordInput.setAttribute('type' , 'text')
} else {
passwordInput.setAttribute('type','password')
}
}
checkbox.addEventListener('change' , showOrHide)
}
let lfc = new LoginFormConstructor(main, true)
}
function drawLog() {
main.innerHTML = ""
let h = document.createElement('h1')
h.innerHTML = 'Вход'
main.append(h)
function Password (parent , open) {
let passwordInput = document.createElement ('input')
let passwordCheckbox = document.createElement('input')
let passwordSpan = document.createElement('span')
let passwordContent = document.createElement('div')
parent.append(passwordContent)
passwordContent.append(passwordInput)
passwordContent.append(passwordCheckbox)
passwordContent.append(passwordSpan)
passwordContent.style.marginTop = "15px"
passwordContent.style.marginBottom = '20px'
passwordInput.placeholder = "Enter a password"
passwordCheckbox.type = 'checkbox'
passwordCheckbox.style.marginLeft = '10px'
passwordSpan.innerHTML = "Hide password"
passwordSpan.style.marginLeft = "10px"
passwordInput.onchange = () => {
if(typeof this.onChange === 'function'){
this.onChange(passwordInput.value)
}
}
function showOrHide() {
if (passwordCheckbox.checked) {
passwordInput.setAttribute('type' , 'password')
} else {
passwordInput.setAttribute('type','text')
}
}
passwordCheckbox.addEventListener('change' , showOrHide)
}
function LoginFormConstructor (parent , open) {
let passwordForm = document.createElement('div')
let loginForm = document.createElement('div')
let btnForm = document.createElement('div')
let loginInput = document.createElement('input')
loginInput.type = 'text'
loginInput.style.marginBottom = '10px'
loginInput.placeholder = "Enter a login"
let passwordInput = document.createElement('input')
passwordInput.type = 'password'
passwordInput.placeholder = "Enter a password"
let checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.style.marginLeft = '7px'
let btn = document.createElement('button')
btn.style.marginLeft = '130px'
btn.style.marginTop = '10px'
btn.innerHTML = 'Log in'
parent.append(loginForm)
parent.append(passwordForm)
parent.append(btnForm)
loginForm.append(loginInput)
passwordForm.append(passwordInput)
passwordForm.append(checkbox)
btnForm.append(btn)
btn.onclick = () => {
store.dispatch(actionFullLogin((loginInput.value),(passwordInput.value)))
}
function showOrHide() {
if (checkbox.checked) {
passwordInput.setAttribute('type' , 'text')
} else {
passwordInput.setAttribute('type','password')
}
}
checkbox.addEventListener('change' , showOrHide)
}
let lfc = new LoginFormConstructor(main, true)
}
const actionFullLogin = (login , password) => async dispatch => {
let result = await dispatch(actionPromise("login",log(login,password)))
if (result.data.login !== null){
dispatch(actionAuthLogin(result.data.login))
location.reload()
}
else {
alert ('Такого пользователя не существует или вы не правильно указали логин/пароль')
}
}
actionFullRegister = (login,password) => async dispatch => {
let result = await dispatch (actionRegister(login,password))
console.log(result)
if (result.errors === undefined) {
await dispatch (actionFullLogin(login,password))
location.reload()
}
else {
alert("Такой пользователь уже есть")
}
}
let pLog = document.createElement('p')
pLog.style.float = 'right'
pLog.style.fontWeight = 'bold'
pLog.style.marginTop = '20px'
pLog.style.color = '#000080'
if (localStorage.authToken) {
regBtn.hidden = true
logBtn.hidden = true
header.append(unlogBtn)
pLog.innerHTML = "Ваш логин:" + " " + store.getState().auth.payload.sub.login
header.append(pLog)
}
let newOrder = async(obj) => {
let option = Object.entries(obj)
let orderGoods = []
for (let key of option) {
let iteration = {
"count": key[1].count,
"good":{"_id":key[0]}
}
orderGoods.push(iteration)
}
let query = `mutation newOrder($order:OrderInput) {
OrderUpsert(order:$order){
_id
}
}`
let qVariables = {
"order": {
"orderGoods": orderGoods}
}
let result = await shopGQL(query,qVariables)
console.log(result)
return result
}
actionOrder = (obj) => async dispatch => {
return await dispatch (actionPromise ('order' , newOrder(obj)))
}
function drawCart () {
const cart = store.getState().cart
if (cart) {
main.innerHTML = ""
let cartState = store.getState().cart
for (key in cartState){
let good = document.createElement('div')
let goodName = document.createElement('h4')
let goodImg = document.createElement('img')
let btnPlus = document.createElement('button')
let btnMinus = document.createElement('button')
let countPriceGood = document.createElement('p')
let goodA = document.createElement('a')
let goodPrice = document.createElement('p')
let goodCount = document.createElement('p')
let btnDel = document.createElement('button')
let input = document.createElement('input')
input.placeholder = "Нужное количество товара"
input.style.marginLeft = '10px'
let btnInput = document.createElement('button')
btnInput.innerHTML = 'Подтвердить'
btnInput.style.marginLeft = '10px'
btnInput.onclick = () => {
if (input.value > 1){
store.dispatch(actionCartChange(key , +input.value,cartState[key].price))
}
}
good.style.overflow = 'hidden'
btnDel.style.float = 'right'
goodImg.style.width = '100px'
btnPlus.innerHTML = "+"
btnPlus.style.marginLeft = '10px'
btnPlus.onclick = () =>{
store.dispatch(actionCartChange(key,+(cartState[key].count) + 1,cartState[key].price))
}
btnMinus.innerHTML = "-"
btnMinus.onclick = () => {
if (cartState[key].count > 1){
store.dispatch(actionCartChange(key , +(cartState[key].count) - 1,cartState[key].price))
}
else {
let question = confirm("Вы хотите удалить товар?")
if (question){
store.dispatch(actionCartRemove(key))
countBasket.innerHTML ="Товаров в корзине:" + " " + 0
}
}
}
countPriceGood.style.fontWeight = 'bold'
countPriceGood.hidden = true
countPriceGood.style.marginTop = '20px'
countPriceGood.style.textAlign = 'center'
countPriceGood.style.float = 'right'
btnDel.innerHTML = "Удалить товар"
btnDel.onclick = () => {
let question = confirm("Вы хотите удалить товар?")
if (question){
store.dispatch(actionCartRemove(key))
}
}
goodA.append(goodImg)
goodA.href = '#/good/' + key
if (cartState[key].count > 1) {
countPriceGood.removeAttribute('hidden' , 'hidden')
goodById(key).then(res => countPriceGood.innerHTML = "Общая цена товара:" + " " + (cartState[key].count) * res.data.GoodFindOne.price + "uah")
}
good.style.width = "50%"
good.style.border = '2px solid black'
good.style.marginTop = '20px'
good.style.padding = '30px'
goodById(key).then(res=> goodName.innerHTML = res.data.GoodFindOne.name)
goodById(key).then(res=> goodImg.src = 'http://shop-roles.asmer.fs.a-level.com.ua/' + res.data.GoodFindOne.images[0].url)
goodById(key).then(res=>goodPrice.innerHTML = "Цена:" + " " + res.data.GoodFindOne.price + 'uah')
goodCount.innerHTML = "Количество товара:" + " " + cartState[key].count
goodById(key).then()
good.style.marginLeft = '100px'
good.append(goodName,goodA,goodPrice,goodCount,btnMinus,input,btnInput,btnPlus,btnDel)
good.append(countPriceGood)
var result = []
result.push(cartState[key].count)
var resId = []
resId.push(cartState[key])
let orderPrice = document.createElement("h5")
orderPrice.style.float = 'right'
main.append(good)
main.append(orderPrice)
}
if (Object.keys(cartState).length > 0){
let orderPrice = document.createElement('h5')
let res = []
for (good in cartState) {
res.push((((cartState[good].price) * (cartState[good].count))))
}
orderPrice.innerHTML = "Цена заказа:" + " " + res.reduce(function(a,b){
return a + b
}) + 'uah'
orderPrice.style.fontSize = '20px'
orderPrice.style.float = 'right'
orderPrice.style.marginRight = '500px'
let btnBuy = document.createElement('button')
btnBuy.innerHTML = 'Купить'
btnBuy.style.float = 'right'
btnBuy.style.marginRight = '500px'
btnBuy.style.marginTop = '10px'
btnBuy.onclick = () => {
if (!localStorage.authToken){
alert('Для начала авторизуйтесь')
location.href = '#/login'
}
else {
let obj = store.getState().cart
for (key in obj){
store.dispatch(actionOrder(obj))
}
store.dispatch({type: 'CART_CLEAR'})
countBasket.innerHTML ="Товаров в корзине:" + " " + 0
}
}
main.append(orderPrice)
main.append(btnBuy)
}
}
}
store.subscribe(() => {
const {1: route, 2:id} = location.hash.split('/')
if (route === 'categories'){
const catById = store.getState().promise.catById?.payload
if (catById){
main.innerText = ''
let h = document.createElement('h2')
h.style.fontSize = '30px'
h.style.marginTop = 0
h.innerHTML = catById.data.CategoryFindOne.name
h.style.textAlign = 'center'
main.append(h)
//вывести циклом товары со ссылками вида #/good/АЙДИШНИК
let goods = document.createElement('div')
goods.className = 'goods'
for (let key in catById.data.CategoryFindOne.goods) {
let box = document.createElement('div')
box.style.border = '3px solid #008B8B'
box.style.padding = '10px'
box.style.margin = '20px'
let img = document.createElement('img')
let productName = document.createElement('h3')
let a = document.createElement('a')
let price = document.createElement('p')
let description = document.createElement('p')
let btnMore = document.createElement('button')
btnMore.innerHTML = "Подробнее"
btnMore.style.backgroundColor = '#DCDCDC'
btnMore.style.display = 'block'
btnMore.style.marginLeft = 'auto'
btnMore.style.marginRight = 'auto'
btnMore.style.marginTop = '20px'
img.src = 'http://shop-roles.asmer.fs.a-level.com.ua/' + catById.data.CategoryFindOne.goods[key].images[0].url
img.style.width = '300px'
img.style.display = 'block'
img.style.marginLeft = 'auto'
img.style.marginRight = 'auto'
a.href = '#/good/' + catById.data.CategoryFindOne.goods[key]._id
productName.innerHTML = catById.data.CategoryFindOne.goods[key].name + '
'
price.innerHTML = "Цена:" + " " + catById.data.CategoryFindOne.goods[key].price + ' ' + 'uah'
price.style.fontWeight = 'bold'
a.style.textDecoration = 'none'
a.style.color = 'black'
description.innerHTML = catById.data.CategoryFindOne.goods[key].description
description.style.textAlign = 'center'
a.append(btnMore)
box.append(productName)
box.append(price)
box.append(img)
box.append(description)
box.append(a)
goods.append(box)
}
main.append(goods)
//ПРИДУМАТЬ КНОПКИ ДОБАВЛЕНИЯ В КОРЗИНУ
// main.innerHTML = `
${JSON.stringify(catById, null ,4)}` } } if (route === 'good'){ const goodById = store.getState().promise.goodById?.payload if (goodById){ //вывести в main страницу товара main.innerText = " " let source = goodById.data.GoodFindOne let product = document.createElement('div') let page = document.createElement('div') let h = document.createElement('h1') h.innerHTML = source.name h.style.textAlign = 'center' let img = document.createElement('img') img.src = 'http://shop-roles.asmer.fs.a-level.com.ua/' + source.images[0].url img.style.width = '300px' img.style.display = 'block' img.style.marginLeft = 'auto' img.style.marginRight = 'auto' let description = document.createElement('p') description.innerHTML = source.description description.style.textAlign = 'center' let price = document.createElement('p') price.innerHTML = 'Цена:' + " " + source.price + 'uah' price.textAlign = 'center' price.style.fontWeight = 'bold' let btnBuy = document.createElement('button') btnBuy.innerHTML = "Купить" btnBuy.style.backgroundColor = '#ADFF2F' btnBuy.style.display = 'block' btnBuy.style.marginLeft = 'auto' btnBuy.style.marginRight = 'auto' btnBuy.style.marginBottom = '10px' btnBuy.style.marginTop = '50px' btnBuy.style.width = '300px' btnBuy.style.fontSize = '20px' let btnAdd = document.createElement('button') btnAdd.innerHTML = "Добавить в корзину" btnAdd.style.backgroundColor = '#3CB371' btnAdd.style.display = 'block' btnAdd.style.marginLeft = 'auto' btnAdd.style.marginRight = 'auto' btnAdd.style.width = '300px' btnAdd.style.fontSize = '20px' btnAdd.onclick = () => { store.dispatch(actionCartAdd(source._id,1 , source.name , source.price)) } page.append(h) page.append(img) page.append(description) page.append(price) page.append(btnBuy) page.append(btnAdd) product.append(page) main.append(product) // console.log(actionCartAdd.count) } } if (route === 'cart') drawCart() } )