123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 |
- 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(state) //и запускаем подписчиков
- }
- }
-
- return {
- getState, //добавление функции getState в результирующий объект
- dispatch,
- subscribe //добавление subscribe в объект
- }
- }
- //JWT
- const jwt = (token) =>{
- try {
- let payload = JSON.parse(atob(token.split(".")[1]))
- return payload
- }
- catch(error){
- return undefined
- }
- }
- const reducers = {
- promise: promiseReducer, //допилить много имен для многих промисов
- auth: localStoredReducer(authReducer,"auth"), //часть предыдущего ДЗ
- cart: localStoredReducer(cartReducer,"cart"), //часть предыдущего ДЗ
- }
- //promiseReducer
- const totalReducer = combineReducers(reducers)
- function promiseReducer(state={}, {type,name, status, payload, error}){
- if (type === 'PROMISE'){
- //имена добавить
- return {
- ...state,
- [name] : {status, payload, error}
- }
- }
- return state
- }
- //CombineReducer
- function combineReducers(reducers){
- function totalReducer(state={}, action){
- const newTotalState = {}
- for (const [reducerName, reducer] of Object.entries(reducers)){
- const newSubState = reducer(state[reducerName], action)
- if (newSubState !== state[reducerName]){
- newTotalState[reducerName] = newSubState
- }
- }
- if (Object.keys(newTotalState).length){
- return {...state, ...newTotalState}
- }
- return state
- }
- return totalReducer
- }
- function getGql (endpoint){
-
- return async function gql(query, variables={}) {
- let headers = {
- 'Content-Type': 'application/json;charset=utf-8',
- 'Accept': 'application/json',
-
- }
- if ("authToken" in localStorage) {
- headers.Authorization = "Bearer " + localStorage.authToken
- }
- let result = await fetch(endpoint, {
-
- method: 'POST',
- headers,
- body: JSON.stringify({
- query,
- variables
- })
- }).then(res => res.json())
- if (("errors" in result) && !("data" in result)) {
- throw new Error(JSON.stringify(result.errors))
- }
- result = Object.values(result.data)[0]
- return result
- }
- }
- gql = getGql('http://shop-roles.node.ed.asmer.org.ua/graphql/')
- //authReducer
- function authReducer (state={},{type,token}){
- if(type === "AUTH_LOGIN"){
- try{
- window.localStorage.setItem("authToken",token)
- return {
- token: token,
- payload : jwt(token)
- }
- }
- catch(e){
- console.log(e)
- }
- }
- if(type === "AUTH_LOGOUT"){
- window.localStorage.removeItem("authToken")
- return {}
- }
- return state
- }
- //cartReducer
- function cartReducer (state={},{type,good,count=1}){
- if(type === 'CART_ADD'){
- return{
- ...state,
- [good._id]: {
- good,
- count : +count
- }
- }
- }
- if(type === "CART_SUB"){
- if(state[good._id].count-count <= 0){
- delete state[good._id]
- }
- else {
- return {
- ...state,
- [good._id] : {
- good,
- count: state[good._id].count - count,
- }
- }
- }
- }
- if(type === "CART_DEL"){
- delete state[good._id]
- return {
- ...state
- }
- }
- if(type==="CART_SET"){
- if(state[good._id].count <= 0){
- delete state[good.id]
- }
- else {
- return {
- ...state,
- [good._id] : {
- good,
- count
- }
- }
- }
- }
- if(type === "CART_CLEAR"){
- return {}
-
- }
- return state
- }
- //имена добавить
- const actionPromise = (name,promise) =>
- async dispatch => {
- dispatch(actionPending(name)) //сигнализируем redux, что промис начался
- try{
- const payload = await promise //ожидаем промиса
- dispatch(actionFulfilled(name,payload)) //сигнализируем redux, что промис успешно выполнен
- return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
- }
- catch (error){
- dispatch(actionRejected(name,error)) //в случае ошибки - сигнализируем redux, что промис несложился
- }
- }
- //ActionPromise
- 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})
- //ActionLogin
- const actionAuthLogin = token => ({type: 'AUTH_LOGIN', token})
- const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
- //ActionCart
- const actionCartAdd = (good, count=1) => ({type: 'CART_ADD', count, good})
- const actionCartSub = (good, count=1) => ({type: 'CART_SUB', count, good})
- const actionCartDel = (good) => ({type: 'CART_DEL', good})
- const actionCartSet = (good, count=1) => ({type: 'CART_SET', count, good})
- const actionCartClear = () => ({type: 'CART_CLEAR'})
- //LocalStoredReducer
- function localStoredReducer(originalReducer, localStorageKey){
- function wrapper(state, action){
- if(!state){
- try{
- return JSON.parse(localStorage[localStorageKey])
- }
- catch {}
- }
- let resp = originalReducer(state,action)
- localStorage[localStorageKey] = JSON.stringify(resp)
- return resp
-
- }
-
- return wrapper
- }
- const store = createStore(totalReducer)
- store.subscribe(() => console.log(store.getState()))
- //const store = createStore(localStoredReducer(cartReducer, 'cart'))
- //store = createStore(totalReducer) //не забудьте combineReducers если он у вас уже есть
- //store.subscribe(() => console.log(store.getState()))
- let queryFindRoots = `query findRoots($q:String) {
- CategoryFind(query: $q){
- _id name parent{
- _id name
- }
- }
- }`
- let variablesFindRoots = {
- q:JSON.stringify([{parent:null}])
- }
- //gql(queryFindRoots , variablesFindRoots ).then(console.log)
- const actionFindRoots = () => actionPromise("findRoots",gql(queryFindRoots, variablesFindRoots))
- store.dispatch(actionFindRoots())
- let queryCatFindOne = `query categoryFindOne ($q : String) {
- CategoryFindOne(query: $q){
- _id name parent{
- _id name
- }
- goods{
- _id name description price
- images{
- url
- }
- }
- subCategories{
- _id name
- }
- }
- }
- `
- const actionCatFindOne = (_id) => actionPromise("CatFindOne",gql(queryCatFindOne,{
- q:JSON.stringify([{ _id }])
- } ))
- let queryGoodFindOne = `query goodfindOne ($q : String) {
- GoodFindOne(query: $q){
- _id name price description
- images{
- url
- }
- }
- }`
- const actionGoodFindOne = (_id) => actionPromise("GoodFindOne",gql(queryGoodFindOne, {
- q:JSON.stringify([{ _id }])
- }))
- //Запрос на регістрацію
- let mutationRegistr = `mutation registration($login:String, $password: String) {
- UserUpsert(user : {login:$login, password: $password}){
- _id createdAt login
- }
- }`
- const actionRegistr = (login,password) => actionPromise("Registr",gql( mutationRegistr, {"login" : login , "password" : password}))
- //Зaпрос на користувача
- let queryLogin = `query login($login:String,$password:String){
- login(login:$login,password:$password)
- }`
- const actionLogin = (login,password) => actionPromise("Login",gql(queryLogin, {login,password}))
- //Історія замовлень
- let queryHistoryOrder = `query historyOrder($order : String){
- OrderFind(query: $order){
- _id createdAt total owner {
- _id
- createdAt
- login
- nick
- } orderGoods{
- _id count good {
- _id
- createdAt
- name
- description
- price
- } total
- }
-
- }
- }
- `
- const HistoryOrder = () => actionPromise("HistoryOrder",gql(queryHistoryOrder, {
- order:JSON.stringify([{}])
- }
- ))
- //Заказ
- const newOrder = (orderGoods) =>
- actionPromise("NewOrder",gql(
- `mutation NewOrder($order: OrderInput) {
- OrderUpsert(order: $order) {
- _id
- orderGoods {
- _id
- price
- count
- total
- good {
- name
- _id
- price
- images {
- url
- }
- }
- }
- }
- }`,
- { order: { orderGoods } }
- )
- )
- // Лічильник в корзині
- store.subscribe (()=>{
- let counter = 0
- for(let {count} of Object.values(store.getState().cart)){
- counter += count
- }
- cartIcon.innerHTML = counter;
- })
- //Анон чи Логін
- store.subscribe(()=>{
- loginName.innerHTML = store.getState().auth.payload?.sub?.login || "anon"
- })
- //Якщо вже отримав токен в минулий раз
- store.subscribe(()=>{
- let result = store.getState().auth?.token
- if(typeof(result)==="string"){
- afterLoginization()
- }
- })
- //URL for img
- const url = 'http://shop-roles.node.ed.asmer.org.ua/'
- store.subscribe(()=>{
- let {status,payload,error} = store.getState().promise?.findRoots
-
- if(status === "PENDING"){
- aside.innerHTML = ""
- }
- if(status === "FULFILLED"){
- aside.innerHTML = ""
- for(let {_id,name} of payload){
- let div = document.createElement('div')
- div.innerHTML = `<a href='#/category/${_id}'>${name}</a>`
- div.classList.add('asideLink')
- document.getElementById('aside').append(div)
- }
- }
- })
- //Категорії
- store.subscribe(()=>{
- let {status,payload,error} = store.getState().promise?.CatFindOne || {}
- const [,route, _id] = location.hash.split('/')
- if(route !== "category"){
- return
- }
-
- if(status === "PENDING"){
- mainCard.innerHTML = `<img src = 'https://i.pinimg.com/originals/71/3a/32/713a3272124cc57ba9e9fb7f59e9ab3b.gif'>`
- }
- if(status === "FULFILLED"){
- const {name,subCategories,goods} = payload
- mainCard.innerHTML = ""
- if(payload && route === "category"){
- mainCard.innerHTML += `<h1>${name} </h1>`
- if(payload.subCategories !== null){
- for(let {_id,name} of subCategories){
- mainCard.innerHTML += `<a href="#/category/${_id}>${name}</a>`
- console.log(name)
- }
- }
-
- for(const {_id,name,images} of goods){
- for (let img of images){
- mainCard.innerHTML += `<img src ="${url + img.url}" style="width:10%;"> `
- }
- mainCard.innerHTML += `<a href='#/good/${_id}'>${name}</a></br>`
-
- }
- }
- }
- })
- //Товар
- store.subscribe(()=>{
- let {status,payload,error} = store.getState().promise?.GoodFindOne || {}
- const [,route, _id] = location.hash.split('/')
- if(route !== "good"){
- return
- }
- if(status === "PENDING"){
- mainCard.innerHTML = `<img src = 'https://i.pinimg.com/originals/71/3a/32/713a3272124cc57ba9e9fb7f59e9ab3b.gif'>`
- }
- if(status === "FULFILLED"){
- mainCard.innerHTML = ""
- if(payload && route === "good"){
- let {_id,name,description,images,price} = payload
-
- mainCard.innerHTML = ""
- let divMain = document.createElement('div')
- divMain.id = "divMain"
-
- divMain.innerHTML += `<h1>${name} </h1>`
- divMain.innerHTML += `<h3>${description}</h3>`
- for(let img of images){
- divMain.innerHTML +=`<img src ="${url + img.url}"> `
- }
- divMain.innerHTML += `<h3>${price} $</h3>`
- divMain.innerHTML +=`<button id="buy">Buy</button>`
- mainCard.append(divMain)
-
- buy.addEventListener("click",function(){
- store.dispatch(actionCartAdd({_id, name, price: price,img:images}))
- })
- }
- }
- })
- //store = createStore(cartReducer);
- //store.dispatch(actionCartAdd({ _id: 'пиво', price: 50 }))
- //store.dispatch(actionCartAdd({ _id: 'чипсы', price: 75 }));
- //store.dispatch(actionCartAdd({ _id: 'пиво', price: 50 }, 5));
- //store.dispatch(actionCartSet({ _id: 'чипсы', price: 75 }, 2));
- //store.dispatch(actionCartSub({ _id: 'пиво', price: 50 }, 4));
- //Авторизація
- document.getElementById("Autorization").onclick = () => location.href = `#/login`;
- const actionFullLogin = (login, password) =>
- async (dispatch) => {
- const token = await dispatch(actionLogin(login, password))
- console.log(token)
- if(typeof(token) === "string"){
- dispatch(actionAuthLogin(token))
- mainCard.innerHTML = `Hello,${login}.`
- afterLoginization()
- }
- else {
- mainCard.innerHTML = `<h1>Bad attempt</h1>
- <button id="reloadAuth">Try again</button>`
- document.getElementById("reloadAuth").onclick = function(){
- location.reload()
- }
- }
- //проверьте что token - строка и отдайте его в actionAuthLogin
- }
- //Регістрація
- document.getElementById("Registration").onclick = () => location.href = `#/register`
- const actionFullRegistr = (login, password) =>
- async (dispatch) => {
- let userRegistr = await dispatch(actionRegistr(login,password))
- if(userRegistr){
- dispatch(actionFullLogin(login,password))
- }
- else {
- mainCard.innerHTML = `<h1>Bad attempt</h1>
- <button id="reloadReg">Try again</button>`
- document.getElementById("reloadReg").onclick = function(){
- location.reload()
- }
- }
-
-
-
- }
-
- //При логінізації
- function afterLoginization (){
- document.getElementById("Registration").hidden = true
- document.getElementById("Autorization").hidden = true
- document.getElementById("logOut").hidden = false
- document.getElementById("history").hidden = false
- logOut.onclick = ()=>{
- store.dispatch(actionAuthLogout())
- location.reload()
- }
- mainCard.innerHTML = `<h3>I hope you are doing great.Let's buy smth!!!</h3>`
- }
- //Історія
- document.getElementById("history").onclick = ()=> {location.href = `#/history`}
- store.subscribe(()=>{
-
- const [,route, _id] = location.hash.split('/')
- if(route !== "history"){
- return
- }
- let {status,payload,error} = store.getState().promise?.HistoryOrder || {}
- if(status === "PENDING"){
- mainCard.innerHTML = `<img src = 'https://i.pinimg.com/originals/71/3a/32/713a3272124cc57ba9e9fb7f59e9ab3b.gif'>`
- }
- if(status === "FULFILLED"){
- mainCard.innerHTML = ""
- if(payload && route === "history"){
- let i=1
- let j = 0
- // let arr = []
- // let [createdAt,total ] = payload
- //console.log(createdAt,total)
- for( let {_id,createdAt,total,orderGoods} of payload){
- let data = new Date(+createdAt).toString()
- let time = data.split(" ").slice(1,5).join(" ")
-
- // arr.push(+createdAt)
- // arr.sort((a,b)=>{
- // return b - a
- // })
-
-
-
-
- mainCard.innerHTML += `<div class = "history" style = "border: 1px solid purple">
- <h3>Order №${i} at ${time}</h3>
- <h3>Total: ${total}$</h3>
- </div>`
- i++
-
- }
-
- //console.log(arr)
- }
- }
- })
- //Створення заказу
- const actionNewOrder = () =>
- async (dispatch,getState) => {
- let {cart} = getState()
- const orderGoods = Object.entries(cart).map(([_id,{count}])=>({
- good : { _id},
- count,
- }))
- console.log(orderGoods)
- let result = await dispatch(newOrder(orderGoods))
- if(result?._id){
- dispatch(actionCartClear())
- }
- }
- //Корзина
- store.subscribe(()=>{
- document.getElementById("cartIcon").onclick = function inforOfCart(){
- location.href = `#/cart`
- mainCard.innerHTML = ``
- let storeCartInfo = store.getState().cart
-
- let h1 = document.createElement('h1')
- h1.innerText = 'Cart'
-
- let sum = 0
- for(let i=0;i<Object.keys(storeCartInfo).length;i++){
- let div = document.createElement('div')
- div.id = `${i}`
- div.className = "Cart"
- div.style = "border: 1px solid purple;"
- mainCard.append(div)
- let userOrder = document.getElementById(i)
- let nameOrder = Object.keys(storeCartInfo)[i]
- let name = storeCartInfo[nameOrder].good.name
- userOrder.innerHTML +=`<h3>${name}</h3>`
- for(const img of storeCartInfo[nameOrder].good.img){
- userOrder.innerHTML +=`<img src="${url + img.url}" alt="imageGood">`
- }
- userOrder.innerHTML +=`<h3>Cost: ${storeCartInfo[nameOrder].good.price}$</h3>`
- let buttonMinus = document.createElement("button")
- buttonMinus.innerText = "-"
- userOrder.append(buttonMinus)
- let input = document.createElement('input')
- input.type = "number"
- input.value = store.getState().cart[nameOrder].count
- userOrder.append(input)
- let buttonPlus = document.createElement("button")
- buttonPlus.innerText = "+"
- userOrder.append(buttonPlus)
- let buttonDel = document.createElement('button')
- buttonDel.innerText = "Delete"
- userOrder.append(buttonDel)
- buttonMinus.onclick = function(){
- input.value--
- store.dispatch(actionCartSub({
- _id:nameOrder,name:name,price:storeCartInfo[nameOrder].good.price,
- img:storeCartInfo[nameOrder].good.img}))
- inforOfCart()
- }
- buttonPlus.onclick = function(){
- input.value++
- store.dispatch(actionCartAdd(
- {_id:nameOrder,name:name,price:storeCartInfo[nameOrder].good.price,
- img:storeCartInfo[nameOrder].good.img},
- input.value))
- inforOfCart()
- }
- input.oninput = function(){
- store.dispatch(actionCartSet({_id:nameOrder,name:name,price:storeCartInfo[nameOrder].good.price},+input.value))
- inforOfCart()
- }
-
- buttonDel.onclick = function(){
- store.dispatch(actionCartDel({_id:nameOrder}))
- inforOfCart()
- }
- sum += storeCartInfo[nameOrder].good.price * storeCartInfo[nameOrder].count
- }
- let divResult = document.createElement("div")
- divResult.className = "cartResult"
-
- divResult.innerHTML += `Sum: ${sum} $`
- mainCard.prepend(h1,divResult)
- let btnCreatOrd = document.createElement("button")
- btnCreatOrd.id = "btnCreateOrd"
- btnCreatOrd.innerText = "Create Order"
- divResult.append(btnCreatOrd)
- btnCreatOrd.onclick = function (){
- store.dispatch(actionNewOrder())
- store.dispatch(actionCartClear())
- inforOfCart()
- }
- if(Object.entries(storeCartInfo).length === 0){
- mainCard.innerHTML += `<img src = "https://static.thenounproject.com/png/576143-200.png">`
- mainCard.innerHTML +=`<h3>The cart is clear</h3>`
-
- document.getElementById("btnCreateOrd").disabled = true
- }
-
- }
- })
- //LoginForm
-
- window.onhashchange = () => {
- const [,route, _id] = location.hash.split('/')
- const routes = {
- category() {
- store.dispatch(actionCatFindOne(_id))
-
- },
- good(){
- // тут был store.dispatch goodById
- store.dispatch(actionGoodFindOne(_id))
- console.log('good', _id)
- },
- login(){
- mainCard.innerHTML = `<form>
- <div class="auto">
- <h1>Autorization</h1>
- <input id="inputLogin" type="text" placeholder="login">
- </br>
- <input id="inputPassword" type="password" placeholder="password">
- </br>
- <button id="loginButton">Log in</button>
- </div>
- </form>`
-
-
- // let aut = new formLogPas(mainCard,true)
- document.getElementById("loginButton").onclick = function() {
- store.dispatch(actionFullLogin(inputLogin.value, inputPassword.value))
- }
- //нарисовать форму логина, которая по нажатию кнопки Login делает store.dispatch(actionFullLogin(login, password))
- },
- register(){
- mainCard.innerHTML = `<form>
- <div class="registration">
- <h1>Registration</h1>
- <input id="loginReg" type="text" placeholder="login">
- </br>
- <input id="passwordReg" type="password" placeholder="password">
- </br>
- <button id="regButton">Log in</button>
- </div>
- </form>`
- //let reg = new formLogPas(mainCard,true)
- document.getElementById("regButton").onclick =() => {
- store.dispatch(actionFullRegistr(loginReg.value, passwordReg.value))
- }
-
- //нарисовать форму регистрации, которая по нажатию кнопки Login делает store.dispatch(actionFullRegister(login, password))
-
-
- },
- history(){
- store.dispatch(HistoryOrder())
- },
- cart(){},
- }
-
- if (route in routes){
- routes[route]()
- }
- }
- const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
- window.onhashchange()
- //const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2M2QyODUyNGRhODExZTMzNWJmY2ZlNTYiLCJsb2dpbiI6InZhczI0MyIsImFjbCI6WyI2M2QyODUyNGRhODExZTMzNWJmY2ZlNTYiLCJ1c2VyIl19LCJpYXQiOjE2NzQ3NDExNDF9.dVileTOVOJF1o6CUKgASIlTW7vdqDNLM3tycKXTIQHA"
|