diff --git a/package-lock.json b/package-lock.json index aa07701..0f9322c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2347,6 +2347,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.173", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz", + "integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==", + "dev": true + }, "@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -2410,6 +2416,15 @@ "@types/react": "*" } }, + "@types/react-input-mask": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/react-input-mask/-/react-input-mask-3.0.1.tgz", + "integrity": "sha512-rGOW8t2Ac558TAAnaq5ZZDGHu1siTblBeY30ggAQ9sX89y2MX0/FFVDLd2DT85QRpYIQOrb7QzkF9RCoV2+nZg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-router": { "version": "5.1.16", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz", @@ -2432,9 +2447,9 @@ } }, "@types/react-transition-group": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.3.tgz", - "integrity": "sha512-fUx5muOWSYP8Bw2BUQ9M9RK9+W1XBK/7FLJ8PTQpnpTEkn0ccyMffyEQvan4C3h53gHdx7KE5Qrxi/LnUGQtdg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-KibDWL6nshuOJ0fu8ll7QnV/LVTo3PzQ9aCPnRUYPfX7eZohHwLIdNHj7pftanREzHNP4/nJa8oeM73uSiavMQ==", "requires": { "@types/react": "*" } @@ -3057,6 +3072,14 @@ "postcss-value-parser": "^4.1.0" } }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, "babel-jest": { "version": "27.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.0.tgz", @@ -5565,8 +5588,7 @@ "follow-redirects": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", - "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", - "dev": true + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" }, "form-data": { "version": "3.0.1", @@ -5579,6 +5601,32 @@ "mime-types": "^2.1.12" } }, + "formik": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", + "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==", + "requires": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^1.10.0" + }, + "dependencies": { + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6345,6 +6393,15 @@ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -8545,6 +8602,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -9082,6 +9144,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==", + "dev": true + }, "nanoid": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", @@ -10223,6 +10291,12 @@ "react-is": "^16.8.1" } }, + "property-expr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz", + "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==", + "dev": true + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -10355,6 +10429,21 @@ "scheduler": "^0.20.2" } }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, + "react-input-mask": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-2.0.4.tgz", + "integrity": "sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ==", + "dev": true, + "requires": { + "invariant": "^2.2.4", + "warning": "^4.0.2" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -12011,6 +12100,12 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=", + "dev": true + }, "tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -12407,6 +12502,15 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", @@ -12749,6 +12853,21 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "yup": { + "version": "0.32.9", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz", + "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.5", + "@types/lodash": "^4.14.165", + "lodash": "^4.17.20", + "lodash-es": "^4.17.15", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + } + }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/package.json b/package.json index cc14832..94b25c0 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ "dependencies": { "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", + "axios": "^0.21.4", "eventemitter3": "^4.0.7", + "formik": "^2.2.9", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^5.3.0", @@ -50,6 +52,7 @@ "@types/jest": "^26.0.24", "@types/react": "^17.0.19", "@types/react-dom": "^17.0.9", + "@types/react-input-mask": "^3.0.1", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", @@ -74,6 +77,7 @@ "postcss": "^8.3.6", "postcss-loader": "^6.1.1", "prettier": "2.3.2", + "react-input-mask": "^2.0.4", "style-loader": "^3.2.1", "stylelint": "^13.13.1", "stylelint-config-prettier": "^8.0.2", @@ -84,7 +88,8 @@ "typescript-eslint": "0.0.1-alpha.0", "webpack": "^5.52.1", "webpack-cli": "^4.8.0", - "webpack-dev-server": "^4.2.0" + "webpack-dev-server": "^4.2.0", + "yup": "^0.32.9" }, "browserslist": { "production": [ diff --git a/public/assets/mario-background.jpeg b/public/assets/mario-background.jpeg new file mode 100644 index 0000000..257c2a5 Binary files /dev/null and b/public/assets/mario-background.jpeg differ diff --git a/public/index.html b/public/index.html index 5e3c952..0ff6cf8 100644 --- a/public/index.html +++ b/public/index.html @@ -5,14 +5,8 @@ - - + + Mario Pro Max diff --git a/src/App.tsx b/src/App.tsx index ec95a96..1712dec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,28 +1,91 @@ /* * Copyright (c) 2021. Written by Leonid Artemev (me@artemev.it) */ -import { createBrowserHistory } from "history"; import React from "react"; +import { createBrowserHistory } from "history"; import { Route, Router, Switch } from "react-router-dom"; -import { Login, Registration, Leaderboard, Forum } from "./pages"; +import { Login, Registration, Leaderboard, Forum, Profile } from "./pages"; +import { createTheme, ThemeProvider } from "@material-ui/core"; + import { Game } from "./pages/Game"; +declare module "@material-ui/core/styles" { + interface Theme { + loginPage: { + background: string; + backdropFilter: string; + }; + } + // allow configuration using `createTheme` + interface ThemeOptions { + loginPage?: { + background: string; + backdropFilter: string; + }; + } +} + const history = createBrowserHistory(); const StubComponent = () =>
Under construction! 👻
; +const darkTheme = createTheme({ + typography: { + fontFamily: [ + "-apple-system", + "BlinkMacSystemFont", + '"Segoe UI"', + "Roboto", + '"Helvetica Neue"', + "Arial", + "sans-serif", + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(","), + }, + loginPage: { + background: "#00000033", + backdropFilter: "blur(10px)", + }, +}); + +const lightTheme = createTheme({ + typography: { + fontFamily: [ + "-apple-system", + "BlinkMacSystemFont", + '"Segoe UI"', + "Roboto", + '"Helvetica Neue"', + "Arial", + "sans-serif", + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(","), + }, + loginPage: { + background: "#FFFFFF88", + backdropFilter: "blur(3px)", + }, +}); + function App() { return (
- - - - - - - - - - + + + + + + + + + + + + +
); } diff --git a/src/constants/url.ts b/src/constants/url.ts new file mode 100644 index 0000000..89a1d6a --- /dev/null +++ b/src/constants/url.ts @@ -0,0 +1,5 @@ +export const API_BASE_URL = "https://ya-praktikum.tech/api/v2"; +export const SIGNIN_URL = `${API_BASE_URL}/auth/signin`; +export const SIGNUP_URL = `${API_BASE_URL}/auth/signup`; +export const SIGNOUT_URL = `${API_BASE_URL}/auth/logout`; +export const USER_URL = `${API_BASE_URL}/auth/user`; diff --git a/src/constants/validationErrors.ts b/src/constants/validationErrors.ts new file mode 100644 index 0000000..b2a288f --- /dev/null +++ b/src/constants/validationErrors.ts @@ -0,0 +1,7 @@ +export const ERROR_MESSAGES = { + required: "Required field", + min: (num: number): string => `Minimum length ${num} symbols`, + max: (num: number): string => `Maximum length ${num} symbols`, + email: "Invalid email format", + password_mismatch: "Passwords do not match", +}; diff --git a/src/constants/validationSchema.ts b/src/constants/validationSchema.ts new file mode 100644 index 0000000..b39baf7 --- /dev/null +++ b/src/constants/validationSchema.ts @@ -0,0 +1,53 @@ +import { object, ref, SchemaOf, string } from "yup"; + +import { ERROR_MESSAGES } from "./validationErrors"; + +import { User as UserModel } from "../services/auth"; + +const VALIDATION = { + login: { + min: 3, + max: 20, + }, + first_name: { + min: 3, + max: 20, + }, + second_name: { + min: 3, + max: 20, + }, + password: { + min: 6, + max: 20, + }, +}; + +export type User = Pick & { password: string }; + +export const UserSchema: SchemaOf = object().shape({ + email: string().email(ERROR_MESSAGES.email).required(ERROR_MESSAGES.required), + login: string() + .min(VALIDATION.login.min, ERROR_MESSAGES.min(VALIDATION.login.min)) + .max(VALIDATION.login.max, ERROR_MESSAGES.min(VALIDATION.login.max)) + .required(ERROR_MESSAGES.required), + first_name: string() + .min(VALIDATION.login.min, ERROR_MESSAGES.min(VALIDATION.login.min)) + .max(VALIDATION.login.max, ERROR_MESSAGES.min(VALIDATION.login.max)) + .required(ERROR_MESSAGES.required), + second_name: string() + .min(VALIDATION.login.min, ERROR_MESSAGES.min(VALIDATION.login.min)) + .max(VALIDATION.login.max, ERROR_MESSAGES.min(VALIDATION.login.max)) + .required(ERROR_MESSAGES.required), + phone: string() + .required(ERROR_MESSAGES.required) + .transform((value: string) => value.replace(/\D/g, "")) + .min(11, ERROR_MESSAGES.required), + password: string() + .min(VALIDATION.password.min, ERROR_MESSAGES.min(VALIDATION.password.min)) + .max(VALIDATION.password.max, ERROR_MESSAGES.min(VALIDATION.password.max)) + .required(ERROR_MESSAGES.required), + password_confirm: string() + .oneOf([ref("password"), undefined], ERROR_MESSAGES.password_mismatch) + .required(ERROR_MESSAGES.required), +}); diff --git a/src/index.css b/src/index.css index 79aa911..2db3492 100644 --- a/src/index.css +++ b/src/index.css @@ -4,14 +4,21 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + +@keyframes from-left-to-right { + from { + transform: translateX(-100%); + } + to { + transform: translateX(0); + } } diff --git a/src/pages/Login/Login.tsx b/src/pages/Login/Login.tsx index bb15889..38b942a 100644 --- a/src/pages/Login/Login.tsx +++ b/src/pages/Login/Login.tsx @@ -1,7 +1,129 @@ -import React from "react"; +import React, { useEffect } from "react"; +import Button from "@material-ui/core/Button"; +import CssBaseline from "@material-ui/core/CssBaseline"; +import TextField from "@material-ui/core/TextField"; +import Link from "@material-ui/core/Link"; +import Box from "@material-ui/core/Box"; +import Grid from "@material-ui/core/Grid"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/core/styles"; +import { Slide } from "@material-ui/core"; +import { getUser, signin, SigninProps } from "../../services/auth"; +import { UserSchema } from "../../constants/validationSchema"; +import { SchemaOf } from "yup"; +import { useFormik } from "formik"; +import { useHistory } from "react-router"; -function Login() { - return
; -} +import { Footer } from "../../components/Footer"; + +const useStyles = makeStyles((theme) => ({ + main: { + height: "100vh", + backgroundImage: "url(/assets/mario-background.jpeg)", + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + backgroundPosition: "left", + animationName: "from-left-to-right", + animationDuration: "4s", + }, + + formContainer: { + background: theme.loginPage.background, + backdropFilter: theme.loginPage.backdropFilter, + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + }, +})); + +export const signInSchema = UserSchema.pick(["login", "password"]) as SchemaOf; + +const initialValues: SigninProps = { + login: "", + password: "", +}; -export default Login; +export default function Login() { + const classes = useStyles(); + const history = useHistory(); + + const formik = useFormik({ + initialValues, + validationSchema: signInSchema, + onSubmit: (values) => { + signin(values).then((data) => { + history.push("/profile"); + }); + }, + }); + + useEffect(() => { + getUser().then(() => history.push("/profile")); + }, []); + + return ( + + + + + + + + Sign in + + +
+ + + + + + + + {"Don't have an account? Sign Up"} + + +
+
+