123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- 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 combineReducers(reducers={cart: cartReducer, promise: promiseReducer, auth: authReducer}){
- return (state={}, action) => {
- let newState = {}
- for (let key in reducers) {
- let newSubState = reducers[key](state[key], action)
- if(newSubState !== state[key]) {
- newState[key] = newSubState
- }
- }
- if (Object.keys(newState).length) {
- return {...state, ...newState}
- } else {
- return state
- }
- }
- }
- //promise
- 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))
- }
- }
- const getGQL = url => {
- return function(query, variables={}) {
- return fetch(url,
- {
- method: "POST",
- headers:
- {"Content-Type": "application/json",
- ...(localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {})
- },
- body: JSON.stringify({query, variables})
- }).then(resp => resp.json())
- .then(data => {
- if ("errors" in data) {
- let error = new Error('ашипка, угадывай што не так')
- throw error
- }
- else {
- return data.data[Object.keys(variables)[0]]
- }
- })
- }
- }
- let shopGQL = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql')
- const goodById = goodId => {
- let id = `[{"_id":"${goodId}"}]`
- return shopGQL(`
- query good($id:String){
- GoodFindOne(query: $id) {
- _id name description price images {
- _id text url
- }
- categories {
- _id name
- }
- }
- }`, {GoodFindOne: '', id })
- }
- const actionGoodById = id =>
- actionPromise('goodById', goodById(id))
- const actionRootCategories = () =>
- actionPromise('rootCategories', shopGQL(`
- query cats($query:String){
- CategoryFind(query:$query){
- _id name
- }
- }
- `, {CategoryFind:'', 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
- }
- }
- }
- }`, { CategoryFindOne: '', query: JSON.stringify([{ _id }]) }))
- const actionAddOrder = (cart) => {
- let string = '['
- for (let goodId in cart) {
- string += `{good: {_id: "${goodId}"}, count: ${cart[goodId].count}},`
- }
- string = string.slice(0, string.length - 1)
- string += ']'
- store.dispatch(actionPromise('orderAdd', shopGQL(`
- mutation {
- OrderUpsert(order: {
- orderGoods: ${string}
- }) {
- _id total
- }
- }`, {OrderUpsert: ''})))
- }
- //cart
- function cartReducer(state={}, {type, good, price, count=1}) {
- if (Object.keys(state).length === 0 && localStorage.cart?.length > 10) {
- let newState = JSON.parse(localStorage.cart)
- return newState
- }
- const types = {
- CART_ADD() {
- let newState = {
- ...state,
- [good._id]: {count: (state[good._id]?.count || 0) + count, good: {id: good._id, name: good.name}, price}
- }
- localStorage.cart = JSON.stringify(newState)
- return newState
- },
- CART_REMOVE() {
- let {[good._id]:poh, ...newState} = state
- localStorage.cart = JSON.stringify(newState)
- return newState
- // let newState = {...state}
- // delete newState[good._id]
- // return newState
- },
- CART_CLEAR() {
- let newState = {}
- localStorage.cart = JSON.stringify(newState)
- return newState
- },
- CART_SET() {
- let newState = {
- ...state,
- [good._id]: {count, good: {id: good._id, name: good.name}, price}
- }
- localStorage.cart = JSON.stringify(newState)
- return newState
- }
- }
- if (type in types) {
- return types[type]()
- }
- return state
- }
- const actionCartAdd = (_id, name, price, count) => ({type: 'CART_ADD', good: {_id, name}, price, count})
- const actionCartRemove = (_id, name) => ({type: 'CART_REMOVE', good: {_id, name}})
- const actionCartSet = (_id, name, price, count) => ({type: 'CART_SET', good: {_id, name}, price, count})
- const actionCartClear = () => ({type: 'CART_CLEAR'})
- //auth
- const jwt_decode = (jwt) => {
- let payload = jwt.split('.')
- return JSON.parse(atob(payload[1]))
- }
- function authReducer(state, action={}){ //....
- if (state === undefined){
- //добавить в action token из localStorage, и проимитировать LOGIN (action.type = 'LOGIN')
- if (localStorage.authToken) {
- action.jwt = localStorage.authToken
- return {token: action.jwt, payload: jwt_decode(action.jwt)}
- }
- }
- if (action.type === 'LOGIN'){
- console.log('ЛОГИН')
- //+localStorage
- //jwt_decode
- return {token: action.jwt, payload: jwt_decode(action.jwt)}
- }
- if (action.type === 'LOGOUT'){
- console.log('ЛОГАУТ')
- //-localStorage
- //вернуть пустой объект
- return {}
- }
- if (action.type === 'LOGGING_IN'){
- return {loginPageHello: true}
- }
- return state
- }
- const actionLogin = (jwt) => ({type: 'LOGIN', jwt})
- const thunkLogin = (login, password) => {
- return (dispatch) => {
- shopGQL(`query login($login:String, $password: String) {login(login:$login, password:$password)}`, { login, password })
- .then(jwt => {
- if (jwt) {
- localStorage.authToken = jwt
- dispatch(actionLogin(jwt))
- } else {
- throw new Error('wrong')
- }
- })
- }
- }
- const actionLogout = () => ({type: 'LOGOUT'})
- const thunkLogout = () => {
- return (dispatch) => {
- localStorage.authToken = ''
- localStorage.cart = ''
- store.dispatch(actionCartClear())
- dispatch(actionLogout())
- }
- }
- const actionLoggingIn = () => ({type: 'LOGGING_IN'})
- //store
- const store = createStore(combineReducers({cart: cartReducer, promise: promiseReducer, auth: authReducer}))
- const unsubscribe1 = store.subscribe(() => console.log(store.getState()))
- store.dispatch(actionRootCategories())
- window.onhashchange = () => {
- let {1: route, 2:id} = location.hash.split('/')
- if (route === 'categories'){
- store.dispatch(actionCategoryById(id))
- }
- if (route === 'good'){
- store.dispatch(actionGoodById(id))
- }
- if (route === 'login'){
- store.dispatch(actionLoggingIn())
- }
- if (route === 'cart'){
- drawCart()
- }
- }
- window.onhashchange()
- function drawMainMenu(){
- let cats = store.getState().promise.rootCategories.payload
- if (cats){ //каждый раз дорисовываются в body
- aside.innerText = ''
- for (let {_id, name} of cats){
- let catA = document.createElement('a')
- catA.href = `#/categories/${_id}`
- catA.innerText = name
- aside.append(catA)
- }
- }
- }
- function drawHeader() {
- login.innerHTML = (store.getState().auth?.payload ? `${store.getState().auth.payload.sub.login} | <a id='logout' href="#/login">Log out</a>` : '<a href="#/login">Log in</a>')
- if (document.querySelector('#logout')) {
- logout.onclick = () => {
- store.dispatch(thunkLogout())
- }
- }
- }
- function drawCart() {
- let cart = store.getState().cart
- if (!localStorage.authToken) {
- main.innerText = 'Залогинтесь плез'
- } else if (!Object.keys(cart).length) {
- main.innerText = 'Корзина пуста'
- } else {
- main.innerText = 'Ваша корзина: '
- for (let goodId in cart) {
- let {good: {id, name}, price, count} = cart[goodId]
- let goodContainer = document.createElement('div')
- goodContainer.classList.add('good-container')
- let goodName = document.createElement('div')
- goodName.innerText = name
- let goodPrice = document.createElement('div')
- goodPrice.innerText = 'Стоимость: ' + price
- let goodCount = document.createElement('input')
- goodCount.type = 'number'
- goodCount.value = count
- goodCount.onchange = () => {
- store.dispatch(actionCartSet(id, name, price, (goodCount.value > 0 ? +goodCount.value : 1)))
- }
- let removeBtn = document.createElement('button')
- removeBtn.innerText = 'Удалить товар'
- removeBtn.onclick = () => {
- store.dispatch(actionCartRemove (id, name))
- }
- goodContainer.append(goodName, goodPrice, goodCount, removeBtn)
- main.append(goodContainer)
- }
- let price = 0
- for (let goodId in cart) {
- price += cart[goodId].price * cart[goodId].count
- }
- let totalPriceContainer = document.createElement('div')
- totalPriceContainer.innerText = 'Общая стоимость: ' + price
- main.append(totalPriceContainer)
- let setOrderBtn = document.createElement('button')
- setOrderBtn.innerText = 'Оформить заказ'
- setOrderBtn.onclick = () => {
- actionAddOrder(store.getState().cart)
- }
- main.append(setOrderBtn)
- let clearBtn = document.createElement('button')
- clearBtn.innerText = 'Очистить корзину'
- clearBtn.onclick = () => {
- store.dispatch(actionCartClear())
- }
- main.append(clearBtn)
- }
- }
- function drawOrderSuccessful() {
- if (store.getState().promise.orderAdd?.status === 'RESOLVED') {
- let order = store.getState().promise.orderAdd.payload
- main.innerText = 'Заказ оформился, всё круто'
- let orderInfo = document.createElement('div')
- orderInfo.innerText = `Номер заказа: ${order._id}. Стоимость: ${order.total}`
- main.append(orderInfo)
- }
- }
- store.subscribe(drawMainMenu)
- store.subscribe(drawHeader)
- 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 categoryName = document.createElement('div')
- categoryName.innerText = catById.name
- categoryName.style.fontSize = '25px'
- categoryName.style.fontWeight = 'bold'
- main.append(categoryName)
- for (let {_id, name, price} of catById.goods){
- let good = document.createElement('a')
- good.href = `#/good/${_id}`
- good.innerText = name
- let btn = document.createElement('button')
- btn.onclick = () => {
- if (!localStorage.authToken) {
- main.innerText = 'Залогинтесь плез'
- } else {
- store.dispatch(actionCartAdd(_id, name, price))
- }
- }
- btn.style.cursor = 'pointer'
- btn.innerText = 'купыть'
- main.append(good, btn)
- }
- }
- }
- if (route === 'good'){
- const goodById = store.getState().promise.goodById?.payload
- if (goodById){
- main.innerText = ''
- let {name, description, price, _id} = goodById
- let goodName = document.createElement('div')
- goodName.innerText = name
- goodName.style.fontSize = '35px'
- goodName.style.fontWeight = 'bold'
- goodName.style.marginBottom = '25px'
- let goodDescription = document.createElement('div')
- goodDescription.innerText = description
- goodDescription.style.marginBottom = '25px'
- let goodPrice = document.createElement('div')
- goodPrice.innerText = 'Цена: ' + price
- goodPrice.style.marginBottom = '5px'
- let btn = document.createElement('button')
- btn.onclick = () => {
- if (!localStorage.authToken) {
- main.innerText = 'Залогинтесь плез'
- } else {
- store.dispatch(actionCartAdd(_id, name, price))
- }
- }
- btn.style.cursor = 'pointer'
- btn.innerText = 'купыть'
- main.append(goodName, goodDescription, goodPrice, btn)
- }
- }
- if (route === 'login') {
- main.innerText = ''
- let inputsContainer = document.createElement('div')
- inputsContainer.id = 'inputs'
- let loginInput = document.createElement('input')
- loginInput.type = 'text'
- let loginLabel = document.createElement('span')
- loginLabel.innerText = 'Login:'
- let passwordInput = document.createElement('input')
- passwordInput.type = 'password'
- let passwordLabel = document.createElement('span')
- passwordLabel.innerText = 'Password:'
- let button = document.createElement('button')
- button.innerText = 'log in cyka'
- button.onclick = () => {
- if (loginInput.value && passwordInput.value){
- store.dispatch(thunkLogin(loginInput.value, passwordInput.value))
- }
- }
- inputsContainer.append(loginLabel, loginInput, passwordLabel, passwordInput, button)
- main.append(inputsContainer)
- if (store.getState().auth?.payload) {
- button.disabled = true
- }
- }
- if (route === 'cart') {
- drawCart()
- drawOrderSuccessful()
- }
- })
|