|
@@ -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} ✖ ${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() )
|