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");
|
||||||
|
});
|
||||||
|
});
|
139
package.json
139
package.json
@ -1,70 +1,73 @@
|
|||||||
{
|
{
|
||||||
"name": "hub_frontend",
|
"name": "hub_frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
|
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
|
||||||
"test:cart": "craco test src/utils/calcCart --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",
|
||||||
"dependencies": {
|
"cypress": "cypress open"
|
||||||
"@emotion/react": "^11.10.5",
|
},
|
||||||
"@emotion/styled": "^11.10.5",
|
"dependencies": {
|
||||||
"@frontend/kitui": "^1.0.17",
|
"@emotion/react": "^11.10.5",
|
||||||
"@mui/icons-material": "^5.10.14",
|
"@emotion/styled": "^11.10.5",
|
||||||
"@mui/material": "^5.10.14",
|
"@frontend/kitui": "^1.0.17",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@mui/icons-material": "^5.10.14",
|
||||||
"axios": "^1.4.0",
|
"@mui/material": "^5.10.14",
|
||||||
"buffer": "^6.0.3",
|
"@popperjs/core": "^2.11.8",
|
||||||
"classnames": "^2.3.2",
|
"axios": "^1.4.0",
|
||||||
"formik": "^2.2.9",
|
"buffer": "^6.0.3",
|
||||||
"immer": "^10.0.2",
|
"classnames": "^2.3.2",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"cypress": "^12.17.3",
|
||||||
"notistack": "^3.0.1",
|
"formik": "^2.2.9",
|
||||||
"pdfjs-dist": "3.6.172",
|
"immer": "^10.0.2",
|
||||||
"react": "^18.2.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"notistack": "^3.0.1",
|
||||||
"react-pdf": "^7.1.2",
|
"pdfjs-dist": "3.6.172",
|
||||||
"react-router-dom": "^6.4.3",
|
"react": "^18.2.0",
|
||||||
"react-slick": "^0.29.0",
|
"react-dom": "^18.2.0",
|
||||||
"slick-carousel": "^1.8.1",
|
"react-pdf": "^7.1.2",
|
||||||
"web-vitals": "^2.1.0",
|
"react-router-dom": "^6.4.3",
|
||||||
"yup": "^1.1.1",
|
"react-slick": "^0.29.0",
|
||||||
"zustand": "^4.3.8"
|
"slick-carousel": "^1.8.1",
|
||||||
},
|
"web-vitals": "^2.1.0",
|
||||||
"devDependencies": {
|
"yup": "^1.1.1",
|
||||||
"@craco/craco": "^7.1.0",
|
"zustand": "^4.3.8"
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
},
|
||||||
"@testing-library/react": "^13.0.0",
|
"devDependencies": {
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@craco/craco": "^7.1.0",
|
||||||
"@types/jest": "^27.0.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@types/node": "^16.7.13",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@types/react": "^18.0.0",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/react-slick": "^0.23.10",
|
"@types/node": "^16.7.13",
|
||||||
"craco-alias": "^3.0.1",
|
"@types/react": "^18.0.0",
|
||||||
"jest": "^29.5.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"react-scripts": "5.0.1",
|
"@types/react-slick": "^0.23.10",
|
||||||
"typescript": "^4.9.3"
|
"craco-alias": "^3.0.1",
|
||||||
},
|
"jest": "^29.5.0",
|
||||||
"eslintConfig": {
|
"react-scripts": "5.0.1",
|
||||||
"extends": [
|
"typescript": "^4.9.3"
|
||||||
"react-app",
|
},
|
||||||
"react-app/jest"
|
"eslintConfig": {
|
||||||
]
|
"extends": [
|
||||||
},
|
"react-app",
|
||||||
"browserslist": {
|
"react-app/jest"
|
||||||
"production": [
|
]
|
||||||
">0.2%",
|
},
|
||||||
"not dead",
|
"browserslist": {
|
||||||
"not op_mini all"
|
"production": [
|
||||||
],
|
">0.2%",
|
||||||
"development": [
|
"not dead",
|
||||||
"last 1 chrome version",
|
"not op_mini all"
|
||||||
"last 1 firefox version",
|
],
|
||||||
"last 1 safari version"
|
"development": [
|
||||||
]
|
"last 1 chrome version",
|
||||||
}
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,9 +51,7 @@ export default function Drawers() {
|
|||||||
(state) => state.summaryPriceAfterDiscountsMap
|
(state) => state.summaryPriceAfterDiscountsMap
|
||||||
);
|
);
|
||||||
const userAccount = useUserStore((state) => state.userAccount);
|
const userAccount = useUserStore((state) => state.userAccount);
|
||||||
const { tickets, ticketCount, apiPage, ticketsPerPage } = useTicketStore(
|
const { tickets, apiPage, ticketsPerPage } = useTicketStore((state) => state);
|
||||||
(state) => state
|
|
||||||
);
|
|
||||||
|
|
||||||
useTickets({
|
useTickets({
|
||||||
url: "https://hub.pena.digital/heruvym/getTickets",
|
url: "https://hub.pena.digital/heruvym/getTickets",
|
||||||
@ -75,6 +73,10 @@ export default function Drawers() {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const notificationsCount = tickets.filter(
|
||||||
|
({ user, top_message }) => user !== top_message.user_id
|
||||||
|
).length;
|
||||||
|
|
||||||
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts + basePrice;
|
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts + basePrice;
|
||||||
const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice;
|
const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice;
|
||||||
|
|
||||||
@ -119,10 +121,10 @@ export default function Drawers() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
badgeContent={ticketCount}
|
badgeContent={notificationsCount}
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiBadge-badge": {
|
"& .MuiBadge-badge": {
|
||||||
display: ticketCount ? "flex" : "none",
|
display: notificationsCount ? "flex" : "none",
|
||||||
color: "#FFFFFF",
|
color: "#FFFFFF",
|
||||||
background: theme.palette.brightPurple.main,
|
background: theme.palette.brightPurple.main,
|
||||||
transform: "scale(0.8) translate(50%, -50%)",
|
transform: "scale(0.8) translate(50%, -50%)",
|
||||||
@ -136,12 +138,13 @@ export default function Drawers() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<NotificationsModal
|
<NotificationsModal
|
||||||
open={openNotificationsModal}
|
open={notificationsCount ? openNotificationsModal : false}
|
||||||
setOpen={setOpenNotificationsModal}
|
setOpen={setOpenNotificationsModal}
|
||||||
anchorElement={bellRef.current}
|
anchorElement={bellRef.current}
|
||||||
notifications={tickets.map((ticket) => ({
|
notifications={tickets.map((ticket) => ({
|
||||||
text: "У вас новое сообщение от техподдержки",
|
text: "У вас новое сообщение от техподдержки",
|
||||||
date: new Date(ticket.updated_at).toLocaleDateString(),
|
date: new Date(ticket.updated_at).toLocaleDateString(),
|
||||||
|
url: `/support/${ticket.id}`,
|
||||||
watched: ticket.user === ticket.top_message.user_id,
|
watched: ticket.user === ticket.top_message.user_id,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
|
|
||||||
import { useUserStore } from "@root/stores/user";
|
import { useUserStore } from "@root/stores/user";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
import { cardShadow } from "@root/utils/themes/shadow";
|
||||||
|
|
||||||
import CustomAvatar from "./Avatar";
|
import CustomAvatar from "./Avatar";
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ export default function DialogMenu({ handleClose }: DialogMenuProps) {
|
|||||||
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1);
|
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
|
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
|
||||||
@ -118,12 +120,13 @@ export default function DialogMenu({ handleClose }: DialogMenuProps) {
|
|||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
boxShadow: !isTablet ? cardShadow : null,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{index === activeSubMenuIndex &&
|
{index === activeSubMenuIndex &&
|
||||||
subMenu.map(({ name, url }) => (
|
subMenu.map(({ name, url }) => (
|
||||||
<Link
|
<Link
|
||||||
key={url}
|
key={name + url}
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: "30px",
|
paddingLeft: "30px",
|
||||||
display: "block",
|
display: "block",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMediaQuery, useTheme } from "@mui/material";
|
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import NavbarCollapsed from "./NavbarCollapsed";
|
import NavbarCollapsed from "./NavbarCollapsed";
|
||||||
import NavbarFull from "./NavbarFull";
|
import NavbarFull from "./NavbarFull";
|
||||||
|
|
||||||
@ -14,12 +14,17 @@ export default function Navbar({ isLoggedIn, children }: Props) {
|
|||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box
|
||||||
|
sx={{
|
||||||
|
paddingTop: upMd ? "80px" : 0,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{upMd ? (
|
{upMd ? (
|
||||||
<NavbarFull isLoggedIn={isLoggedIn}>{children}</NavbarFull>
|
<NavbarFull isLoggedIn={isLoggedIn}>{children}</NavbarFull>
|
||||||
) : (
|
) : (
|
||||||
<NavbarCollapsed isLoggedIn={isLoggedIn}>{children}</NavbarCollapsed>
|
<NavbarCollapsed isLoggedIn={isLoggedIn}>{children}</NavbarCollapsed>
|
||||||
)}
|
)}
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,7 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
|||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const bellRef = useRef<HTMLButtonElement | null>(null);
|
const bellRef = useRef<HTMLButtonElement | null>(null);
|
||||||
const userAccount = useUserStore((state) => state.userAccount);
|
const userAccount = useUserStore((state) => state.userAccount);
|
||||||
const { ticketCount, tickets, apiPage, ticketsPerPage } = useTicketStore(
|
const { tickets, apiPage, ticketsPerPage } = useTicketStore((state) => state);
|
||||||
(state) => state
|
|
||||||
);
|
|
||||||
|
|
||||||
useTickets({
|
useTickets({
|
||||||
url: "https://hub.pena.digital/heruvym/getTickets",
|
url: "https://hub.pena.digital/heruvym/getTickets",
|
||||||
@ -55,6 +53,10 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const notificationsCount = tickets.filter(
|
||||||
|
({ user, top_message }) => user !== top_message.user_id
|
||||||
|
).length;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
@ -71,36 +73,80 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
|||||||
maxWidth="lg"
|
maxWidth="lg"
|
||||||
outerContainerSx={{
|
outerContainerSx={{
|
||||||
backgroundColor: theme.palette.navbarbg.main,
|
backgroundColor: theme.palette.navbarbg.main,
|
||||||
position: "sticky",
|
|
||||||
top: 0,
|
|
||||||
}}
|
}}
|
||||||
sx={{ height: "51px", padding: "0" }}
|
sx={{ height: "51px", padding: "0" }}
|
||||||
>
|
>
|
||||||
<Box
|
<Box sx={{ height: "100%" }}>
|
||||||
sx={{
|
<Box
|
||||||
display: "flex",
|
|
||||||
columnGap: "10px",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100%",
|
|
||||||
padding: "0 18px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setOpen((isOpened) => !isOpened)}
|
|
||||||
sx={{
|
sx={{
|
||||||
p: 0,
|
zIndex: 2,
|
||||||
width: "30px",
|
position: "fixed",
|
||||||
color: theme.palette.primary.main,
|
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
|
<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"
|
aria-label="cart"
|
||||||
sx={{
|
sx={{
|
||||||
width: "30px",
|
width: "30px",
|
||||||
@ -113,80 +159,43 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
|||||||
background: theme.palette.background.default,
|
background: theme.palette.background.default,
|
||||||
color: theme.palette.brightPurple.main,
|
color: theme.palette.brightPurple.main,
|
||||||
},
|
},
|
||||||
"& svg > path:nth-child(1)": { fill: "#FFFFFF" },
|
"& svg > path:first-child": { fill: "#FFFFFF" },
|
||||||
"& svg > path:nth-child(2)": { fill: "#FFFFFF" },
|
"& svg > path:last-child": { stroke: "#FFFFFF" },
|
||||||
"& svg > path:nth-child(3)": { stroke: "#FFFFFF" },
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
badgeContent={userAccount?.cart.length}
|
badgeContent={notificationsCount}
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiBadge-badge": {
|
"& .MuiBadge-badge": {
|
||||||
display: userAccount?.cart.length ? "flex" : "none",
|
display: notificationsCount ? "flex" : "none",
|
||||||
color: "#FFFFFF",
|
color: "#FFFFFF",
|
||||||
background: theme.palette.brightPurple.main,
|
background: theme.palette.brightPurple.main,
|
||||||
transform: "scale(0.7) translate(50%, -50%)",
|
transform: "scale(0.7) translate(50%, -50%)",
|
||||||
top: "2px",
|
top: "3px",
|
||||||
right: "3px",
|
right: "3px",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CartIcon />
|
<BellIcon />
|
||||||
</Badge>
|
</Badge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Link>
|
<NotificationsModal
|
||||||
<IconButton
|
open={notificationsCount ? openNotificationsModal : false}
|
||||||
ref={bellRef}
|
setOpen={setOpenNotificationsModal}
|
||||||
onClick={() => setOpenNotificationsModal((isOpened) => !isOpened)}
|
anchorElement={bellRef.current}
|
||||||
aria-label="cart"
|
notifications={tickets.map((ticket) => ({
|
||||||
sx={{
|
text: "У вас новое сообщение от техподдержки",
|
||||||
width: "30px",
|
date: new Date(ticket.updated_at).toLocaleDateString(),
|
||||||
height: "30px",
|
url: `/support/${ticket.id}`,
|
||||||
background: theme.palette.background.default,
|
watched: ticket.user === ticket.top_message.user_id,
|
||||||
borderRadius: "6px",
|
}))}
|
||||||
"&:hover": {
|
/>
|
||||||
background: theme.palette.brightPurple.main,
|
<Link to="/" style={{ marginLeft: "auto" }}>
|
||||||
"& .MuiBadge-badge": {
|
<PenaLogo width={100} />
|
||||||
background: theme.palette.background.default,
|
</Link>
|
||||||
color: theme.palette.brightPurple.main,
|
</Box>
|
||||||
},
|
|
||||||
"& 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>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: "flex", overflow: open ? "hidden" : "unset" }}>
|
<Box sx={{ display: "flex", overflow: open ? "hidden" : "unset" }}>
|
||||||
<Drawer
|
<Drawer
|
||||||
@ -195,10 +204,11 @@ export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: open ? "none" : "-1",
|
zIndex: open ? "none" : "-1",
|
||||||
"& .MuiDrawer-paper": {
|
"& .MuiDrawer-paper": {
|
||||||
position: "absolute",
|
position: "fixed",
|
||||||
top: "0",
|
top: "0",
|
||||||
width: 210,
|
width: 210,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
marginTop: "51px",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
variant="persistent"
|
variant="persistent"
|
||||||
|
@ -58,6 +58,8 @@ export default function NavbarFull({ isLoggedIn, children }: Props) {
|
|||||||
disableGutters
|
disableGutters
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
sx={{
|
sx={{
|
||||||
|
position: "fixed",
|
||||||
|
top: "0",
|
||||||
px: "16px",
|
px: "16px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: "80px",
|
height: "80px",
|
||||||
|
@ -3,209 +3,215 @@ import { TransitionProps } from "@mui/material/transitions";
|
|||||||
import logotip from "../../assets/Icons/logoPenaHab.svg";
|
import logotip from "../../assets/Icons/logoPenaHab.svg";
|
||||||
import logotipBlack from "../../assets/Icons/black_logo_PenaHab.svg";
|
import logotipBlack from "../../assets/Icons/black_logo_PenaHab.svg";
|
||||||
import CustomAvatar from "./Avatar";
|
import CustomAvatar from "./Avatar";
|
||||||
import CloseIcon from "../icons/CloseIcons";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
AppBar,
|
Box,
|
||||||
Box,
|
Button,
|
||||||
Button,
|
Dialog,
|
||||||
Dialog,
|
List,
|
||||||
IconButton,
|
ListItem,
|
||||||
List,
|
Slide,
|
||||||
ListItem,
|
Typography,
|
||||||
Slide,
|
useMediaQuery,
|
||||||
Toolbar,
|
useTheme,
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import { useUserStore } from "@root/stores/user";
|
import { useUserStore } from "@root/stores/user";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
|
||||||
const arrayMenu = [
|
const arrayMenu = [
|
||||||
{ name: "Главная", url: "/" },
|
{ name: "Главная", url: "/" },
|
||||||
{ name: "Тарифы", url: "/tariffs" },
|
{ name: "Тарифы", url: "/tariffs" },
|
||||||
{ name: "Тарифы на время", url: "/tariffs/time" },
|
{ name: "Тарифы на время", url: "/tariffs/time" },
|
||||||
{ name: "Тарифы на объём", url: "/tariffs/volume" },
|
{ name: "Тарифы на объём", url: "/tariffs/volume" },
|
||||||
{ name: "Кастомный тариф", url: "/tariffconstructor" },
|
{ name: "Кастомный тариф", url: "/tariffconstructor" },
|
||||||
{ name: "Вопросы и ответы", url: "/faq" },
|
{ name: "Вопросы и ответы", url: "/faq" },
|
||||||
{ name: "История", url: "/history" },
|
{ name: "История", url: "/history" },
|
||||||
{ name: "Оплата", url: "/payment" },
|
{ name: "Оплата", url: "/payment" },
|
||||||
{ name: "Поддержка", url: "/support" },
|
{ name: "Поддержка", url: "/support" },
|
||||||
{ name: "Оплата", url: "/wallet" },
|
{ name: "Оплата", url: "/wallet" },
|
||||||
{ name: "Корзина", url: "/cart" },
|
{ name: "Корзина", url: "/cart" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Transition = React.forwardRef(function Transition(
|
const Transition = React.forwardRef(function Transition(
|
||||||
props: TransitionProps & {
|
props: TransitionProps & {
|
||||||
children: React.ReactElement;
|
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 {
|
interface DialogMenuProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||||
const user = useUserStore((state) => state.user);
|
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 (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
fullScreen
|
fullScreen
|
||||||
sx={{ width: isMobile ? "100%" : "320px", ml: "auto", height: "100%" }}
|
sx={{
|
||||||
open={open}
|
width: isTablet ? "100%" : "320px",
|
||||||
onClose={handleClose}
|
ml: "auto",
|
||||||
TransitionComponent={Transition}
|
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
|
{arrayMenu.map(({ name, url }, index) => (
|
||||||
sx={{
|
<Button
|
||||||
position: "relative",
|
key={index}
|
||||||
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
|
component={Link}
|
||||||
boxShadow: "none",
|
to={url}
|
||||||
height: isMobile ? "66px" : "100px",
|
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
|
{name}
|
||||||
sx={{
|
</Button>
|
||||||
display: "flex",
|
))}
|
||||||
justifyContent: "space-between",
|
</ListItem>
|
||||||
svg: { color: "#000000" },
|
{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" }}>
|
</Typography>
|
||||||
<img src={location.pathname === "/" ? logotip : logotipBlack} alt="icon" />
|
<Typography
|
||||||
</Box>
|
variant="body2"
|
||||||
)}
|
color={theme.palette.brightPurple.main}
|
||||||
<IconButton sx={{ ml: "auto" }} edge="start" color="inherit" onClick={handleClose} aria-label="close">
|
>
|
||||||
<CloseIcon />
|
{currencyFormatter.format(cash / 100)}
|
||||||
</IconButton>
|
</Typography>
|
||||||
</Toolbar>
|
</Box>
|
||||||
</AppBar>
|
</Box>
|
||||||
<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
|
<Button
|
||||||
key={index}
|
component={Link}
|
||||||
component={Link}
|
to={user ? "/tariffs" : "/signin"}
|
||||||
to={url}
|
state={user ? undefined : { backgroundLocation: location }}
|
||||||
onClick={handleClose}
|
variant="outlined"
|
||||||
state={user ? undefined : { backgroundLocation: location }}
|
sx={{
|
||||||
disableRipple
|
width: "188px",
|
||||||
variant="text"
|
color: "white",
|
||||||
sx={{
|
border: "1px solid white",
|
||||||
fontWeight: "500",
|
margin: "35px 0 0 126px",
|
||||||
color: location.pathname === url ? "#7E2AEA" : location.pathname === "/" ? "white" : "black",
|
textTransform: "none",
|
||||||
height: "20px",
|
fontWeight: "400",
|
||||||
textTransform: "none",
|
fontSize: "18px",
|
||||||
marginBottom: "25px",
|
lineHeight: "24px",
|
||||||
fontSize: "16px",
|
borderRadius: "8px",
|
||||||
"&:hover": {
|
padding: "8px 17px",
|
||||||
background: "none",
|
}}
|
||||||
color: "#7E2AEA",
|
>
|
||||||
},
|
Личный кабинет
|
||||||
}}
|
</Button>
|
||||||
>
|
<Box
|
||||||
{name}
|
sx={{
|
||||||
</Button>
|
position: "absolute",
|
||||||
))}
|
right: "40px",
|
||||||
</ListItem>
|
bottom: "60px",
|
||||||
{isMobile ? (
|
}}
|
||||||
location.pathname === "/" ? (
|
>
|
||||||
<Button
|
<img
|
||||||
component={Link}
|
src={location.pathname === "/" ? logotip : logotipBlack}
|
||||||
to={user ? "/tariffs" : "/signin"}
|
alt="icon"
|
||||||
state={user ? undefined : { backgroundLocation: location }}
|
/>
|
||||||
variant="outlined"
|
</Box>
|
||||||
sx={{
|
</>
|
||||||
width: "188px",
|
)}
|
||||||
color: "white",
|
</List>
|
||||||
border: "1px solid white",
|
</Dialog>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { IconButton, useTheme } from "@mui/material";
|
import { IconButton, useTheme } from "@mui/material";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import SectionWrapper from "../SectionWrapper";
|
import SectionWrapper from "../SectionWrapper";
|
||||||
|
|
||||||
import PenaLogo from "../PenaLogo";
|
import PenaLogo from "../PenaLogo";
|
||||||
import DialogMenu from "./DialogMenu";
|
import DialogMenu from "./DialogMenu";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
@ -30,11 +30,10 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
|
|||||||
component="nav"
|
component="nav"
|
||||||
maxWidth="lg"
|
maxWidth="lg"
|
||||||
outerContainerSx={{
|
outerContainerSx={{
|
||||||
|
position: "fixed",
|
||||||
|
top: "0",
|
||||||
backgroundColor: theme.palette.navbarbg.main,
|
backgroundColor: theme.palette.navbarbg.main,
|
||||||
position: "sticky",
|
borderBottom: "1px solid #E3E3E3",
|
||||||
top: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
// borderBottom: "1px solid #E3E3E3",
|
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
height: "51px",
|
height: "51px",
|
||||||
@ -44,9 +43,14 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link to="/"><PenaLogo width={100} /></Link>
|
<Link to="/">
|
||||||
|
<PenaLogo width={100} />
|
||||||
<IconButton onClick={handleClickOpen} sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}>
|
</Link>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
onClick={handleClickOpen}
|
||||||
|
sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}
|
||||||
|
>
|
||||||
<MenuIcon sx={{ height: "30px", width: "30px" }} />
|
<MenuIcon sx={{ height: "30px", width: "30px" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<DialogMenu open={open} handleClose={handleClose} />
|
<DialogMenu open={open} handleClose={handleClose} />
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
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 SectionWrapper from "../SectionWrapper";
|
||||||
import LogoutIcon from "../icons/LogoutIcon";
|
import LogoutIcon from "../icons/LogoutIcon";
|
||||||
import WalletIcon from "../icons/WalletIcon";
|
import WalletIcon from "../icons/WalletIcon";
|
||||||
@ -15,119 +22,129 @@ import { clearCustomTariffs } from "@root/stores/customTariffs";
|
|||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NavbarFull({ isLoggedIn }: Props) {
|
export default function NavbarFull({ isLoggedIn }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const user = useUserStore((state) => state.user);
|
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() {
|
async function handleLogoutClick() {
|
||||||
try {
|
try {
|
||||||
await logout();
|
await logout();
|
||||||
clearAuthToken();
|
clearAuthToken();
|
||||||
clearUserData();
|
clearUserData();
|
||||||
clearCustomTariffs();
|
clearCustomTariffs();
|
||||||
navigate("/");
|
navigate("/");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message = getMessageFromFetchError(error, "Не удалось выйти");
|
const message = getMessageFromFetchError(error, "Не удалось выйти");
|
||||||
if (message) enqueueSnackbar(message);
|
if (message) enqueueSnackbar(message);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return isLoggedIn ? (
|
return isLoggedIn ? (
|
||||||
<Container
|
<Container
|
||||||
component="nav"
|
component="nav"
|
||||||
disableGutters
|
disableGutters
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
sx={{
|
sx={{
|
||||||
px: "16px",
|
px: "16px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: "80px",
|
height: "80px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "60px",
|
gap: "60px",
|
||||||
bgcolor: "white",
|
bgcolor: "white",
|
||||||
borderBottom: "1px solid #E3E3E3",
|
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>
|
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
|
||||||
<Menu />
|
</IconButton>
|
||||||
<Box
|
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
|
||||||
sx={{
|
<Typography
|
||||||
display: "flex",
|
sx={{
|
||||||
ml: "auto",
|
fontSize: "12px",
|
||||||
}}
|
lineHeight: "14px",
|
||||||
>
|
color: theme.palette.grey3.main,
|
||||||
<Drawers />
|
}}
|
||||||
<IconButton
|
>
|
||||||
sx={{ p: 0, ml: "8px" }}
|
Мой баланс
|
||||||
onClick={() => navigate("/wallet")}
|
</Typography>
|
||||||
>
|
<Typography variant="body2" color={theme.palette.brightPurple.main}>
|
||||||
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
|
{currencyFormatter.format(cash / 100)}
|
||||||
</IconButton>
|
</Typography>
|
||||||
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
|
</Box>
|
||||||
<Typography
|
<CustomAvatar />
|
||||||
sx={{
|
<IconButton
|
||||||
fontSize: "12px",
|
onClick={handleLogoutClick}
|
||||||
lineHeight: "14px",
|
sx={{
|
||||||
color: theme.palette.grey3.main,
|
ml: "20px",
|
||||||
}}
|
bgcolor: "#F2F3F7",
|
||||||
>
|
borderRadius: "6px",
|
||||||
Мой баланс
|
height: "36px",
|
||||||
</Typography>
|
width: "36px",
|
||||||
<Typography variant="body2" color={theme.palette.brightPurple.main}>
|
}}
|
||||||
{currencyFormatter.format(cash / 100)}
|
>
|
||||||
</Typography>
|
<LogoutIcon />
|
||||||
</Box>
|
</IconButton>
|
||||||
<CustomAvatar />
|
</Box>
|
||||||
<IconButton
|
</Container>
|
||||||
onClick={handleLogoutClick}
|
) : (
|
||||||
sx={{ ml: "20px", bgcolor: "#F2F3F7", borderRadius: "6px", height: "36px", width: "36px" }}
|
<>
|
||||||
>
|
<SectionWrapper
|
||||||
<LogoutIcon />
|
component="nav"
|
||||||
</IconButton>
|
maxWidth="lg"
|
||||||
</Box>
|
outerContainerSx={{
|
||||||
</Container>
|
position: "fixed",
|
||||||
) : (
|
top: "0",
|
||||||
<>
|
backgroundColor: theme.palette.lightPurple.main,
|
||||||
<SectionWrapper
|
borderBottom: "1px solid #E3E3E3",
|
||||||
component="nav"
|
}}
|
||||||
maxWidth="lg"
|
sx={{
|
||||||
outerContainerSx={{
|
px: "20px",
|
||||||
backgroundColor: theme.palette.lightPurple.main,
|
display: "flex",
|
||||||
borderBottom: "1px solid #E3E3E3",
|
justifyContent: "space-between",
|
||||||
}}
|
height: "80px",
|
||||||
sx={{
|
alignItems: "center",
|
||||||
px: "20px",
|
gap: "50px",
|
||||||
display: "flex",
|
}}
|
||||||
justifyContent: "space-between",
|
>
|
||||||
height: "80px",
|
<PenaLogo width={150} />
|
||||||
alignItems: "center",
|
<Menu />
|
||||||
gap: "50px",
|
<Button
|
||||||
}}
|
component={Link}
|
||||||
>
|
to={user ? "/tariffs" : "/signin"}
|
||||||
<PenaLogo width={150} />
|
state={user ? undefined : { backgroundLocation: location }}
|
||||||
<Menu />
|
variant="outlined"
|
||||||
<Button
|
sx={{
|
||||||
component={Link}
|
px: "18px",
|
||||||
to={user ? "/tariffs" : "/signin"}
|
py: "10px",
|
||||||
state={user ? undefined : { backgroundLocation: location }}
|
borderColor: "white",
|
||||||
variant="outlined"
|
borderRadius: "8px",
|
||||||
sx={{
|
whiteSpace: "nowrap",
|
||||||
px: "18px",
|
minWidth: "180px",
|
||||||
py: "10px",
|
}}
|
||||||
borderColor: "white",
|
>
|
||||||
borderRadius: "8px",
|
Личный кабинет
|
||||||
whiteSpace: "nowrap",
|
</Button>
|
||||||
minWidth: "180px",
|
</SectionWrapper>
|
||||||
}}
|
</>
|
||||||
>
|
);
|
||||||
Личный кабинет
|
|
||||||
</Button>
|
|
||||||
</SectionWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
type Notification = {
|
type Notification = {
|
||||||
text: string;
|
text: string;
|
||||||
date: string;
|
date: string;
|
||||||
|
url: string;
|
||||||
watched?: boolean;
|
watched?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,58 +61,68 @@ export const NotificationsModal = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<List sx={{ width: "100%", padding: "5px" }}>
|
<List sx={{ width: "100%", padding: "5px" }}>
|
||||||
{notifications.map(({ text, date, watched = true }) => (
|
{notifications.map(({ text, date, url, watched = true }) => (
|
||||||
<ListItem
|
<Link
|
||||||
sx={{
|
to={url}
|
||||||
display: "flex",
|
onClick={() => setOpen(false)}
|
||||||
alignItems: isMobile ? "normal" : "center",
|
style={{
|
||||||
justifyContent: "space-between",
|
textDecoration: "none",
|
||||||
flexDirection: isMobile ? "column-reverse" : "unset",
|
color: "inherit",
|
||||||
borderBottom: "1px solid #F2F3F7",
|
|
||||||
padding: "20px 10px",
|
|
||||||
background: watched ? "none" : "#F0F0F6",
|
|
||||||
borderRadius: watched ? "0" : "8px",
|
|
||||||
"&:first-child": {
|
|
||||||
borderTop: "1px solid #F2F3F7",
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<ListItem
|
||||||
|
key={text + date}
|
||||||
sx={{
|
sx={{
|
||||||
position: "relative",
|
display: "flex",
|
||||||
fontSize: "16px",
|
alignItems: isMobile ? "normal" : "center",
|
||||||
lineHeight: "19px",
|
justifyContent: "space-between",
|
||||||
paddingLeft: watched ? "0" : "35px",
|
flexDirection: isMobile ? "column-reverse" : "unset",
|
||||||
fontWeight: watched ? "normal" : "bold",
|
borderBottom: "1px solid #F2F3F7",
|
||||||
"&::before": {
|
padding: "20px 10px",
|
||||||
content: '""',
|
background: watched ? "none" : "#F0F0F6",
|
||||||
display: watched ? "none" : "block",
|
borderRadius: watched ? "0" : "8px",
|
||||||
position: "absolute",
|
"&:first-child": {
|
||||||
left: "10px",
|
borderTop: "1px solid #F2F3F7",
|
||||||
top: isMobile ? "5px" : "50%",
|
|
||||||
transform: isMobile ? "none" : "translateY(-50%)",
|
|
||||||
height: "8px",
|
|
||||||
width: "8px",
|
|
||||||
borderRadius: "50%",
|
|
||||||
background: "#7E2AEA",
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{text}
|
<Typography
|
||||||
</Typography>
|
sx={{
|
||||||
<Typography
|
position: "relative",
|
||||||
sx={{
|
fontSize: "16px",
|
||||||
fontSize: "14px",
|
lineHeight: "19px",
|
||||||
lineHeight: "19px",
|
paddingLeft: watched ? "0" : "35px",
|
||||||
color: "#9A9AAF",
|
fontWeight: watched ? "normal" : "bold",
|
||||||
fontWeight: watched ? "normal" : "bold",
|
"&::before": {
|
||||||
paddingLeft: isMobile ? (watched ? "0" : "35px") : "0",
|
content: '""',
|
||||||
marginBottom: isMobile ? "5px" : "0",
|
display: watched ? "none" : "block",
|
||||||
}}
|
position: "absolute",
|
||||||
>
|
left: "10px",
|
||||||
{date}
|
top: isMobile ? "5px" : "50%",
|
||||||
</Typography>
|
transform: isMobile ? "none" : "translateY(-50%)",
|
||||||
</ListItem>
|
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>
|
</List>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -39,7 +39,7 @@ export const Select = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
zIndex: 1500,
|
zIndex: 1,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "56px",
|
height: "56px",
|
||||||
@ -81,6 +81,7 @@ export const Select = ({
|
|||||||
<MuiSelect
|
<MuiSelect
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="select"
|
className="select"
|
||||||
|
value=""
|
||||||
open={opened}
|
open={opened}
|
||||||
MenuProps={{ disablePortal: true }}
|
MenuProps={{ disablePortal: true }}
|
||||||
sx={{ width: "100%" }}
|
sx={{ width: "100%" }}
|
||||||
|
@ -26,3 +26,7 @@
|
|||||||
.MuiInputBase-root.MuiOutlinedInput-root .MuiSelect-icon {
|
.MuiInputBase-root.MuiOutlinedInput-root .MuiSelect-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MuiMenu-root.MuiModal-root {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
@ -22,9 +22,7 @@ interface Props {
|
|||||||
color?: string;
|
color?: string;
|
||||||
FormInputSx?: SxProps<Theme>;
|
FormInputSx?: SxProps<Theme>;
|
||||||
TextfieldProps: TextFieldProps;
|
TextfieldProps: TextFieldProps;
|
||||||
onChange: (
|
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
||||||
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PasswordInput({
|
export default function PasswordInput({
|
||||||
@ -46,17 +44,13 @@ export default function PasswordInput({
|
|||||||
: { ...theme.typography.body1, fontWeight: 500 }
|
: { ...theme.typography.body1, fontWeight: 500 }
|
||||||
: theme.typography.body2;
|
: theme.typography.body2;
|
||||||
|
|
||||||
const placeholderFont = upMd
|
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
|
||||||
? undefined
|
|
||||||
: { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
|
|
||||||
|
|
||||||
const [showPassword, setShowPassword] = React.useState(false);
|
const [showPassword, setShowPassword] = React.useState(false);
|
||||||
|
|
||||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||||
|
|
||||||
const handleMouseDownPassword = (
|
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
event: React.MouseEvent<HTMLButtonElement>
|
|
||||||
) => {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ export default function Landing({ templaterOnly = false }: Props) {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
paddingTop: "80px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Navbar isLoggedIn={false} />
|
<Navbar isLoggedIn={false} />
|
||||||
|
@ -46,6 +46,10 @@
|
|||||||
right: -5px;
|
right: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slider .slick-arrow::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.slider .slick-dots {
|
.slider .slick-dots {
|
||||||
bottom: -15px;
|
bottom: -15px;
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,23 @@ import "slick-carousel/slick/slick-theme.css";
|
|||||||
import "./slider.css";
|
import "./slider.css";
|
||||||
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import type { CustomArrowProps } from "react-slick";
|
||||||
|
|
||||||
type SliderProps = {
|
type SliderProps = {
|
||||||
items: ReactNode[];
|
items: ReactNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ArrowProps = CustomArrowProps & {
|
||||||
|
icon: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
export const Slider = ({ items }: SliderProps) => {
|
export const Slider = ({ items }: SliderProps) => {
|
||||||
const [range, setRange] = useState<number>(3);
|
const [range, setRange] = useState<number>(3);
|
||||||
const [activeRange, setActiveRange] = useState<number[]>([0, 1, 2]);
|
const [activeRange, setActiveRange] = useState<number[]>([0, 1, 2]);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMiddle = useMediaQuery(
|
const isMiddle = useMediaQuery(theme.breakpoints.down(1200));
|
||||||
theme.breakpoints.down(1200) && theme.breakpoints.up(830)
|
|
||||||
);
|
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(830));
|
const isTablet = useMediaQuery(theme.breakpoints.down(830));
|
||||||
|
const isMobileHeader = useMediaQuery(theme.breakpoints.down(900));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isTablet) {
|
if (isTablet) {
|
||||||
@ -64,10 +68,14 @@ export const Slider = ({ items }: SliderProps) => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Arrow = ({ currentSlide, slideCount, icon, ...props }: ArrowProps) => (
|
||||||
|
<Box {...props}>{icon}</Box>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="slider-wrapper">
|
<Box className="slider-wrapper">
|
||||||
{(items.length < 4 && !isMiddle && !isTablet) ||
|
{(items.length < 4 && !isMiddle && !isTablet) ||
|
||||||
(items.length < 3 && isMiddle) ||
|
(items.length < 3 && isMiddle && !isTablet) ||
|
||||||
(items.length < 1 && isTablet) ? (
|
(items.length < 1 && isTablet) ? (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -87,14 +95,15 @@ export const Slider = ({ items }: SliderProps) => {
|
|||||||
dots
|
dots
|
||||||
infinite
|
infinite
|
||||||
variableWidth
|
variableWidth
|
||||||
|
lazyLoad={isMobileHeader ? "progressive" : undefined}
|
||||||
slidesToShow={range}
|
slidesToShow={range}
|
||||||
prevArrow={<img src={arrowLeftIcon} alt="prev" />}
|
prevArrow={<Arrow icon={<img src={arrowLeftIcon} alt="prev" />} />}
|
||||||
nextArrow={<img src={arrowRightIcon} alt="next" />}
|
nextArrow={<Arrow icon={<img src={arrowRightIcon} alt="next" />} />}
|
||||||
beforeChange={(_, active) =>
|
beforeChange={(_, active) =>
|
||||||
setActiveRange(calculateRange(active, items.length))
|
setActiveRange(calculateRange(active, items.length))
|
||||||
}
|
}
|
||||||
customPaging={(slideNumber) => (
|
customPaging={(slideNumber) => (
|
||||||
<li
|
<Box
|
||||||
className={classNames("dot", {
|
className={classNames("dot", {
|
||||||
active: activeRange.includes(slideNumber),
|
active: activeRange.includes(slideNumber),
|
||||||
})}
|
})}
|
||||||
|
@ -76,39 +76,39 @@ export default function SigninDialog() {
|
|||||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isDialogOpen}
|
open={isDialogOpen}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
width: "600px",
|
width: "600px",
|
||||||
maxWidth: "600px",
|
maxWidth: "600px",
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
backdrop: {
|
backdrop: {
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
noValidate
|
noValidate
|
||||||
sx={{
|
sx={{
|
||||||
position: "relative",
|
position: "relative",
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
p: upMd ? "50px" : "18px",
|
p: upMd ? "50px" : "18px",
|
||||||
pb: upMd ? "40px" : "30px",
|
pb: upMd ? "40px" : "30px",
|
||||||
gap: "15px",
|
gap: "15px",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow: cardShadow,
|
boxShadow: cardShadow,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -16,193 +16,203 @@ import { makeRequest } from "@frontend/kitui";
|
|||||||
import { cardShadow } from "@root/utils/themes/shadow";
|
import { cardShadow } from "@root/utils/themes/shadow";
|
||||||
import PasswordInput from "@root/components/passwordInput";
|
import PasswordInput from "@root/components/passwordInput";
|
||||||
|
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
login: string;
|
login: string;
|
||||||
password: string;
|
password: string;
|
||||||
repeatPassword: string;
|
repeatPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: Values = {
|
const initialValues: Values = {
|
||||||
login: "",
|
login: "",
|
||||||
password: "",
|
password: "",
|
||||||
repeatPassword: "",
|
repeatPassword: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
login: string().required("Поле обязательно"),
|
login: string().required("Поле обязательно"),
|
||||||
password: string().min(8, "Минимум 8 символов").matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы").required("Поле обязательно"),
|
password: string()
|
||||||
repeatPassword: string().oneOf([ref("password"), undefined], "Пароли не совпадают"),
|
.min(8, "Минимум 8 символов")
|
||||||
|
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
||||||
|
.required("Поле обязательно"),
|
||||||
|
repeatPassword: string()
|
||||||
|
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
||||||
|
.required("Повторите пароль"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function SignupDialog() {
|
export default function SignupDialog() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||||
const user = useUserStore(state => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const formik = useFormik<Values>({
|
const formik = useFormik<Values>({
|
||||||
initialValues,
|
initialValues,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit: (values, formikHelpers) => {
|
onSubmit: (values, formikHelpers) => {
|
||||||
makeRequest<RegisterRequest, RegisterResponse>({
|
makeRequest<RegisterRequest, RegisterResponse>({
|
||||||
url: "https://hub.pena.digital/auth/register",
|
url: "https://hub.pena.digital/auth/register",
|
||||||
body: {
|
body: {
|
||||||
login: values.login.trim(),
|
login: values.login.trim(),
|
||||||
password: values.password.trim(),
|
password: values.password.trim(),
|
||||||
phoneNumber: "+7",
|
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);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
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() {
|
useEffect(
|
||||||
if (user) navigate("/tariffs", { replace: true });
|
function redirectIfSignedIn() {
|
||||||
}, [navigate, user]);
|
if (user) navigate("/tariffs", { replace: true });
|
||||||
|
},
|
||||||
|
[navigate, user]
|
||||||
|
);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isDialogOpen}
|
open={isDialogOpen}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
width: "600px",
|
width: "600px",
|
||||||
maxWidth: "600px",
|
maxWidth: "600px",
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
backdrop: {
|
backdrop: {
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
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
|
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||||
component="form"
|
</IconButton>
|
||||||
onSubmit={formik.handleSubmit}
|
<Box sx={{ mt: upMd ? undefined : "62px" }}>
|
||||||
noValidate
|
<PenaLogo width={upMd ? 233 : 196} />
|
||||||
sx={{
|
</Box>
|
||||||
position: "relative",
|
<Typography
|
||||||
backgroundColor: "white",
|
sx={{
|
||||||
display: "flex",
|
color: theme.palette.grey3.main,
|
||||||
alignItems: "center",
|
mt: "5px",
|
||||||
flexDirection: "column",
|
mb: upMd ? "35px" : "33px",
|
||||||
p: upMd ? "50px" : "18px",
|
}}
|
||||||
pb: upMd ? "40px" : "30px",
|
>
|
||||||
gap: "15px",
|
Регистрация
|
||||||
borderRadius: "12px",
|
</Typography>
|
||||||
boxShadow: cardShadow,
|
<InputTextfield
|
||||||
}}
|
TextfieldProps={{
|
||||||
>
|
value: formik.values.login,
|
||||||
<IconButton
|
placeholder: "username",
|
||||||
onClick={handleClose}
|
onBlur: formik.handleBlur,
|
||||||
sx={{
|
error: formik.touched.login && Boolean(formik.errors.login),
|
||||||
position: "absolute",
|
helperText: formik.touched.login && formik.errors.login,
|
||||||
right: "7px",
|
}}
|
||||||
top: "7px",
|
onChange={formik.handleChange}
|
||||||
}}
|
color="#F2F3F7"
|
||||||
>
|
id="login"
|
||||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
label="Логин"
|
||||||
</IconButton>
|
gap={upMd ? "10px" : "10px"}
|
||||||
<Box sx={{ mt: upMd ? undefined : "62px" }}>
|
/>
|
||||||
<PenaLogo width={upMd ? 233 : 196} />
|
<PasswordInput
|
||||||
</Box>
|
TextfieldProps={{
|
||||||
<Typography
|
value: formik.values.password,
|
||||||
sx={{
|
placeholder: "Не менее 8 символов",
|
||||||
color: theme.palette.grey3.main,
|
onBlur: formik.handleBlur,
|
||||||
mt: "5px",
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
mb: upMd ? "35px" : "33px",
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
}}
|
autoComplete: "new-password",
|
||||||
>
|
}}
|
||||||
Регистрация
|
onChange={formik.handleChange}
|
||||||
</Typography>
|
color="#F2F3F7"
|
||||||
<InputTextfield
|
id="password"
|
||||||
TextfieldProps={{
|
label="Пароль"
|
||||||
value: formik.values.login,
|
gap={upMd ? "10px" : "10px"}
|
||||||
placeholder: "username",
|
/>
|
||||||
onBlur: formik.handleBlur,
|
<PasswordInput
|
||||||
error: formik.touched.login && Boolean(formik.errors.login),
|
TextfieldProps={{
|
||||||
helperText: formik.touched.login && formik.errors.login,
|
value: formik.values.repeatPassword,
|
||||||
}}
|
placeholder: "Не менее 8 символов",
|
||||||
onChange={formik.handleChange}
|
onBlur: formik.handleBlur,
|
||||||
color="#F2F3F7"
|
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
||||||
id="login"
|
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||||
label="Логин"
|
autoComplete: "new-password",
|
||||||
gap={upMd ? "10px" : "10px"}
|
}}
|
||||||
/>
|
onChange={formik.handleChange}
|
||||||
<PasswordInput
|
color="#F2F3F7"
|
||||||
TextfieldProps={{
|
id="repeatPassword"
|
||||||
value: formik.values.password,
|
label="Повторить пароль"
|
||||||
placeholder: "Не менее 8 символов",
|
gap={upMd ? "10px" : "10px"}
|
||||||
onBlur: formik.handleBlur,
|
/>
|
||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
<CustomButton
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
fullWidth
|
||||||
autoComplete: "new-password"
|
variant="contained"
|
||||||
}}
|
sx={{
|
||||||
onChange={formik.handleChange}
|
backgroundColor: theme.palette.brightPurple.main,
|
||||||
color="#F2F3F7"
|
textColor: "white",
|
||||||
id="password"
|
width: "100%",
|
||||||
label="Пароль"
|
py: "12px",
|
||||||
gap={upMd ? "10px" : "10px"}
|
mt: upMd ? undefined : "10px",
|
||||||
/>
|
}}
|
||||||
<PasswordInput
|
type="submit"
|
||||||
TextfieldProps={{
|
disabled={formik.isSubmitting}
|
||||||
value: formik.values.repeatPassword,
|
>
|
||||||
placeholder: "Не менее 8 символов",
|
Зарегистрироваться
|
||||||
onBlur: formik.handleBlur,
|
</CustomButton>
|
||||||
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
<Link
|
||||||
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
component={RouterLink}
|
||||||
autoComplete: "new-password"
|
to="/signin"
|
||||||
}}
|
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||||
onChange={formik.handleChange}
|
sx={{
|
||||||
color="#F2F3F7"
|
color: theme.palette.brightPurple.main,
|
||||||
id="repeatPassword"
|
mt: "auto",
|
||||||
label="Повторить пароль"
|
}}
|
||||||
gap={upMd ? "10px" : "10px"}
|
>
|
||||||
/>
|
Вход в личный кабинет
|
||||||
<CustomButton
|
</Link>
|
||||||
fullWidth
|
</Box>
|
||||||
variant="contained"
|
</Dialog>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -655,7 +655,7 @@ const templategenTariff1: Tariff = {
|
|||||||
price: 0,
|
price: 0,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
privilegies: [
|
privilegies: [
|
||||||
{
|
{
|
||||||
_id: "p1",
|
_id: "p1",
|
||||||
name: "n1",
|
name: "n1",
|
||||||
privilegeId: "p1",
|
privilegeId: "p1",
|
||||||
@ -678,7 +678,7 @@ const templategenTariff2: Tariff = {
|
|||||||
price: 0,
|
price: 0,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
privilegies: [
|
privilegies: [
|
||||||
{
|
{
|
||||||
_id: "p5",
|
_id: "p5",
|
||||||
name: "n5",
|
name: "n5",
|
||||||
privilegeId: "p5",
|
privilegeId: "p5",
|
||||||
|
@ -1,8 +1 @@
|
|||||||
export const cardShadow = `
|
export const cardShadow = "0px 15px 80px rgb(210 208 225 / 70%)";
|
||||||
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)
|
|
||||||
`;
|
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.extend.json",
|
"extends": "./tsconfig.extend.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["es5", "dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
"types": ["node"],
|
||||||
"dom.iterable",
|
"allowJs": true,
|
||||||
"esnext"
|
"skipLibCheck": true,
|
||||||
],
|
"esModuleInterop": true,
|
||||||
"allowJs": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"strict": true,
|
"module": "esnext",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"moduleResolution": "node",
|
||||||
"noFallthroughCasesInSwitch": true,
|
"resolveJsonModule": true,
|
||||||
"module": "esnext",
|
"isolatedModules": true,
|
||||||
"moduleResolution": "node",
|
"noEmit": true,
|
||||||
"resolveJsonModule": true,
|
"jsx": "react-jsx"
|
||||||
"isolatedModules": true,
|
},
|
||||||
"noEmit": true,
|
"include": ["src", "**/*.ts"]
|
||||||
"jsx": "react-jsx"
|
}
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user