Dev
This commit is contained in:
parent
1df8502813
commit
24399db30b
11
cypress.config.ts
Normal file
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
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");
|
||||
});
|
||||
});
|
@ -7,7 +7,9 @@
|
||||
"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"
|
||||
"eject": "craco eject",
|
||||
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
|
||||
"cypress": "cypress open"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
@ -19,6 +21,7 @@
|
||||
"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",
|
||||
|
@ -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,18 +73,23 @@ 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={{ height: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
zIndex: 2,
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
columnGap: "10px",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
height: "51px",
|
||||
padding: "0 18px",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@ -158,10 +165,10 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
||||
}}
|
||||
>
|
||||
<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.7) translate(50%, -50%)",
|
||||
@ -175,12 +182,13 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
||||
</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,
|
||||
}))}
|
||||
/>
|
||||
@ -188,6 +196,7 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
||||
<PenaLogo width={100} />
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", overflow: open ? "hidden" : "unset" }}>
|
||||
<Drawer
|
||||
sx={{
|
||||
@ -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,18 +3,14 @@ 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,
|
||||
@ -55,45 +51,44 @@ interface DialogMenuProps {
|
||||
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
||||
const theme = useTheme();
|
||||
const location = useLocation();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
const user = useUserStore((state) => state.user);
|
||||
const cash = useUserStore(state => state.userAccount?.wallet.cash) ?? 0;
|
||||
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
fullScreen
|
||||
sx={{ width: isMobile ? "100%" : "320px", ml: "auto", height: "100%" }}
|
||||
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}
|
||||
>
|
||||
<AppBar
|
||||
<List
|
||||
sx={{
|
||||
position: "relative",
|
||||
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
|
||||
boxShadow: "none",
|
||||
height: isMobile ? "66px" : "100px",
|
||||
height: "100vh",
|
||||
p: "0",
|
||||
paddingTop: "20px",
|
||||
}}
|
||||
>
|
||||
<Toolbar
|
||||
<ListItem
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
svg: { color: "#000000" },
|
||||
pl: "40px",
|
||||
flexDirection: "column",
|
||||
alignItems: isTablet ? "start" : "end",
|
||||
}}
|
||||
>
|
||||
{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}
|
||||
@ -105,7 +100,12 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
||||
variant="text"
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
color: location.pathname === url ? "#7E2AEA" : location.pathname === "/" ? "white" : "black",
|
||||
color:
|
||||
location.pathname === url
|
||||
? "#7E2AEA"
|
||||
: location.pathname === "/"
|
||||
? "white"
|
||||
: "black",
|
||||
height: "20px",
|
||||
textTransform: "none",
|
||||
marginBottom: "25px",
|
||||
@ -120,7 +120,7 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
||||
</Button>
|
||||
))}
|
||||
</ListItem>
|
||||
{isMobile ? (
|
||||
{isTablet ? (
|
||||
location.pathname === "/" ? (
|
||||
<Button
|
||||
component={Link}
|
||||
@ -166,7 +166,10 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
||||
>
|
||||
Мой баланс
|
||||
</Typography>
|
||||
<Typography variant="body2" color={theme.palette.brightPurple.main}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.brightPurple.main}
|
||||
>
|
||||
{currencyFormatter.format(cash / 100)}
|
||||
</Typography>
|
||||
</Box>
|
||||
@ -201,7 +204,10 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
||||
bottom: "60px",
|
||||
}}
|
||||
>
|
||||
<img src={location.pathname === "/" ? logotip : logotipBlack} alt="icon" />
|
||||
<img
|
||||
src={location.pathname === "/" ? logotip : logotipBlack}
|
||||
alt="icon"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
@ -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";
|
||||
@ -23,7 +30,7 @@ export default function NavbarFull({ isLoggedIn }: Props) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const cash = useUserStore(state => state.userAccount?.wallet.cash) ?? 0;
|
||||
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
|
||||
|
||||
async function handleLogoutClick() {
|
||||
try {
|
||||
@ -53,7 +60,9 @@ export default function NavbarFull({ isLoggedIn }: Props) {
|
||||
borderBottom: "1px solid #E3E3E3",
|
||||
}}
|
||||
>
|
||||
<Link to="/"><PenaLogo width={124} /></Link>
|
||||
<Link to="/">
|
||||
<PenaLogo width={124} />
|
||||
</Link>
|
||||
<Menu />
|
||||
<Box
|
||||
sx={{
|
||||
@ -85,7 +94,13 @@ export default function NavbarFull({ isLoggedIn }: Props) {
|
||||
<CustomAvatar />
|
||||
<IconButton
|
||||
onClick={handleLogoutClick}
|
||||
sx={{ ml: "20px", bgcolor: "#F2F3F7", borderRadius: "6px", height: "36px", width: "36px" }}
|
||||
sx={{
|
||||
ml: "20px",
|
||||
bgcolor: "#F2F3F7",
|
||||
borderRadius: "6px",
|
||||
height: "36px",
|
||||
width: "36px",
|
||||
}}
|
||||
>
|
||||
<LogoutIcon />
|
||||
</IconButton>
|
||||
@ -97,6 +112,8 @@ export default function NavbarFull({ isLoggedIn }: Props) {
|
||||
component="nav"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
backgroundColor: theme.palette.lightPurple.main,
|
||||
borderBottom: "1px solid #E3E3E3",
|
||||
}}
|
||||
|
@ -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,8 +61,17 @@ export const NotificationsModal = ({
|
||||
}}
|
||||
>
|
||||
<List sx={{ width: "100%", padding: "5px" }}>
|
||||
{notifications.map(({ text, date, watched = true }) => (
|
||||
{notifications.map(({ text, date, url, watched = true }) => (
|
||||
<Link
|
||||
to={url}
|
||||
onClick={() => setOpen(false)}
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
}}
|
||||
>
|
||||
<ListItem
|
||||
key={text + date}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: isMobile ? "normal" : "center",
|
||||
@ -111,6 +122,7 @@ export const NotificationsModal = ({
|
||||
{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),
|
||||
})}
|
||||
|
@ -84,14 +84,14 @@ export default function SigninDialog() {
|
||||
sx: {
|
||||
width: "600px",
|
||||
maxWidth: "600px",
|
||||
}
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
backdrop: {
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
|
@ -16,7 +16,6 @@ import { makeRequest } from "@frontend/kitui";
|
||||
import { cardShadow } from "@root/utils/themes/shadow";
|
||||
import PasswordInput from "@root/components/passwordInput";
|
||||
|
||||
|
||||
interface Values {
|
||||
login: string;
|
||||
password: string;
|
||||
@ -31,13 +30,18 @@ const initialValues: Values = {
|
||||
|
||||
const validationSchema = object({
|
||||
login: string().required("Поле обязательно"),
|
||||
password: string().min(8, "Минимум 8 символов").matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы").required("Поле обязательно"),
|
||||
repeatPassword: string().oneOf([ref("password"), undefined], "Пароли не совпадают"),
|
||||
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 user = useUserStore((state) => state.user);
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
@ -55,20 +59,26 @@ export default function SignupDialog() {
|
||||
},
|
||||
useToken: false,
|
||||
withCredentials: true,
|
||||
}).then(result => {
|
||||
})
|
||||
.then((result) => {
|
||||
setUserId(result._id);
|
||||
}).catch((error: any) => {
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const errorMessage = getMessageFromFetchError(error);
|
||||
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
formikHelpers.setSubmitting(false);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(function redirectIfSignedIn() {
|
||||
useEffect(
|
||||
function redirectIfSignedIn() {
|
||||
if (user) navigate("/tariffs", { replace: true });
|
||||
}, [navigate, user]);
|
||||
},
|
||||
[navigate, user]
|
||||
);
|
||||
|
||||
function handleClose() {
|
||||
setIsDialogOpen(false);
|
||||
@ -83,14 +93,14 @@ export default function SignupDialog() {
|
||||
sx: {
|
||||
width: "600px",
|
||||
maxWidth: "600px",
|
||||
}
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
backdrop: {
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -153,7 +163,7 @@ export default function SignupDialog() {
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
autoComplete: "new-password"
|
||||
autoComplete: "new-password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
color="#F2F3F7"
|
||||
@ -168,7 +178,7 @@ export default function SignupDialog() {
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
||||
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||
autoComplete: "new-password"
|
||||
autoComplete: "new-password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
color="#F2F3F7"
|
||||
|
@ -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%)";
|
||||
|
@ -2,11 +2,8 @@
|
||||
"extends": "./tsconfig.extend.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["es5", "dom", "dom.iterable", "esnext"],
|
||||
"types": ["node"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@ -21,7 +18,5 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src", "**/*.ts"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user