This commit is contained in:
Nastya 2023-08-07 15:57:44 +00:00
parent 1df8502813
commit 24399db30b
24 changed files with 1670 additions and 788 deletions

11
cypress.config.ts Normal file

@ -0,0 +1,11 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
viewportWidth: 1200,
viewportHeight: 800,
fixturesFolder: "tests/e2e/fixtures",
supportFile: false,
defaultCommandTimeout: 100,
},
});

125
cypress/e2e/access.cy.ts Normal file

@ -0,0 +1,125 @@
describe("Форма Входа", () => {
beforeEach(() => {
cy.visit("http://localhost:3000");
cy.wait(1000);
cy.contains("Личный кабинет").click();
});
it("должна успешно входить с правильными учетными данными", () => {
const login = "valid_user@example.com";
const password = "valid_password";
cy.get("#login").type(login);
cy.get("#password").type(password);
cy.get('button[type="submit"]').click();
cy.wait(2000);
cy.url().should("include", "http://localhost:3000/tariffs");
});
it("должна отображать два сообщение об ошибке при отсутствии полей", () => {
cy.get('button[type="submit"]').click();
cy.wait(2000);
cy.get("#password-helper-text").should("contain", "Поле обязательно");
cy.get("#login-helper-text").should("contain", "Поле обязательно");
});
it("должна отображать сообщение об ошибке при отсутствии пароля", () => {
cy.get("#login").type("valid_email@example.com");
cy.get('button[type="submit"]').click();
cy.get("#password-helper-text").should("contain", "Поле обязательно");
});
it("должна отображать сообщение об ошибке при отсутствии Логина", () => {
cy.get("#password").type("valid_password");
cy.get('button[type="submit"]').click();
cy.get("#login-helper-text").should("contain", "Поле обязательно");
});
});
describe("Форма регистрации", () => {
beforeEach(() => {
cy.visit("http://localhost:3000");
cy.wait(1000);
cy.contains("Личный кабинет").click();
cy.contains("Регистрация").click();
});
it("должна регистрировать нового пользователя с правильными данными", () => {
const login = Cypress._.random(1000) + "@example.com";
const password = "valid_password";
cy.get("#login").type(login);
cy.get("#password").type(password);
cy.get("#repeatPassword").type(password);
cy.get('button[type="submit"]').click();
cy.wait(5000);
cy.url().should("include", "http://localhost:3000/tariffs");
});
it("должна отображать ошибку при отсутсвии логина", () => {
cy.get("#password").type("valid_password");
cy.get("#repeatPassword").type("valid_password");
cy.get('button[type="submit"]').click();
cy.get("#login-helper-text").should("contain", "Поле обязательно");
});
it("должна отображать ошибку при отсутствии пароля", () => {
cy.get("#login").type("valid_login");
cy.get('button[type="submit"]').click();
cy.get("#password-helper-text").should("contain", "Поле обязательно");
});
it("должна отображать ошибку при отсутствии поля Повторения пароля", () => {
cy.get("#login").type("valid_login");
cy.get("#password").type("valid_password");
cy.get('button[type="submit"]').click();
cy.get("#repeatPassword-helper-text").should("contain", "Повторите пароль");
});
it("должна отображать ошибку при некоректном пароле", () => {
cy.get("#login").type("valid_log");
cy.get("#password").type("valid@12_-_@@password");
cy.get('button[type="submit"]').click();
cy.get("#password-helper-text").should("contain", "Некорректные символы");
cy.get("#repeatPassword-helper-text").should("contain", "Повторите пароль");
});
it("должна отображать ошибку при несовпадении паролей", () => {
cy.get("#login").type("valid_login");
cy.get("#password").type("valid_password");
cy.get("#repeatPassword").type("invalidPassword");
cy.get('button[type="submit"]').click();
cy.get("#repeatPassword-helper-text").should("contain", "Пароли не совпадают");
});
it("попытка отправки запроса при уже зарегистрированном пользователе", () => {
const login = "valid_user@example.com";
const password = "valid_password";
cy.get("#login").type(login);
cy.get("#password").type(password);
cy.get("#repeatPassword").type(password);
cy.get('button[type="submit"]').click();
cy.wait(5000);
cy.contains("user with this login is exist");
});
});

@ -1,70 +1,73 @@
{
"name": "hub_frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
"test:cart": "craco test src/utils/calcCart --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
"eject": "craco eject"
},
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@frontend/kitui": "^1.0.17",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@popperjs/core": "^2.11.8",
"axios": "^1.4.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"formik": "^2.2.9",
"immer": "^10.0.2",
"isomorphic-fetch": "^3.0.0",
"notistack": "^3.0.1",
"pdfjs-dist": "3.6.172",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-pdf": "^7.1.2",
"react-router-dom": "^6.4.3",
"react-slick": "^0.29.0",
"slick-carousel": "^1.8.1",
"web-vitals": "^2.1.0",
"yup": "^1.1.1",
"zustand": "^4.3.8"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-slick": "^0.23.10",
"craco-alias": "^3.0.1",
"jest": "^29.5.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.3"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"name": "hub_frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
"test:cart": "craco test src/utils/calcCart --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
"eject": "craco eject",
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
"cypress": "cypress open"
},
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@frontend/kitui": "^1.0.17",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@popperjs/core": "^2.11.8",
"axios": "^1.4.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"cypress": "^12.17.3",
"formik": "^2.2.9",
"immer": "^10.0.2",
"isomorphic-fetch": "^3.0.0",
"notistack": "^3.0.1",
"pdfjs-dist": "3.6.172",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-pdf": "^7.1.2",
"react-router-dom": "^6.4.3",
"react-slick": "^0.29.0",
"slick-carousel": "^1.8.1",
"web-vitals": "^2.1.0",
"yup": "^1.1.1",
"zustand": "^4.3.8"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-slick": "^0.23.10",
"craco-alias": "^3.0.1",
"jest": "^29.5.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.3"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

@ -51,9 +51,7 @@ export default function Drawers() {
(state) => state.summaryPriceAfterDiscountsMap
);
const userAccount = useUserStore((state) => state.userAccount);
const { tickets, ticketCount, apiPage, ticketsPerPage } = useTicketStore(
(state) => state
);
const { tickets, apiPage, ticketsPerPage } = useTicketStore((state) => state);
useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
@ -75,6 +73,10 @@ export default function Drawers() {
0
);
const notificationsCount = tickets.filter(
({ user, top_message }) => user !== top_message.user_id
).length;
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts + basePrice;
const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice;
@ -119,10 +121,10 @@ export default function Drawers() {
}}
>
<Badge
badgeContent={ticketCount}
badgeContent={notificationsCount}
sx={{
"& .MuiBadge-badge": {
display: ticketCount ? "flex" : "none",
display: notificationsCount ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.8) translate(50%, -50%)",
@ -136,12 +138,13 @@ export default function Drawers() {
</Badge>
</IconButton>
<NotificationsModal
open={openNotificationsModal}
open={notificationsCount ? openNotificationsModal : false}
setOpen={setOpenNotificationsModal}
anchorElement={bellRef.current}
notifications={tickets.map((ticket) => ({
text: "У вас новое сообщение от техподдержки",
date: new Date(ticket.updated_at).toLocaleDateString(),
url: `/support/${ticket.id}`,
watched: ticket.user === ticket.top_message.user_id,
}))}
/>

@ -12,6 +12,7 @@ import {
import { useUserStore } from "@root/stores/user";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { cardShadow } from "@root/utils/themes/shadow";
import CustomAvatar from "./Avatar";
@ -45,6 +46,7 @@ export default function DialogMenu({ handleClose }: DialogMenuProps) {
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1);
const theme = useTheme();
const location = useLocation();
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
@ -118,12 +120,13 @@ export default function DialogMenu({ handleClose }: DialogMenuProps) {
sx={{
backgroundColor: theme.palette.background.paper,
width: "100%",
boxShadow: !isTablet ? cardShadow : null,
}}
>
{index === activeSubMenuIndex &&
subMenu.map(({ name, url }) => (
<Link
key={url}
key={name + url}
style={{
paddingLeft: "30px",
display: "block",

@ -1,4 +1,4 @@
import { useMediaQuery, useTheme } from "@mui/material";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import NavbarCollapsed from "./NavbarCollapsed";
import NavbarFull from "./NavbarFull";
@ -14,12 +14,17 @@ export default function Navbar({ isLoggedIn, children }: Props) {
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return (
<>
<Box
sx={{
paddingTop: upMd ? "80px" : 0,
width: "100%",
}}
>
{upMd ? (
<NavbarFull isLoggedIn={isLoggedIn}>{children}</NavbarFull>
) : (
<NavbarCollapsed isLoggedIn={isLoggedIn}>{children}</NavbarCollapsed>
)}
</>
</Box>
);
}

@ -34,9 +34,7 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
useState<boolean>(false);
const bellRef = useRef<HTMLButtonElement | null>(null);
const userAccount = useUserStore((state) => state.userAccount);
const { ticketCount, tickets, apiPage, ticketsPerPage } = useTicketStore(
(state) => state
);
const { tickets, apiPage, ticketsPerPage } = useTicketStore((state) => state);
useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
@ -55,6 +53,10 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
setOpen(false);
};
const notificationsCount = tickets.filter(
({ user, top_message }) => user !== top_message.user_id
).length;
useEffect(() => {
if (open) {
document.body.style.overflow = "hidden";
@ -71,36 +73,80 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
maxWidth="lg"
outerContainerSx={{
backgroundColor: theme.palette.navbarbg.main,
position: "sticky",
top: 0,
}}
sx={{ height: "51px", padding: "0" }}
>
<Box
sx={{
display: "flex",
columnGap: "10px",
alignItems: "center",
height: "100%",
padding: "0 18px",
}}
>
<IconButton
onClick={() => setOpen((isOpened) => !isOpened)}
<Box sx={{ height: "100%" }}>
<Box
sx={{
p: 0,
width: "30px",
color: theme.palette.primary.main,
zIndex: 2,
position: "fixed",
top: 0,
left: 0,
width: "100%",
display: "flex",
columnGap: "10px",
alignItems: "center",
height: "51px",
padding: "0 18px",
background: "#FFFFFF",
}}
>
{open ? (
<CloseIcon />
) : (
<MenuIcon sx={{ height: "30px", width: "30px" }} />
)}
</IconButton>
<Link to="/cart">
<IconButton
onClick={() => setOpen((isOpened) => !isOpened)}
sx={{
p: 0,
width: "30px",
color: theme.palette.primary.main,
}}
>
{open ? (
<CloseIcon />
) : (
<MenuIcon sx={{ height: "30px", width: "30px" }} />
)}
</IconButton>
<Link to="/cart">
<IconButton
aria-label="cart"
sx={{
width: "30px",
height: "30px",
background: theme.palette.background.default,
borderRadius: "6px",
"&:hover": {
background: theme.palette.brightPurple.main,
"& .MuiBadge-badge": {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:nth-child(1)": { fill: "#FFFFFF" },
"& svg > path:nth-child(2)": { fill: "#FFFFFF" },
"& svg > path:nth-child(3)": { stroke: "#FFFFFF" },
},
}}
>
<Badge
badgeContent={userAccount?.cart.length}
sx={{
"& .MuiBadge-badge": {
display: userAccount?.cart.length ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.7) translate(50%, -50%)",
top: "2px",
right: "3px",
fontWeight: 400,
},
}}
>
<CartIcon />
</Badge>
</IconButton>
</Link>
<IconButton
ref={bellRef}
onClick={() => setOpenNotificationsModal((isOpened) => !isOpened)}
aria-label="cart"
sx={{
width: "30px",
@ -113,80 +159,43 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:nth-child(1)": { fill: "#FFFFFF" },
"& svg > path:nth-child(2)": { fill: "#FFFFFF" },
"& svg > path:nth-child(3)": { stroke: "#FFFFFF" },
"& svg > path:first-child": { fill: "#FFFFFF" },
"& svg > path:last-child": { stroke: "#FFFFFF" },
},
}}
>
<Badge
badgeContent={userAccount?.cart.length}
badgeContent={notificationsCount}
sx={{
"& .MuiBadge-badge": {
display: userAccount?.cart.length ? "flex" : "none",
display: notificationsCount ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.7) translate(50%, -50%)",
top: "2px",
top: "3px",
right: "3px",
fontWeight: 400,
},
}}
>
<CartIcon />
<BellIcon />
</Badge>
</IconButton>
</Link>
<IconButton
ref={bellRef}
onClick={() => setOpenNotificationsModal((isOpened) => !isOpened)}
aria-label="cart"
sx={{
width: "30px",
height: "30px",
background: theme.palette.background.default,
borderRadius: "6px",
"&:hover": {
background: theme.palette.brightPurple.main,
"& .MuiBadge-badge": {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:first-child": { fill: "#FFFFFF" },
"& svg > path:last-child": { stroke: "#FFFFFF" },
},
}}
>
<Badge
badgeContent={ticketCount}
sx={{
"& .MuiBadge-badge": {
display: ticketCount ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.7) translate(50%, -50%)",
top: "3px",
right: "3px",
fontWeight: 400,
},
}}
>
<BellIcon />
</Badge>
</IconButton>
<NotificationsModal
open={openNotificationsModal}
setOpen={setOpenNotificationsModal}
anchorElement={bellRef.current}
notifications={tickets.map((ticket) => ({
text: "У вас новое сообщение от техподдержки",
date: new Date(ticket.updated_at).toLocaleDateString(),
watched: ticket.user === ticket.top_message.user_id,
}))}
/>
<Link to="/" style={{ marginLeft: "auto" }}>
<PenaLogo width={100} />
</Link>
<NotificationsModal
open={notificationsCount ? openNotificationsModal : false}
setOpen={setOpenNotificationsModal}
anchorElement={bellRef.current}
notifications={tickets.map((ticket) => ({
text: "У вас новое сообщение от техподдержки",
date: new Date(ticket.updated_at).toLocaleDateString(),
url: `/support/${ticket.id}`,
watched: ticket.user === ticket.top_message.user_id,
}))}
/>
<Link to="/" style={{ marginLeft: "auto" }}>
<PenaLogo width={100} />
</Link>
</Box>
</Box>
<Box sx={{ display: "flex", overflow: open ? "hidden" : "unset" }}>
<Drawer
@ -195,10 +204,11 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
position: "relative",
zIndex: open ? "none" : "-1",
"& .MuiDrawer-paper": {
position: "absolute",
position: "fixed",
top: "0",
width: 210,
height: "100%",
marginTop: "51px",
},
}}
variant="persistent"

@ -58,6 +58,8 @@ export default function NavbarFull({ isLoggedIn, children }: Props) {
disableGutters
maxWidth={false}
sx={{
position: "fixed",
top: "0",
px: "16px",
display: "flex",
height: "80px",

@ -3,209 +3,215 @@ import { TransitionProps } from "@mui/material/transitions";
import logotip from "../../assets/Icons/logoPenaHab.svg";
import logotipBlack from "../../assets/Icons/black_logo_PenaHab.svg";
import CustomAvatar from "./Avatar";
import CloseIcon from "../icons/CloseIcons";
import React from "react";
import {
AppBar,
Box,
Button,
Dialog,
IconButton,
List,
ListItem,
Slide,
Toolbar,
Typography,
useMediaQuery,
useTheme,
Box,
Button,
Dialog,
List,
ListItem,
Slide,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Link, useLocation } from "react-router-dom";
import { useUserStore } from "@root/stores/user";
import { currencyFormatter } from "@root/utils/currencyFormatter";
const arrayMenu = [
{ name: "Главная", url: "/" },
{ name: "Тарифы", url: "/tariffs" },
{ name: "Тарифы на время", url: "/tariffs/time" },
{ name: "Тарифы на объём", url: "/tariffs/volume" },
{ name: "Кастомный тариф", url: "/tariffconstructor" },
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "История", url: "/history" },
{ name: "Оплата", url: "/payment" },
{ name: "Поддержка", url: "/support" },
{ name: "Оплата", url: "/wallet" },
{ name: "Корзина", url: "/cart" },
{ name: "Главная", url: "/" },
{ name: "Тарифы", url: "/tariffs" },
{ name: "Тарифы на время", url: "/tariffs/time" },
{ name: "Тарифы на объём", url: "/tariffs/volume" },
{ name: "Кастомный тариф", url: "/tariffconstructor" },
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "История", url: "/history" },
{ name: "Оплата", url: "/payment" },
{ name: "Поддержка", url: "/support" },
{ name: "Оплата", url: "/wallet" },
{ name: "Корзина", url: "/cart" },
];
const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement;
},
props: TransitionProps & {
children: React.ReactElement;
},
ref: React.Ref<null>
ref: React.Ref<null>
) {
return <Slide direction={"left"} ref={ref} {...props} />;
return <Slide direction={"left"} ref={ref} {...props} />;
});
interface DialogMenuProps {
open: boolean;
handleClose: () => void;
open: boolean;
handleClose: () => void;
}
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
const theme = useTheme();
const location = useLocation();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const user = useUserStore((state) => state.user);
const cash = useUserStore(state => state.userAccount?.wallet.cash) ?? 0;
const theme = useTheme();
const location = useLocation();
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
return (
<Dialog
fullScreen
sx={{ width: isMobile ? "100%" : "320px", ml: "auto", height: "100%" }}
open={open}
onClose={handleClose}
TransitionComponent={Transition}
return (
<Dialog
fullScreen
sx={{
width: isTablet ? "100%" : "320px",
ml: "auto",
mt: "50px",
height: "100%",
".MuiBackdrop-root.MuiModal-backdrop": {
background: "transparent",
},
".MuiPaper-root.MuiPaper-rounded": {
background: "#333647",
},
}}
open={open}
onClose={handleClose}
TransitionComponent={Transition}
>
<List
sx={{
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
height: "100vh",
p: "0",
paddingTop: "20px",
}}
>
<ListItem
sx={{
pl: "40px",
flexDirection: "column",
alignItems: isTablet ? "start" : "end",
}}
>
<AppBar
sx={{
position: "relative",
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
boxShadow: "none",
height: isMobile ? "66px" : "100px",
}}
{arrayMenu.map(({ name, url }, index) => (
<Button
key={index}
component={Link}
to={url}
onClick={handleClose}
state={user ? undefined : { backgroundLocation: location }}
disableRipple
variant="text"
sx={{
fontWeight: "500",
color:
location.pathname === url
? "#7E2AEA"
: location.pathname === "/"
? "white"
: "black",
height: "20px",
textTransform: "none",
marginBottom: "25px",
fontSize: "16px",
"&:hover": {
background: "none",
color: "#7E2AEA",
},
}}
>
<Toolbar
sx={{
display: "flex",
justifyContent: "space-between",
svg: { color: "#000000" },
}}
{name}
</Button>
))}
</ListItem>
{isTablet ? (
location.pathname === "/" ? (
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
width: "188px",
color: "white",
border: "1px solid white",
ml: "40px",
mt: "35px",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
Личный кабинет
</Button>
) : (
<Box
sx={{
width: "100%",
height: "72px",
background: "#F2F3F7",
display: "flex",
alignItems: "center",
position: "absolute",
bottom: "0",
}}
>
<CustomAvatar />
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
{isMobile && (
<Box sx={{ mt: "6px" }}>
<img src={location.pathname === "/" ? logotip : logotipBlack} alt="icon" />
</Box>
)}
<IconButton sx={{ ml: "auto" }} edge="start" color="inherit" onClick={handleClose} aria-label="close">
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
<List sx={{ background: location.pathname === "/" ? "#333647" : "#FFFFFF", height: "100vh", p: "0" }}>
<ListItem sx={{ pl: "40px", flexDirection: "column", alignItems: isMobile ? "start" : "end" }}>
{arrayMenu.map(({ name, url }, index) => (
<Button
key={index}
component={Link}
to={url}
onClick={handleClose}
state={user ? undefined : { backgroundLocation: location }}
disableRipple
variant="text"
sx={{
fontWeight: "500",
color: location.pathname === url ? "#7E2AEA" : location.pathname === "/" ? "white" : "black",
height: "20px",
textTransform: "none",
marginBottom: "25px",
fontSize: "16px",
"&:hover": {
background: "none",
color: "#7E2AEA",
},
}}
>
{name}
</Button>
))}
</ListItem>
{isMobile ? (
location.pathname === "/" ? (
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
width: "188px",
color: "white",
border: "1px solid white",
ml: "40px",
mt: "35px",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
Личный кабинет
</Button>
) : (
<Box
sx={{
width: "100%",
height: "72px",
background: "#F2F3F7",
display: "flex",
alignItems: "center",
position: "absolute",
bottom: "0",
}}
>
<CustomAvatar />
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
</Box>
)
) : (
<>
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
width: "188px",
color: "white",
border: "1px solid white",
margin: "35px 0 0 126px",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
Личный кабинет
</Button>
<Box
sx={{
position: "absolute",
right: "40px",
bottom: "60px",
}}
>
<img src={location.pathname === "/" ? logotip : logotipBlack} alt="icon" />
</Box>
</>
)}
</List>
</Dialog>
);
Мой баланс
</Typography>
<Typography
variant="body2"
color={theme.palette.brightPurple.main}
>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
</Box>
)
) : (
<>
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
width: "188px",
color: "white",
border: "1px solid white",
margin: "35px 0 0 126px",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
Личный кабинет
</Button>
<Box
sx={{
position: "absolute",
right: "40px",
bottom: "60px",
}}
>
<img
src={location.pathname === "/" ? logotip : logotipBlack}
alt="icon"
/>
</Box>
</>
)}
</List>
</Dialog>
);
}

@ -1,12 +1,12 @@
import { useState } from "react";
import { IconButton, useTheme } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { Link } from "react-router-dom";
import SectionWrapper from "../SectionWrapper";
import PenaLogo from "../PenaLogo";
import DialogMenu from "./DialogMenu";
import { Link } from "react-router-dom";
interface Props {
isLoggedIn: boolean;
@ -30,11 +30,10 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
component="nav"
maxWidth="lg"
outerContainerSx={{
position: "fixed",
top: "0",
backgroundColor: theme.palette.navbarbg.main,
position: "sticky",
top: 0,
zIndex: 1,
// borderBottom: "1px solid #E3E3E3",
borderBottom: "1px solid #E3E3E3",
}}
sx={{
height: "51px",
@ -44,9 +43,14 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
alignItems: "center",
}}
>
<Link to="/"><PenaLogo width={100} /></Link>
<Link to="/">
<PenaLogo width={100} />
</Link>
<IconButton onClick={handleClickOpen} sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}>
<IconButton
onClick={handleClickOpen}
sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}
>
<MenuIcon sx={{ height: "30px", width: "30px" }} />
</IconButton>
<DialogMenu open={open} handleClose={handleClose} />

@ -1,5 +1,12 @@
import { Link, useLocation, useNavigate } from "react-router-dom";
import { Box, Button, Container, IconButton, Typography, useTheme } from "@mui/material";
import {
Box,
Button,
Container,
IconButton,
Typography,
useTheme,
} from "@mui/material";
import SectionWrapper from "../SectionWrapper";
import LogoutIcon from "../icons/LogoutIcon";
import WalletIcon from "../icons/WalletIcon";
@ -15,119 +22,129 @@ import { clearCustomTariffs } from "@root/stores/customTariffs";
import { currencyFormatter } from "@root/utils/currencyFormatter";
interface Props {
isLoggedIn: boolean;
isLoggedIn: boolean;
}
export default function NavbarFull({ isLoggedIn }: Props) {
const theme = useTheme();
const location = useLocation();
const navigate = useNavigate();
const user = useUserStore((state) => state.user);
const cash = useUserStore(state => state.userAccount?.wallet.cash) ?? 0;
const theme = useTheme();
const location = useLocation();
const navigate = useNavigate();
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
async function handleLogoutClick() {
try {
await logout();
clearAuthToken();
clearUserData();
clearCustomTariffs();
navigate("/");
} catch (error: any) {
const message = getMessageFromFetchError(error, "Не удалось выйти");
if (message) enqueueSnackbar(message);
}
async function handleLogoutClick() {
try {
await logout();
clearAuthToken();
clearUserData();
clearCustomTariffs();
navigate("/");
} catch (error: any) {
const message = getMessageFromFetchError(error, "Не удалось выйти");
if (message) enqueueSnackbar(message);
}
}
return isLoggedIn ? (
<Container
component="nav"
disableGutters
maxWidth={false}
sx={{
px: "16px",
display: "flex",
height: "80px",
alignItems: "center",
gap: "60px",
bgcolor: "white",
borderBottom: "1px solid #E3E3E3",
}}
return isLoggedIn ? (
<Container
component="nav"
disableGutters
maxWidth={false}
sx={{
px: "16px",
display: "flex",
height: "80px",
alignItems: "center",
gap: "60px",
bgcolor: "white",
borderBottom: "1px solid #E3E3E3",
}}
>
<Link to="/">
<PenaLogo width={124} />
</Link>
<Menu />
<Box
sx={{
display: "flex",
ml: "auto",
}}
>
<Drawers />
<IconButton
sx={{ p: 0, ml: "8px" }}
onClick={() => navigate("/wallet")}
>
<Link to="/"><PenaLogo width={124} /></Link>
<Menu />
<Box
sx={{
display: "flex",
ml: "auto",
}}
>
<Drawers />
<IconButton
sx={{ p: 0, ml: "8px" }}
onClick={() => navigate("/wallet")}
>
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
</IconButton>
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
<CustomAvatar />
<IconButton
onClick={handleLogoutClick}
sx={{ ml: "20px", bgcolor: "#F2F3F7", borderRadius: "6px", height: "36px", width: "36px" }}
>
<LogoutIcon />
</IconButton>
</Box>
</Container>
) : (
<>
<SectionWrapper
component="nav"
maxWidth="lg"
outerContainerSx={{
backgroundColor: theme.palette.lightPurple.main,
borderBottom: "1px solid #E3E3E3",
}}
sx={{
px: "20px",
display: "flex",
justifyContent: "space-between",
height: "80px",
alignItems: "center",
gap: "50px",
}}
>
<PenaLogo width={150} />
<Menu />
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
px: "18px",
py: "10px",
borderColor: "white",
borderRadius: "8px",
whiteSpace: "nowrap",
minWidth: "180px",
}}
>
Личный кабинет
</Button>
</SectionWrapper>
</>
);
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
</IconButton>
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
<CustomAvatar />
<IconButton
onClick={handleLogoutClick}
sx={{
ml: "20px",
bgcolor: "#F2F3F7",
borderRadius: "6px",
height: "36px",
width: "36px",
}}
>
<LogoutIcon />
</IconButton>
</Box>
</Container>
) : (
<>
<SectionWrapper
component="nav"
maxWidth="lg"
outerContainerSx={{
position: "fixed",
top: "0",
backgroundColor: theme.palette.lightPurple.main,
borderBottom: "1px solid #E3E3E3",
}}
sx={{
px: "20px",
display: "flex",
justifyContent: "space-between",
height: "80px",
alignItems: "center",
gap: "50px",
}}
>
<PenaLogo width={150} />
<Menu />
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
px: "18px",
py: "10px",
borderColor: "white",
borderRadius: "8px",
whiteSpace: "nowrap",
minWidth: "180px",
}}
>
Личный кабинет
</Button>
</SectionWrapper>
</>
);
}

@ -6,10 +6,12 @@ import {
useTheme,
useMediaQuery,
} from "@mui/material";
import { Link } from "react-router-dom";
type Notification = {
text: string;
date: string;
url: string;
watched?: boolean;
};
@ -59,58 +61,68 @@ export const NotificationsModal = ({
}}
>
<List sx={{ width: "100%", padding: "5px" }}>
{notifications.map(({ text, date, watched = true }) => (
<ListItem
sx={{
display: "flex",
alignItems: isMobile ? "normal" : "center",
justifyContent: "space-between",
flexDirection: isMobile ? "column-reverse" : "unset",
borderBottom: "1px solid #F2F3F7",
padding: "20px 10px",
background: watched ? "none" : "#F0F0F6",
borderRadius: watched ? "0" : "8px",
"&:first-child": {
borderTop: "1px solid #F2F3F7",
},
{notifications.map(({ text, date, url, watched = true }) => (
<Link
to={url}
onClick={() => setOpen(false)}
style={{
textDecoration: "none",
color: "inherit",
}}
>
<Typography
<ListItem
key={text + date}
sx={{
position: "relative",
fontSize: "16px",
lineHeight: "19px",
paddingLeft: watched ? "0" : "35px",
fontWeight: watched ? "normal" : "bold",
"&::before": {
content: '""',
display: watched ? "none" : "block",
position: "absolute",
left: "10px",
top: isMobile ? "5px" : "50%",
transform: isMobile ? "none" : "translateY(-50%)",
height: "8px",
width: "8px",
borderRadius: "50%",
background: "#7E2AEA",
display: "flex",
alignItems: isMobile ? "normal" : "center",
justifyContent: "space-between",
flexDirection: isMobile ? "column-reverse" : "unset",
borderBottom: "1px solid #F2F3F7",
padding: "20px 10px",
background: watched ? "none" : "#F0F0F6",
borderRadius: watched ? "0" : "8px",
"&:first-child": {
borderTop: "1px solid #F2F3F7",
},
}}
>
{text}
</Typography>
<Typography
sx={{
fontSize: "14px",
lineHeight: "19px",
color: "#9A9AAF",
fontWeight: watched ? "normal" : "bold",
paddingLeft: isMobile ? (watched ? "0" : "35px") : "0",
marginBottom: isMobile ? "5px" : "0",
}}
>
{date}
</Typography>
</ListItem>
<Typography
sx={{
position: "relative",
fontSize: "16px",
lineHeight: "19px",
paddingLeft: watched ? "0" : "35px",
fontWeight: watched ? "normal" : "bold",
"&::before": {
content: '""',
display: watched ? "none" : "block",
position: "absolute",
left: "10px",
top: isMobile ? "5px" : "50%",
transform: isMobile ? "none" : "translateY(-50%)",
height: "8px",
width: "8px",
borderRadius: "50%",
background: "#7E2AEA",
},
}}
>
{text}
</Typography>
<Typography
sx={{
fontSize: "14px",
lineHeight: "19px",
color: "#9A9AAF",
fontWeight: watched ? "normal" : "bold",
paddingLeft: isMobile ? (watched ? "0" : "35px") : "0",
marginBottom: isMobile ? "5px" : "0",
}}
>
{date}
</Typography>
</ListItem>
</Link>
))}
</List>
</Popover>

@ -39,7 +39,7 @@ export const Select = ({
<Box>
<Box
sx={{
zIndex: 1500,
zIndex: 1,
position: "relative",
width: "100%",
height: "56px",
@ -81,6 +81,7 @@ export const Select = ({
<MuiSelect
ref={ref}
className="select"
value=""
open={opened}
MenuProps={{ disablePortal: true }}
sx={{ width: "100%" }}

@ -26,3 +26,7 @@
.MuiInputBase-root.MuiOutlinedInput-root .MuiSelect-icon {
display: none;
}
.MuiMenu-root.MuiModal-root {
z-index: 0;
}

@ -22,9 +22,7 @@ interface Props {
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => void;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
}
export default function PasswordInput({
@ -46,17 +44,13 @@ export default function PasswordInput({
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
const placeholderFont = upMd
? undefined
: { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const [showPassword, setShowPassword] = React.useState(false);
const handleClickShowPassword = () => setShowPassword((show) => !show);
const handleMouseDownPassword = (
event: React.MouseEvent<HTMLButtonElement>
) => {
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
};

@ -20,6 +20,7 @@ export default function Landing({ templaterOnly = false }: Props) {
<Box
sx={{
position: "relative",
paddingTop: "80px",
}}
>
<Navbar isLoggedIn={false} />

@ -46,6 +46,10 @@
right: -5px;
}
.slider .slick-arrow::before {
display: none;
}
.slider .slick-dots {
bottom: -15px;
}

@ -12,19 +12,23 @@ import "slick-carousel/slick/slick-theme.css";
import "./slider.css";
import type { ReactNode } from "react";
import type { CustomArrowProps } from "react-slick";
type SliderProps = {
items: ReactNode[];
};
type ArrowProps = CustomArrowProps & {
icon: ReactNode;
};
export const Slider = ({ items }: SliderProps) => {
const [range, setRange] = useState<number>(3);
const [activeRange, setActiveRange] = useState<number[]>([0, 1, 2]);
const theme = useTheme();
const isMiddle = useMediaQuery(
theme.breakpoints.down(1200) && theme.breakpoints.up(830)
);
const isMiddle = useMediaQuery(theme.breakpoints.down(1200));
const isTablet = useMediaQuery(theme.breakpoints.down(830));
const isMobileHeader = useMediaQuery(theme.breakpoints.down(900));
useEffect(() => {
if (isTablet) {
@ -64,10 +68,14 @@ export const Slider = ({ items }: SliderProps) => {
];
};
const Arrow = ({ currentSlide, slideCount, icon, ...props }: ArrowProps) => (
<Box {...props}>{icon}</Box>
);
return (
<Box className="slider-wrapper">
{(items.length < 4 && !isMiddle && !isTablet) ||
(items.length < 3 && isMiddle) ||
(items.length < 3 && isMiddle && !isTablet) ||
(items.length < 1 && isTablet) ? (
<Box
sx={{
@ -87,14 +95,15 @@ export const Slider = ({ items }: SliderProps) => {
dots
infinite
variableWidth
lazyLoad={isMobileHeader ? "progressive" : undefined}
slidesToShow={range}
prevArrow={<img src={arrowLeftIcon} alt="prev" />}
nextArrow={<img src={arrowRightIcon} alt="next" />}
prevArrow={<Arrow icon={<img src={arrowLeftIcon} alt="prev" />} />}
nextArrow={<Arrow icon={<img src={arrowRightIcon} alt="next" />} />}
beforeChange={(_, active) =>
setActiveRange(calculateRange(active, items.length))
}
customPaging={(slideNumber) => (
<li
<Box
className={classNames("dot", {
active: activeRange.includes(slideNumber),
})}

@ -76,39 +76,39 @@ export default function SigninDialog() {
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
}
return (
<Dialog
open={isDialogOpen}
onClose={handleClose}
PaperProps={{
sx: {
width: "600px",
maxWidth: "600px",
}
}}
slotProps={{
backdrop: {
style: {
backgroundColor: "rgb(0 0 0 / 0.7)",
}
}
}}
>
<Box
component="form"
onSubmit={formik.handleSubmit}
noValidate
sx={{
position: "relative",
backgroundColor: "white",
display: "flex",
alignItems: "center",
flexDirection: "column",
p: upMd ? "50px" : "18px",
pb: upMd ? "40px" : "30px",
gap: "15px",
borderRadius: "12px",
boxShadow: cardShadow,
return (
<Dialog
open={isDialogOpen}
onClose={handleClose}
PaperProps={{
sx: {
width: "600px",
maxWidth: "600px",
},
}}
slotProps={{
backdrop: {
style: {
backgroundColor: "rgb(0 0 0 / 0.7)",
},
},
}}
>
<Box
component="form"
onSubmit={formik.handleSubmit}
noValidate
sx={{
position: "relative",
backgroundColor: "white",
display: "flex",
alignItems: "center",
flexDirection: "column",
p: upMd ? "50px" : "18px",
pb: upMd ? "40px" : "30px",
gap: "15px",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
<IconButton

@ -16,193 +16,203 @@ import { makeRequest } from "@frontend/kitui";
import { cardShadow } from "@root/utils/themes/shadow";
import PasswordInput from "@root/components/passwordInput";
interface Values {
login: string;
password: string;
repeatPassword: string;
login: string;
password: string;
repeatPassword: string;
}
const initialValues: Values = {
login: "",
password: "",
repeatPassword: "",
login: "",
password: "",
repeatPassword: "",
};
const validationSchema = object({
login: string().required("Поле обязательно"),
password: string().min(8, "Минимум 8 символов").matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы").required("Поле обязательно"),
repeatPassword: string().oneOf([ref("password"), undefined], "Пароли не совпадают"),
login: string().required("Поле обязательно"),
password: string()
.min(8, "Минимум 8 символов")
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
.required("Поле обязательно"),
repeatPassword: string()
.oneOf([ref("password"), undefined], "Пароли не совпадают")
.required("Повторите пароль"),
});
export default function SignupDialog() {
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
const user = useUserStore(state => state.user);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const navigate = useNavigate();
const location = useLocation();
const formik = useFormik<Values>({
initialValues,
validationSchema,
onSubmit: (values, formikHelpers) => {
makeRequest<RegisterRequest, RegisterResponse>({
url: "https://hub.pena.digital/auth/register",
body: {
login: values.login.trim(),
password: values.password.trim(),
phoneNumber: "+7",
},
useToken: false,
withCredentials: true,
}).then(result => {
setUserId(result._id);
}).catch((error: any) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
}).finally(() => {
formikHelpers.setSubmitting(false);
});
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
const user = useUserStore((state) => state.user);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const navigate = useNavigate();
const location = useLocation();
const formik = useFormik<Values>({
initialValues,
validationSchema,
onSubmit: (values, formikHelpers) => {
makeRequest<RegisterRequest, RegisterResponse>({
url: "https://hub.pena.digital/auth/register",
body: {
login: values.login.trim(),
password: values.password.trim(),
phoneNumber: "+7",
},
});
useToken: false,
withCredentials: true,
})
.then((result) => {
setUserId(result._id);
})
.catch((error: any) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
})
.finally(() => {
formikHelpers.setSubmitting(false);
});
},
});
useEffect(function redirectIfSignedIn() {
if (user) navigate("/tariffs", { replace: true });
}, [navigate, user]);
useEffect(
function redirectIfSignedIn() {
if (user) navigate("/tariffs", { replace: true });
},
[navigate, user]
);
function handleClose() {
setIsDialogOpen(false);
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
}
function handleClose() {
setIsDialogOpen(false);
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
}
return (
<Dialog
open={isDialogOpen}
onClose={handleClose}
PaperProps={{
sx: {
width: "600px",
maxWidth: "600px",
}
}}
slotProps={{
backdrop: {
style: {
backgroundColor: "rgb(0 0 0 / 0.7)",
}
}
}}
return (
<Dialog
open={isDialogOpen}
onClose={handleClose}
PaperProps={{
sx: {
width: "600px",
maxWidth: "600px",
},
}}
slotProps={{
backdrop: {
style: {
backgroundColor: "rgb(0 0 0 / 0.7)",
},
},
}}
>
<Box
component="form"
onSubmit={formik.handleSubmit}
noValidate
sx={{
position: "relative",
backgroundColor: "white",
display: "flex",
alignItems: "center",
flexDirection: "column",
p: upMd ? "50px" : "18px",
pb: upMd ? "40px" : "30px",
gap: "15px",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
<IconButton
onClick={handleClose}
sx={{
position: "absolute",
right: "7px",
top: "7px",
}}
>
<Box
component="form"
onSubmit={formik.handleSubmit}
noValidate
sx={{
position: "relative",
backgroundColor: "white",
display: "flex",
alignItems: "center",
flexDirection: "column",
p: upMd ? "50px" : "18px",
pb: upMd ? "40px" : "30px",
gap: "15px",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
<IconButton
onClick={handleClose}
sx={{
position: "absolute",
right: "7px",
top: "7px",
}}
>
<CloseIcon sx={{ transform: "scale(1.5)" }} />
</IconButton>
<Box sx={{ mt: upMd ? undefined : "62px" }}>
<PenaLogo width={upMd ? 233 : 196} />
</Box>
<Typography
sx={{
color: theme.palette.grey3.main,
mt: "5px",
mb: upMd ? "35px" : "33px",
}}
>
Регистрация
</Typography>
<InputTextfield
TextfieldProps={{
value: formik.values.login,
placeholder: "username",
onBlur: formik.handleBlur,
error: formik.touched.login && Boolean(formik.errors.login),
helperText: formik.touched.login && formik.errors.login,
}}
onChange={formik.handleChange}
color="#F2F3F7"
id="login"
label="Логин"
gap={upMd ? "10px" : "10px"}
/>
<PasswordInput
TextfieldProps={{
value: formik.values.password,
placeholder: "Не менее 8 символов",
onBlur: formik.handleBlur,
error: formik.touched.password && Boolean(formik.errors.password),
helperText: formik.touched.password && formik.errors.password,
autoComplete: "new-password"
}}
onChange={formik.handleChange}
color="#F2F3F7"
id="password"
label="Пароль"
gap={upMd ? "10px" : "10px"}
/>
<PasswordInput
TextfieldProps={{
value: formik.values.repeatPassword,
placeholder: "Не менее 8 символов",
onBlur: formik.handleBlur,
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
autoComplete: "new-password"
}}
onChange={formik.handleChange}
color="#F2F3F7"
id="repeatPassword"
label="Повторить пароль"
gap={upMd ? "10px" : "10px"}
/>
<CustomButton
fullWidth
variant="contained"
sx={{
backgroundColor: theme.palette.brightPurple.main,
textColor: "white",
width: "100%",
py: "12px",
mt: upMd ? undefined : "10px",
}}
type="submit"
disabled={formik.isSubmitting}
>
Зарегистрироваться
</CustomButton>
<Link
component={RouterLink}
to="/signin"
state={{ backgroundLocation: location.state.backgroundLocation }}
sx={{
color: theme.palette.brightPurple.main,
mt: "auto",
}}
>
Вход в личный кабинет
</Link>
</Box>
</Dialog>
);
<CloseIcon sx={{ transform: "scale(1.5)" }} />
</IconButton>
<Box sx={{ mt: upMd ? undefined : "62px" }}>
<PenaLogo width={upMd ? 233 : 196} />
</Box>
<Typography
sx={{
color: theme.palette.grey3.main,
mt: "5px",
mb: upMd ? "35px" : "33px",
}}
>
Регистрация
</Typography>
<InputTextfield
TextfieldProps={{
value: formik.values.login,
placeholder: "username",
onBlur: formik.handleBlur,
error: formik.touched.login && Boolean(formik.errors.login),
helperText: formik.touched.login && formik.errors.login,
}}
onChange={formik.handleChange}
color="#F2F3F7"
id="login"
label="Логин"
gap={upMd ? "10px" : "10px"}
/>
<PasswordInput
TextfieldProps={{
value: formik.values.password,
placeholder: "Не менее 8 символов",
onBlur: formik.handleBlur,
error: formik.touched.password && Boolean(formik.errors.password),
helperText: formik.touched.password && formik.errors.password,
autoComplete: "new-password",
}}
onChange={formik.handleChange}
color="#F2F3F7"
id="password"
label="Пароль"
gap={upMd ? "10px" : "10px"}
/>
<PasswordInput
TextfieldProps={{
value: formik.values.repeatPassword,
placeholder: "Не менее 8 символов",
onBlur: formik.handleBlur,
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
autoComplete: "new-password",
}}
onChange={formik.handleChange}
color="#F2F3F7"
id="repeatPassword"
label="Повторить пароль"
gap={upMd ? "10px" : "10px"}
/>
<CustomButton
fullWidth
variant="contained"
sx={{
backgroundColor: theme.palette.brightPurple.main,
textColor: "white",
width: "100%",
py: "12px",
mt: upMd ? undefined : "10px",
}}
type="submit"
disabled={formik.isSubmitting}
>
Зарегистрироваться
</CustomButton>
<Link
component={RouterLink}
to="/signin"
state={{ backgroundLocation: location.state.backgroundLocation }}
sx={{
color: theme.palette.brightPurple.main,
mt: "auto",
}}
>
Вход в личный кабинет
</Link>
</Box>
</Dialog>
);
}

@ -1,8 +1 @@
export const cardShadow = `
0px 100px 309px rgba(210, 208, 225, 0.24),
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)
`;
export const cardShadow = "0px 15px 80px rgb(210 208 225 / 70%)";

@ -1,27 +1,22 @@
{
"extends": "./tsconfig.extend.json",
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
"extends": "./tsconfig.extend.json",
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom", "dom.iterable", "esnext"],
"types": ["node"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "**/*.ts"]
}

710
yarn.lock

File diff suppressed because it is too large Load Diff