|
@@ -1,5 +1,5 @@
|
|
|
/* eslint-disable jsx-a11y/alt-text */
|
|
|
-import React, { useState } from "react";
|
|
|
+import React, { useState, useEffect } from "react";
|
|
|
import logoDefault from "./logo.svg";
|
|
|
import "./App.scss";
|
|
|
import { Provider, connect } from "react-redux";
|
|
@@ -7,6 +7,9 @@ import { createStore, combineReducers, applyMiddleware } from "redux";
|
|
|
import thunk from "redux-thunk";
|
|
|
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
|
|
|
import { Button } from "react-bootstrap";
|
|
|
+import { Router, Route, Link, Switch, withRouter } from "react-router-dom";
|
|
|
+import { createBrowserHistory } from "history";
|
|
|
+import { Navbar, NavDropdown, Container, Nav } from "react-bootstrap";
|
|
|
|
|
|
const jwtDecode = (token) => {
|
|
|
try {
|
|
@@ -55,15 +58,35 @@ const actionCatById = (_id) =>
|
|
|
"catById",
|
|
|
gql(
|
|
|
`query catById($q: String){
|
|
|
- CategoryFindOne(query: $q){
|
|
|
- subCategories{name, _id}
|
|
|
- _id name goods {
|
|
|
- _id name price images {
|
|
|
- url
|
|
|
- }
|
|
|
- }
|
|
|
+ CategoryFindOne(query: $q){
|
|
|
+ _id name,
|
|
|
+ goods{
|
|
|
+ _id name price images {
|
|
|
+ url
|
|
|
+ }
|
|
|
+ },
|
|
|
+ subCategories{
|
|
|
+ name, subCategories{
|
|
|
+ name
|
|
|
+ }
|
|
|
}
|
|
|
- }`,
|
|
|
+ }
|
|
|
+ }`,
|
|
|
+ { q: JSON.stringify([{ _id }]) }
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+const actionGoodById = (_id) =>
|
|
|
+ actionPromise(
|
|
|
+ "goodById",
|
|
|
+ gql(
|
|
|
+ `query goodById($q: String){
|
|
|
+ GoodFindOne(query: $q){
|
|
|
+ _id name description price images{
|
|
|
+ url
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }`,
|
|
|
{ q: JSON.stringify([{ _id }]) }
|
|
|
)
|
|
|
);
|
|
@@ -104,15 +127,34 @@ const actionLogin = (login, password) =>
|
|
|
)
|
|
|
);
|
|
|
|
|
|
+const actionRegister = (login, password) =>
|
|
|
+ actionPromise(
|
|
|
+ "registration",
|
|
|
+ gql(
|
|
|
+ `mutation reg2($user:UserInput) {
|
|
|
+ UserUpsert(user:$user) {
|
|
|
+ _id login
|
|
|
+ }
|
|
|
+ }
|
|
|
+ `,
|
|
|
+ { user: { login: login, password: password } }
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
const actionFullLogin = (login, password) => async (dispatch) => {
|
|
|
- // console.log(login, password);
|
|
|
let token = await dispatch(actionLogin(login, password));
|
|
|
- // console.log(token);
|
|
|
if (token) {
|
|
|
dispatch(actionAuthLogin(token));
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+const actionFullRegister = (login, password) => async (dispatch) => {
|
|
|
+ let check = await dispatch(actionRegister(login, password));
|
|
|
+ if (check) {
|
|
|
+ dispatch(actionFullLogin(login, password));
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
|
|
|
const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
|
|
|
|
|
@@ -219,196 +261,85 @@ const store = createStore(
|
|
|
|
|
|
store.subscribe(() => console.log(store.getState()));
|
|
|
store.dispatch(actionRootCats());
|
|
|
-store.dispatch(actionCatById("5dc458985df9d670df48cc47"));
|
|
|
|
|
|
const Logo = ({ logo = logoDefault }) => (
|
|
|
- <a href="https://www.google.com.ua/?hl=ru" className="Logo">
|
|
|
- <img src={logo} />
|
|
|
- </a>
|
|
|
+ <Link to="/" className="navbar-brand">
|
|
|
+ <img src={logo} width="30" height="30" alt="" />
|
|
|
+ React Site
|
|
|
+ </Link>
|
|
|
);
|
|
|
|
|
|
const Cart = ({ cart }) => {
|
|
|
let count = 0;
|
|
|
let sum = Object.entries(cart).map(([, val]) => val.count);
|
|
|
count = sum.reduce((a, b) => a + b, 0);
|
|
|
- return <div className="Cart">Товаров в корзине: {count}</div>;
|
|
|
+ return (
|
|
|
+ <Link className="nav-link" to="/cart">
|
|
|
+ Корзина: {count} товара
|
|
|
+ </Link>
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
const CCart = connect(({ cart }) => ({ cart }))(Cart);
|
|
|
|
|
|
-const Header = ({ logo = logoDefault }) => (
|
|
|
- <header>
|
|
|
- <Logo logo={logo} />
|
|
|
- <CCart />
|
|
|
- </header>
|
|
|
-);
|
|
|
-
|
|
|
-const Footer = ({ logo = logoDefault }) => (
|
|
|
- <footer>
|
|
|
- <Logo logo={logo} />
|
|
|
- </footer>
|
|
|
-);
|
|
|
-
|
|
|
-const defaultRootCats = [
|
|
|
- { _id: "5dc49f4d5df9d670df48cc64", name: "Airconditions" },
|
|
|
- { _id: "5dc458985df9d670df48cc47", name: " Smartphones" },
|
|
|
- { _id: "5dc4b2553f23b553bf354101", name: "Крупная бытовая техника" },
|
|
|
- { _id: "5dcac1b56d09c45440d14cf8", name: "Макароны" },
|
|
|
-];
|
|
|
-
|
|
|
const RootCategory = ({ cat: { _id, name } = {} }) => (
|
|
|
- <li>
|
|
|
- <a href={`#/${_id}`}>{name}</a>
|
|
|
- </li>
|
|
|
+ <NavDropdown.Item
|
|
|
+ componentclass={Link}
|
|
|
+ href={`/category/${_id}`}
|
|
|
+ to={`/category/${_id}`}
|
|
|
+ >
|
|
|
+ {name}
|
|
|
+ </NavDropdown.Item>
|
|
|
);
|
|
|
-const RootCategories = ({ cats = defaultRootCats }) => (
|
|
|
- <ul className="RootCategories">
|
|
|
- {cats.map((cat) => (
|
|
|
- <RootCategory cat={cat} />
|
|
|
+
|
|
|
+const RootCategories = ({ cats }) => (
|
|
|
+ <NavDropdown title="Категории" id="collasible-nav-dropdown">
|
|
|
+ {cats.map((cat, i) => (
|
|
|
+ <RootCategory cat={cat} key={i} />
|
|
|
))}
|
|
|
- </ul>
|
|
|
+ </NavDropdown>
|
|
|
);
|
|
|
|
|
|
const CRootCategories = connect((state) => ({
|
|
|
cats: state.promise.rootCats?.payload || [],
|
|
|
}))(RootCategories);
|
|
|
|
|
|
-const Aside = () => (
|
|
|
- <aside>
|
|
|
- <CRootCategories />
|
|
|
- </aside>
|
|
|
+const Header = () => (
|
|
|
+ <Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
|
|
|
+ <Container>
|
|
|
+ <Navbar.Brand>
|
|
|
+ <Logo />
|
|
|
+ </Navbar.Brand>
|
|
|
+ <Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
|
|
+ <Navbar.Collapse id="responsive-navbar-nav">
|
|
|
+ <Nav className="me-auto">
|
|
|
+ <CRootCategories />
|
|
|
+ </Nav>
|
|
|
+ <Nav>
|
|
|
+ <CCart />
|
|
|
+ <СAuthorizations />
|
|
|
+ </Nav>
|
|
|
+ </Navbar.Collapse>
|
|
|
+ </Container>
|
|
|
+ </Navbar>
|
|
|
+);
|
|
|
+
|
|
|
+const Footer = ({ logo = logoDefault }) => (
|
|
|
+ <footer>
|
|
|
+ <Logo logo={logo} />
|
|
|
+ </footer>
|
|
|
);
|
|
|
|
|
|
const Content = ({ children }) => <div className="Content">{children}</div>;
|
|
|
-const defaultCat = {
|
|
|
- subCategories: null,
|
|
|
- _id: "5dc458985df9d670df48cc47",
|
|
|
- name: " Smartphones",
|
|
|
- goods: [
|
|
|
- {
|
|
|
- _id: "5dc45c3d5df9d670df48cc4a",
|
|
|
- name: "AABBA",
|
|
|
- price: 1000,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/1b2b030516d2c7c5b43d5b5b5ec0e25a",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc45d0b5df9d670df48cc4b",
|
|
|
- name: "Apple iPhone X 64GB Space Gray",
|
|
|
- price: 5000,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/00505f5f08ac113874318dee67975aa9",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4617c5df9d670df48cc4c",
|
|
|
- name: "Apple iPhone XR 64Gb Black",
|
|
|
- price: 750,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/0671607428c0cb401a899a469395fe2f",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc49faf5df9d670df48cc66",
|
|
|
- name: "Xiaomi Redmi Note 7 4/64GB Neptune Bluel",
|
|
|
- price: 3150,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/a915392b85ccad7f8572f7a597a9104d",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4a06e5df9d670df48cc67",
|
|
|
- name: "Xiaomi Mi A3 4/128GB Not just Blue",
|
|
|
- price: 220,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/32ea31cc1251b10314bfce25c4c81419",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4a1535df9d670df48cc68",
|
|
|
- name: "Samsung Galaxy S10e 128GB Yellow",
|
|
|
- price: 700,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/dee2c46e1ffafe70083831e5e631b43a",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4a1e35df9d670df48cc69",
|
|
|
- name: "Samsung Galaxy A80 2019 8/128GB Black",
|
|
|
- price: 600,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/d62dc5e3446f427f59a8b5fe922e4ac6",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4a26c5df9d670df48cc6a",
|
|
|
- name: "Samsung Galaxy S10e 128GB Black",
|
|
|
- price: 700,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/298583a975a33311c3f55cd325586a77",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4a3e15df9d670df48cc6b",
|
|
|
- name: "Apple iPhone 11 Pro Max 64GB Gold",
|
|
|
- price: 1500,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/b599634ebfecf2a19d900e22434bedbd",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "5dc4a4365df9d670df48cc6c",
|
|
|
- name: "Apple iPhone XS Max 256GB Gold",
|
|
|
- price: 1300,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/63c4a052377862494e33746b375903f6",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "61b1056cc750c12ba6ba4522",
|
|
|
- name: "iPhone ",
|
|
|
- price: 1000,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/cc23c15a3ae1ac60582785ebf9b3d207",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- _id: "61b105f9c750c12ba6ba4524",
|
|
|
- name: "iPhone ",
|
|
|
- price: 1200,
|
|
|
- images: [
|
|
|
- {
|
|
|
- url: "images/50842a3af34bfa28be037aa644910d07",
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- ],
|
|
|
-};
|
|
|
|
|
|
-const SubCategories = ({ cats }) => <></>;
|
|
|
+const SubCategories = ({ cats } = {}) => {
|
|
|
+ console.log(...cats);
|
|
|
+ return (
|
|
|
+ <div className="SubCategories">
|
|
|
+ <h3>Подкатегории</h3>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
|
|
|
const GoodCard = ({ good: { _id, name, price, images } = {}, onCartAdd }) => (
|
|
|
<div className="GoodCard">
|
|
@@ -416,9 +347,11 @@ const GoodCard = ({ good: { _id, name, price, images } = {}, onCartAdd }) => (
|
|
|
{images && images[0] && images[0].url && (
|
|
|
<img src={backURL + "/" + images[0].url} />
|
|
|
)}
|
|
|
- <strong>{price}</strong>
|
|
|
+ <p>Цена: {price}</p>
|
|
|
+ <Link to={`/good/${_id}`}>Посмотреть</Link>
|
|
|
+ <br />
|
|
|
<Button
|
|
|
- variant="primary"
|
|
|
+ variant="success"
|
|
|
onClick={() => onCartAdd({ _id, name, price, images })}
|
|
|
>
|
|
|
+
|
|
@@ -428,14 +361,12 @@ const GoodCard = ({ good: { _id, name, price, images } = {}, onCartAdd }) => (
|
|
|
|
|
|
const CGoodCard = connect(null, { onCartAdd: actionCartAdd })(GoodCard);
|
|
|
|
|
|
-const Category = ({
|
|
|
- cat: { _id, name, goods, subCategories } = defaultCat,
|
|
|
-}) => (
|
|
|
+const Category = ({ cat: { _id, name, goods, subCategories } }) => (
|
|
|
<div className="Category">
|
|
|
<h1>{name}</h1>
|
|
|
{subCategories && <SubCategories cats={subCategories} />}
|
|
|
- {(goods || []).map((good) => (
|
|
|
- <CGoodCard good={good} />
|
|
|
+ {(goods || []).map((good, i) => (
|
|
|
+ <CGoodCard good={good} key={i} />
|
|
|
))}
|
|
|
</div>
|
|
|
);
|
|
@@ -468,7 +399,7 @@ const CartGood = ({
|
|
|
>
|
|
|
Delete
|
|
|
</Button>
|
|
|
- <label for="CountField">Change count</label>
|
|
|
+ <label hrmlFor="CountField">Change count</label>
|
|
|
<input
|
|
|
type="number"
|
|
|
value={count}
|
|
@@ -487,7 +418,7 @@ const CCartGood = connect(null, {
|
|
|
|
|
|
const CartPage = ({ cart, clearCart }) => (
|
|
|
<div className="CartMain">
|
|
|
- <h1>Корзина</h1>
|
|
|
+ <h1>Cart</h1>
|
|
|
|
|
|
{Object.entries(cart).map(([, cartGood]) => (
|
|
|
<CCartGood cartGood={cartGood} />
|
|
@@ -507,7 +438,7 @@ const CCartPage = connect((state) => ({ cart: state.cart }), {
|
|
|
clearCart: actionCartClear,
|
|
|
})(CartPage);
|
|
|
|
|
|
-const LoginForm = ({ onLogin }) => {
|
|
|
+const LoginForm = ({ onLogin, onRegister }) => {
|
|
|
const [login, setLogin] = useState("");
|
|
|
const [password, setPassword] = useState("");
|
|
|
return (
|
|
@@ -525,109 +456,167 @@ const LoginForm = ({ onLogin }) => {
|
|
|
placeholder="Password"
|
|
|
/>
|
|
|
{
|
|
|
- <button
|
|
|
+ <Button
|
|
|
+ variant="success"
|
|
|
disabled={login.length < 1 || password.length < 1 ? true : false}
|
|
|
onClick={() => onLogin(login, password)}
|
|
|
>
|
|
|
Login
|
|
|
- </button>
|
|
|
+ </Button>
|
|
|
+ }
|
|
|
+ {
|
|
|
+ <Button
|
|
|
+ variant="secondary"
|
|
|
+ disabled={login.length < 1 || password.length < 1 ? true : false}
|
|
|
+ onClick={() => onRegister(login, password)}
|
|
|
+ >
|
|
|
+ Register
|
|
|
+ </Button>
|
|
|
}
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-const CLoginForm = connect(null, { onLogin: actionFullLogin })(LoginForm);
|
|
|
+const CLoginForm = connect(null, {
|
|
|
+ onLogin: actionFullLogin,
|
|
|
+ onRegister: actionFullRegister,
|
|
|
+})(LoginForm);
|
|
|
+
|
|
|
+const PageMain = () => <h1>Главная страница</h1>;
|
|
|
+
|
|
|
+const PageCategory = ({
|
|
|
+ match: {
|
|
|
+ params: { _id },
|
|
|
+ },
|
|
|
+ getData,
|
|
|
+ history,
|
|
|
+}) => {
|
|
|
+ useEffect(() => {
|
|
|
+ getData(_id);
|
|
|
+ }, [_id]);
|
|
|
+ return <CCategory />;
|
|
|
+};
|
|
|
+const CPageCategory = connect(null, { getData: actionCatById })(PageCategory);
|
|
|
+
|
|
|
+const Good = ({
|
|
|
+ good: { _id, name, description, price, images } = {},
|
|
|
+ onCartAdd,
|
|
|
+}) => {
|
|
|
+ return (
|
|
|
+ <div className="good">
|
|
|
+ <h1>Страница товара</h1>
|
|
|
+ <h1>{name}</h1>
|
|
|
+ {images && images[0] && images[0].url && (
|
|
|
+ <img src={backURL + "/" + images[0].url} />
|
|
|
+ )}
|
|
|
+ <p>
|
|
|
+ <strong>Описание:</strong> {description}
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ <strong>ID:</strong> {_id}
|
|
|
+ </p>
|
|
|
+ <strong>Цена: {price} USD</strong>
|
|
|
+ <br />
|
|
|
+ <Button
|
|
|
+ variant="success"
|
|
|
+ onClick={() => onCartAdd({ _id, name, price, images })}
|
|
|
+ >
|
|
|
+ Добавить в корзину
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+const CGood = connect((state) => ({ good: state.promise.goodById?.payload }), {
|
|
|
+ onCartAdd: actionCartAdd,
|
|
|
+})(Good);
|
|
|
+
|
|
|
+const PageGood = ({
|
|
|
+ match: {
|
|
|
+ params: { _id },
|
|
|
+ },
|
|
|
+ getData,
|
|
|
+}) => {
|
|
|
+ useEffect(() => {
|
|
|
+ getData(_id);
|
|
|
+ }, [_id]);
|
|
|
+ return <CGood />;
|
|
|
+};
|
|
|
+const CPageGood = connect(null, { getData: actionGoodById })(PageGood);
|
|
|
+
|
|
|
+const Authorizations = ({ auth, actionLogOut }) => {
|
|
|
+ if (auth.payload) {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <strong>Привет, {auth.payload.sub.login}</strong>
|
|
|
+ <Button variant="danger" onClick={() => actionLogOut()}>
|
|
|
+ Выйти
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ return (
|
|
|
+ <Link className="nav-link" to={`/authorizations`}>
|
|
|
+ Войти/Зарегистрироваться
|
|
|
+ </Link>
|
|
|
+ );
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const СAuthorizations = connect((state) => ({ auth: state.auth }), {
|
|
|
+ actionLogOut: actionAuthLogout,
|
|
|
+})(Authorizations);
|
|
|
+
|
|
|
+const Page404 = () => (
|
|
|
+ <>
|
|
|
+ <h1>404 Error Page</h1>
|
|
|
+ <p className="zoom-area">Page was not found</p>
|
|
|
+ <section className="error-container">
|
|
|
+ <span className="four">
|
|
|
+ <span className="screen-reader-text">4</span>
|
|
|
+ </span>
|
|
|
+ <span className="zero">
|
|
|
+ <span className="screen-reader-text">0</span>
|
|
|
+ </span>
|
|
|
+ <span className="four">
|
|
|
+ <span className="screen-reader-text">4</span>
|
|
|
+ </span>
|
|
|
+ </section>
|
|
|
+ <div className="link-container">
|
|
|
+ <Link to="/" className="more-link">
|
|
|
+ Go to home
|
|
|
+ </Link>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+);
|
|
|
|
|
|
const Main = () => (
|
|
|
<main>
|
|
|
- <Aside />
|
|
|
<Content>
|
|
|
- <CCategory />
|
|
|
+ <Switch>
|
|
|
+ <Route path="/" component={withRouter(PageMain)} exact />
|
|
|
+ <Route path="/category/:_id" component={withRouter(CPageCategory)} />
|
|
|
+ <Route path="/cart" component={withRouter(CCartPage)} />
|
|
|
+ <Route path="/good/:_id" component={withRouter(CPageGood)} />
|
|
|
+ <Route path="/authorizations" component={withRouter(CLoginForm)} />
|
|
|
+ <Route path="" component={withRouter(Page404)} />
|
|
|
+ </Switch>
|
|
|
</Content>
|
|
|
</main>
|
|
|
);
|
|
|
|
|
|
-// const JSONTest = ({ data }) => (
|
|
|
-// <pre>
|
|
|
-// {JSON.stringify(data, null, 4)}
|
|
|
-// {Math.random() > 0.5 && <h1>Hello</h1>}
|
|
|
-// </pre>
|
|
|
-// );
|
|
|
-
|
|
|
-// const ReduxJSON = connect((state) => ({ data: state }))(JSONTest);
|
|
|
-
|
|
|
-const ListItem = ({ item }) => <li>{item}</li>;
|
|
|
-
|
|
|
-const List = ({ data = ["пиво", "чипсы", "сиги"] }) => (
|
|
|
- <ul>
|
|
|
- {data.map((item) => (
|
|
|
- <ListItem item={item} />
|
|
|
- ))}
|
|
|
- </ul>
|
|
|
-);
|
|
|
-
|
|
|
-const LowerCase = ({ children }) => <>{children.toLowerCase()}</>;
|
|
|
-
|
|
|
-const Input = () => {
|
|
|
- const [text, setText] = useState("text");
|
|
|
- // text !== "Another text" && setTimeout(() => setText("Another text"), 2000);
|
|
|
- return (
|
|
|
- <>
|
|
|
- <h1>{text}</h1>
|
|
|
- <h1>
|
|
|
- <LowerCase>{text}</LowerCase>
|
|
|
- </h1>
|
|
|
- <h1>Length: {text.length}</h1>
|
|
|
- <input value={text} onChange={(e) => setText(e.target.value)} />
|
|
|
- </>
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-const RGBInput = () => {
|
|
|
- const [red, setRed] = useState(0);
|
|
|
- const [green, setGreen] = useState(0);
|
|
|
- const [blue, setBlue] = useState(0);
|
|
|
- const color = `rgba(${red},${green},${blue},1)`;
|
|
|
- const bounds = (x) => (x < 0 ? 0 : x > 255 ? 255 : x);
|
|
|
- return (
|
|
|
- <div style={{ backgroundColor: color }}>
|
|
|
- <input
|
|
|
- type="number"
|
|
|
- min="0"
|
|
|
- max="255"
|
|
|
- value={red}
|
|
|
- onChange={(e) => setRed(bounds(+e.target.value))}
|
|
|
- />
|
|
|
- <input
|
|
|
- type="number"
|
|
|
- min="0"
|
|
|
- max="255"
|
|
|
- value={green}
|
|
|
- onChange={(e) => setGreen(bounds(+e.target.value))}
|
|
|
- />
|
|
|
- <input
|
|
|
- type="number"
|
|
|
- min="0"
|
|
|
- max="255"
|
|
|
- value={blue}
|
|
|
- onChange={(e) => setBlue(bounds(+e.target.value))}
|
|
|
- />
|
|
|
- </div>
|
|
|
- );
|
|
|
-};
|
|
|
+const history = createBrowserHistory();
|
|
|
|
|
|
function App() {
|
|
|
return (
|
|
|
- <Provider store={store}>
|
|
|
- <div className="App">
|
|
|
- <CLoginForm />
|
|
|
- <RGBInput />
|
|
|
- <Header />
|
|
|
- <Main />
|
|
|
- <CCartPage />
|
|
|
- <Footer />
|
|
|
- </div>
|
|
|
- </Provider>
|
|
|
+ <Router history={history}>
|
|
|
+ <Provider store={store}>
|
|
|
+ <div className="App">
|
|
|
+ <Header />
|
|
|
+ <Main />
|
|
|
+ <Footer />
|
|
|
+ </div>
|
|
|
+ </Provider>
|
|
|
+ </Router>
|
|
|
);
|
|
|
}
|
|
|
|