Browse Source

react HW2 done

vladislavaSim 1 year ago
parent
commit
1c8fd27a87

+ 5 - 0
JavaScriptHomeWorks/JsModule/index.js

@@ -1,4 +1,9 @@
+import React, { useState } from "react";
+import "./App.css";
 
+import thunk from "redux-thunk";
+import { createStore, combineReducers, applyMiddleware } from "redux";
+import { Provider, connect } from "react-redux";
 function createStore(reducer){
     let state       = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
     let cbs         = []                     //массив подписчиков

+ 166 - 0
ReactHWs/react1/package-lock.json

@@ -13,7 +13,10 @@
         "@testing-library/user-event": "^13.5.0",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
+        "react-redux": "^8.0.2",
         "react-scripts": "5.0.1",
+        "redux": "^4.2.0",
+        "redux-thunk": "^2.4.1",
         "web-vitals": "^2.1.4"
       }
     },
@@ -3747,6 +3750,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "dependencies": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "node_modules/@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4131,6 +4143,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "node_modules/@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -8484,6 +8501,19 @@
         "he": "bin/he"
       }
     },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/hoist-non-react-statics/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
     "node_modules/hoopy": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -13971,6 +14001,49 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "node_modules/react-redux": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz",
+      "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==",
+      "dependencies": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8 || ^17.0 || ^18.0",
+        "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react-native": ">=0.59",
+        "redux": "^4"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "react-native": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-redux/node_modules/react-is": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+    },
     "node_modules/react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -14117,6 +14190,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "node_modules/redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "peerDependencies": {
+        "redux": "^4"
+      }
+    },
     "node_modules/regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -15784,6 +15873,14 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -19299,6 +19396,15 @@
         "@types/node": "*"
       }
     },
+    "@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "requires": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -19633,6 +19739,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -22781,6 +22892,21 @@
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
     },
+    "hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "requires": {
+        "react-is": "^16.7.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
     "hoopy": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -26565,6 +26691,26 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "react-redux": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz",
+      "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==",
+      "requires": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "18.2.0",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+        }
+      }
+    },
     "react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -26678,6 +26824,20 @@
         "strip-indent": "^3.0.0"
       }
     },
+    "redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "requires": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "requires": {}
+    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -27897,6 +28057,12 @@
         "punycode": "^2.1.0"
       }
     },
+    "use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "requires": {}
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

+ 3 - 0
ReactHWs/react1/package.json

@@ -8,7 +8,10 @@
     "@testing-library/user-event": "^13.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-redux": "^8.0.2",
     "react-scripts": "5.0.1",
+    "redux": "^4.2.0",
+    "redux-thunk": "^2.4.1",
     "web-vitals": "^2.1.4"
   },
   "scripts": {

+ 319 - 10
ReactHWs/react1/src/App.js

@@ -7,18 +7,327 @@ import CategoryMenu from "./components/CategoryMenu";
 import Category from "./components/Category";
 import Footer from "./components/Footer";
 
+import thunk from "redux-thunk";
+import { createStore, combineReducers, applyMiddleware } from "redux";
+import { Provider, connect } from "react-redux";
+import {mapStateToPropsFactory} from "react-redux/es/connect/mapStateToProps";
+import Spoiler from "./components/Spoiler";
+import RangeInput from "./components/RangeInput";
+import PasswordConfirm from "./components/PasswordConfirm";
+
+function jwtDecode(token){
+    try {
+        return JSON.parse(atob(token.split('.')[1]))
+    }
+    catch(e){
+    }
+}
+
+function authReducer(state, {type, token}) {
+    if (!state) {
+        if (localStorage.authToken) {
+            token = localStorage.authToken
+            type = 'AUTH_LOGIN'
+        } else {
+            return {}
+        }
+    }
+    if (type === 'AUTH_LOGIN') {
+        let payload = jwtDecode(token)
+        if (typeof payload === 'object') {
+            localStorage.authToken = token
+            return {
+                ...state,
+                token,
+                payload
+            }
+        } else {
+            return state
+        }
+    }
+    if (type === 'AUTH_LOGOUT') {
+        localStorage.removeItem("authToken");
+        return {}
+    }
+    return state
+}
+
+const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token})
+const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
+
+
+
+function cartReducer (state={}, {type, good={}, count=1}) {
+
+    if (Object.keys(state).length === 0 && localStorage.cart) {
+        let currCart = JSON.parse(localStorage.cart)
+        if (currCart && Object.keys(currCart).length !== 0) {
+            state = currCart
+        }
+    }
+
+    const {_id} = good
+
+    const types = {
+        CART_ADD() {
+            count = +count
+            if (!count) {
+                return state
+            }
+            let newState = {
+                ...state,
+                [_id]: {good, count: (count + (state[_id]?.count || 0)) < 1 ? 1 : count + (state[_id]?.count || 0)}
+            }
+            localStorage.cart = JSON.stringify(newState)
+            return newState
+        },
+        CART_CHANGE() {
+            count = +count
+            if (!count) {
+                return state
+            }
+            let newState = {
+                ...state,
+                [_id]: {good, count: count < 0 ? 0 : count}
+            }
+            localStorage.cart = JSON.stringify(newState)
+            return newState
+        },
+        CART_REMOVE() {
+            let { [_id]: removed, ...newState }  = state
+            localStorage.cart = JSON.stringify(newState)
+            return newState
+        },
+        CART_CLEAR() {
+            localStorage.cart = JSON.stringify({})
+            return {}
+        },
+    }
+    if (type in types) {
+        return types[type]()
+    }
+    return state
+}
+
+const actionCartAdd = (good, count) => ({type: 'CART_ADD', good, count})
+const actionCartChange = (good, count) => ({type: 'CART_CHANGE', good, count})
+const actionCartRemove = (good) => ({type: 'CART_REMOVE', good})
+const actionCartClear = () => ({type: 'CART_CLEAR'})
+
+
+function promiseReducer(state={}, {type, status, payload, error, name}) {
+    if (!state) {
+        return {}
+    }
+    if (type === 'PROMISE') {
+        return {
+            ...state,
+            [name]: {
+                status: status,
+                payload : payload,
+                error: 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 actionPromise = (name, promise) => (
+    async (dispatch) => {
+        dispatch(actionPending(name))
+        try {
+            let data = await promise
+            dispatch(actionResolved(name, data))
+            return data
+        }
+        catch(error){
+            dispatch(actionRejected(name, error))
+        }
+    }
+)
+
+const getGQL = url => (
+    async (query, variables={}) => {
+        let obj = await fetch(url, {
+            method: 'POST',
+            headers: {
+                "Content-Type": "application/json",
+                ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
+            },
+            body: JSON.stringify({ query, variables })
+        })
+        let a = await obj.json()
+        if (!a.data && a.errors) {
+            throw new Error(JSON.stringify(a.errors))
+        } else {
+            return a.data[Object.keys(a.data)[0]]
+        }
+    }
+)
+
+const backURL = 'http://shop-roles.node.ed.asmer.org.ua/'
+const gql = getGQL(backURL + 'graphql');
+
+
+
+const actionOrder = () => (
+    async (dispatch, getState) => {
+        let {cart} = getState()
+
+        const orderGoods = Object.entries(cart)
+            .map(([_id, {good, count}]) => ({good: {_id}, count}))
+
+        let result = await dispatch(actionPromise('order', gql(`
+                  mutation newOrder($order:OrderInput){
+                    OrderUpsert(order:$order)
+                      { _id total}
+                  }
+          `, {order: {orderGoods}})))
+        if (result?._id) {
+            dispatch(actionCartClear())
+        }
+    })
+
+
+
+const actionLogin = (login, password) => (
+    actionPromise('login', gql(`query log($login: String, $password: String) {
+        login(login: $login, password: $password)
+    }`, {login, password}))
+)
+
+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 actionRegister = (login, password) => (
+    actionPromise('register', gql(`mutation reg($user:UserInput) {
+        UserUpsert(user:$user) {
+        _id 
+        }
+    }
+    `, {user: {login, password}})
+    )
+)
+
+const actionFullRegister = (login, password) => (
+    async (dispatch) => {
+        let registerId = await dispatch(actionRegister(login, password))
+
+        if (registerId) {
+            dispatch(actionFullLogin(login, password))
+        }
+    }
+)
+
+
+
+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}])}))
+)
+
+const actionGoodsByUser = (_id) => (
+    actionPromise('goodByUser', gql(`query oUser($query: String) {
+        OrderFind(query:$query){
+        _id orderGoods{
+                price count total good{
+                    _id name categories{
+                    name
+                    }
+                    images {
+                        url
+                    }
+                }
+            } 
+            owner {
+            _id login
+            }
+        }
+    }`,
+        {query: JSON.stringify([{___owner: _id}])}))
+)
+const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer})
+const store = createStore(combinedReducer)
+
+const CLoginForm = connect(null, {onLogin: actionLogin})(LoginForm)
+
 function App() {
   return (
-    <div className="App">
-      {/*<Header/>*/}
-      {/*<Input />*/}
-      <LoginForm/>
-        <div className="main">
-            <CategoryMenu />
-            <Category />
-        </div>
-      <Footer />
-    </div>
+   <Provider store={store}>
+       <div className="App">
+           {/*<Header/>*/}
+           {/*<Input />*/}
+           <CLoginForm/>
+
+           <Spoiler header={<h1>Заголовок!!!</h1>} open={false}>
+               Контент 1
+               <p>
+                   лорем ипсум траливали и тп.
+               </p>
+           </Spoiler>
+           <Spoiler header={<h1>Заголовок 2 !!!</h1>} open={false}>
+               <h2>Контент 2</h2>
+               <p>
+                   лорем ипсум траливали и тп.
+               </p>
+           </Spoiler>
+           <PasswordConfirm min={5}/>
+           <RangeInput max={3} min={1} />
+           <div className="main">
+               <CategoryMenu />
+               <Category />
+           </div>
+           <Footer />
+       </div>
+   </Provider>
   );
 }
 

+ 6 - 3
ReactHWs/react1/src/components/LoginForm.jsx

@@ -1,18 +1,21 @@
 import React, {useState} from "react";
 
-export const LoginForm = () => {
+export const LoginForm = ({onLogin}) => {
     const [login, setLogin] = useState('')
     const [password, setPassword] = useState('')
 
-    const onLogin = () => {
+    const checkButton = () => {
         return !(login !== '' && password.match(/^[A-Za-z]\w{5,10}$/));
     }
+    const auth = () => {
+        onLogin(login, password)
+    }
     return (
         <>
             <div className="loginBox">
                 <input type="text" value={login} onChange={(e) => setLogin(e.target.value)}/>
                 <input type="password" value={password} onChange={(e) => setPassword(e.target.value)}/>
-                <button disabled={onLogin()}>Log In</button>
+                <button disabled={checkButton()} onClick={auth}>Log In</button>
             </div>
         </>
     )

+ 34 - 0
ReactHWs/react1/src/components/PasswordConfirm.js

@@ -0,0 +1,34 @@
+import React, {useState} from 'react';
+
+const PasswordConfirm = ({min}) => {
+    const [val1, setVal1] = useState('')
+    const [val2, setVal2] = useState('')
+
+    const checkMatch = () => {
+        if(val1 === val2) {
+            if(val1.length > min && val2.length > min) {
+                return false
+            }
+          } else {
+            return true
+        }
+    }
+
+    return (
+        <div>
+            <p>enter password</p>
+            <input type='password'
+                   value={val1}
+                   onChange={(e) => setVal1(e.target.value)}
+                   onInput={checkMatch}/>
+               <p>repeat password</p>
+            <input type='password'
+                   value={val2}
+                   onChange={(e) => setVal2(e.target.value)}
+                   onInput={checkMatch}/>
+            <button disabled={checkMatch()}>click!</button>
+        </div>
+    );
+};
+
+export default PasswordConfirm;

+ 17 - 0
ReactHWs/react1/src/components/RangeInput.jsx

@@ -0,0 +1,17 @@
+import React, {useState} from 'react';
+
+const RangeInput = ({min, max}) => {
+    const [val, setVal] = useState('')
+
+    return (
+        <div>
+            <input type='number'
+                   style={{color: val < min || val > max ? 'red' : 'black'}}
+                   value={val}
+                   onChange={(e) => setVal(e.target.value)}
+            />
+        </div>
+    );
+};
+
+export default RangeInput;

+ 14 - 0
ReactHWs/react1/src/components/Spoiler.jsx

@@ -0,0 +1,14 @@
+import React, {useState} from 'react';
+
+const Spoiler = ({header="+", open, children}) => {
+    const [showed, setShowed] = useState(open)
+    console.log(showed)
+    return (
+        <div>
+            <div onClick={() => setShowed(!showed)}>{header}</div>
+            {showed ? children : null}
+        </div>
+    );
+};
+
+export default Spoiler;

+ 3 - 0
ReactHWs/react1/src/index.js

@@ -4,6 +4,9 @@ import './index.css';
 import App from './App';
 import reportWebVitals from './reportWebVitals';
 
+import thunk from "redux-thunk";
+import { createStore, combineReducers, applyMiddleware } from "redux";
+
 const root = ReactDOM.createRoot(document.getElementById('root'));
 root.render(
   <React.StrictMode>