аbsolute import(components, utils), remove App.tsx
This commit is contained in:
parent
07677825c6
commit
e681c52bce
132
src/App.tsx
132
src/App.tsx
@ -1,132 +0,0 @@
|
||||
import { CssBaseline, Divider, ThemeProvider, useMediaQuery } from "@mui/material";
|
||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
|
||||
import Landing from "./pages/Landing/Landing";
|
||||
import Signin from "./pages/Signin";
|
||||
import Signup from "./pages/Signup";
|
||||
import Tariffs from "./pages/Tariffs/Tariffs";
|
||||
import { TariffsTime } from "./pages/Tariffs/TariffsTime";
|
||||
import Faq from "./pages/Faq/Faq";
|
||||
import Wallet from "./pages/Wallet";
|
||||
import Payment from "./pages/Payment/Payment";
|
||||
import Support from "./pages/Support/Support";
|
||||
import CustomTariff from "./pages/CustomTariff/CustomTariff";
|
||||
import { TariffsVolume } from "./pages/Tariffs/TariffsVolume";
|
||||
import { AccountSetup } from "./pages/AccountSetup";
|
||||
|
||||
import Footer from "./components/Footer";
|
||||
import Navbar from "./components/Navbar/Navbar";
|
||||
|
||||
import darkTheme from "./utils/themes/dark";
|
||||
import lightTheme from "./utils/themes/light";
|
||||
|
||||
import { SnackbarProvider } from "notistack";
|
||||
|
||||
function App() {
|
||||
const upMd = useMediaQuery(lightTheme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<SnackbarProvider>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CssBaseline />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Navbar isLoggedIn={false} isCollapsed={!upMd} />
|
||||
<Divider sx={{ bgcolor: "#E3E3E3", borderColor: "#00000000" }} />
|
||||
<Landing />
|
||||
<Footer />
|
||||
</ThemeProvider>
|
||||
}
|
||||
/>
|
||||
<Route path="/signin" element={<Signin />} />
|
||||
<Route path="/signup" element={<Signup />} />
|
||||
|
||||
<Route
|
||||
path="tariffs/*"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Tariffs />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Route path="time" element={<TariffsTime />} />
|
||||
<Route path="volume" element={<TariffsVolume />} />
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path="/faq"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Faq />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/wallet"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Wallet />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/payment"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Payment />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/support"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Support />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/support/:ticketId"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Support />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/tariffconstructor"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<CustomTariff />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<AccountSetup />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</SnackbarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
141
src/index.tsx
141
src/index.tsx
@ -1,16 +1,143 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import { CssBaseline, Divider, ThemeProvider, useMediaQuery } from "@mui/material";
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById("root") as HTMLElement
|
||||
);
|
||||
import Faq from "./pages/Faq/Faq";
|
||||
import Wallet from "./pages/Wallet";
|
||||
import Payment from "./pages/Payment/Payment";
|
||||
import Support from "./pages/Support/Support";
|
||||
import CustomTariff from "./pages/CustomTariff/CustomTariff";
|
||||
import { TariffsVolume } from "./pages/Tariffs/TariffsVolume";
|
||||
import { AccountSetup } from "./pages/AccountSetup";
|
||||
import Landing from "./pages/Landing/Landing";
|
||||
import Tariffs from "./pages/Tariffs/Tariffs";
|
||||
import { TariffsTime } from "./pages/Tariffs/TariffsTime";
|
||||
import Signin from "./pages/Signin";
|
||||
import Signup from "./pages/Signup";
|
||||
|
||||
import Footer from "@components/Footer";
|
||||
import Navbar from "@components/Navbar/Navbar";
|
||||
|
||||
import darkTheme from "@utils/themes/dark";
|
||||
import lightTheme from "@utils/themes/light";
|
||||
|
||||
import { SnackbarProvider } from "notistack";
|
||||
|
||||
export const App = () => {
|
||||
const upMd = useMediaQuery(lightTheme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<SnackbarProvider>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CssBaseline />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Navbar isLoggedIn={false} isCollapsed={!upMd} />
|
||||
<Divider sx={{ bgcolor: "#E3E3E3", borderColor: "#00000000" }} />
|
||||
<Landing />
|
||||
<Footer />
|
||||
</ThemeProvider>
|
||||
}
|
||||
/>
|
||||
<Route path="/signin" element={<Signin />} />
|
||||
<Route path="/signup" element={<Signup />} />
|
||||
|
||||
<Route
|
||||
path="tariffs/*"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Tariffs />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Route path="time" element={<TariffsTime />} />
|
||||
<Route path="volume" element={<TariffsVolume />} />
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path="/faq"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Faq />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/wallet"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Wallet />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/payment"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Payment />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/support"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Support />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/support/:ticketId"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<Support />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/tariffconstructor"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<CustomTariff />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<>
|
||||
<Navbar isLoggedIn={true} isCollapsed={!upMd} />
|
||||
<AccountSetup />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</SnackbarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Box, Button, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "@root/components/CustomButton";
|
||||
import InputTextfield from "@root/components/InputTextfield";
|
||||
import SectionWrapper from "@root/components/SectionWrapper";
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import InputTextfield from "@components/InputTextfield";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
|
||||
import Download from "../assets/Icons/Download.svg";
|
||||
import Account from "../assets/Icons/Account.svg";
|
||||
|
@ -1,34 +1,33 @@
|
||||
import { Box } from "@mui/material";
|
||||
import CustomAccordionBasket from "../../components/CustomAccordionBasket";
|
||||
|
||||
import CustomAccordionBasket from "@components/CustomAccordionBasket";
|
||||
|
||||
interface Props {
|
||||
content:{title:string, data:[string,number][]}[]
|
||||
content: { title: string; data: [string, number][] }[];
|
||||
}
|
||||
|
||||
export default function AccordionWrapperBasket({content}:Props) {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
export default function AccordionWrapperBasket({ content }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `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)`,
|
||||
}}
|
||||
>
|
||||
{content.map((accordionItem, index) => (
|
||||
<CustomAccordionBasket
|
||||
key={index}
|
||||
header={accordionItem.title}
|
||||
dataSection={accordionItem.data}
|
||||
totalPrice={3920}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{content.map((accordionItem, index) => (
|
||||
<CustomAccordionBasket
|
||||
key={index}
|
||||
header={accordionItem.title}
|
||||
dataSection={accordionItem.data}
|
||||
totalPrice={3920}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,112 +1,101 @@
|
||||
import { Box, IconButton, Tabs, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import ComplexNavText from "../../components/ComplexNavText";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import { useState } from "react";
|
||||
import AccordionWrapperBasket from "./AccordionWrapper";
|
||||
import TotalPrice from "../../components/TotalPrice";
|
||||
|
||||
import ComplexNavText from "@components/ComplexNavText";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import TotalPrice from "@components/TotalPrice";
|
||||
import AccordionWrapperBasket from "./AccordionWrapper";
|
||||
|
||||
interface TabPanelProps {
|
||||
index: number;
|
||||
value: number;
|
||||
children?: React.ReactNode;
|
||||
mt: string;
|
||||
index: number;
|
||||
value: number;
|
||||
children?: React.ReactNode;
|
||||
mt: string;
|
||||
}
|
||||
|
||||
function TabPanel({ index, value, children, mt }: TabPanelProps) {
|
||||
return (
|
||||
<Box
|
||||
hidden={index !== value}
|
||||
sx={{ mt }}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
return (
|
||||
<Box hidden={index !== value} sx={{ mt }}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const contentBasket = [
|
||||
{
|
||||
title:"Шаблонизатор",
|
||||
data:[
|
||||
{
|
||||
title: "Шаблонизатор",
|
||||
data: [
|
||||
["Дисковое хранилище 5 гб", 390],
|
||||
["Подписка на месяц ", 290],
|
||||
["200 бесплатных генераций", 590],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Квиз конструктор",
|
||||
data: [
|
||||
["Дисковое хранилище 5 гб", 200],
|
||||
["Подписка на месяц ", 300],
|
||||
["200 бесплатных генераций", 1000],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function BasketPage() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [tabIndex, setTabIndex] = useState<number>(0);
|
||||
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setTabIndex(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: upMd ? "25px" : "20px",
|
||||
mb: upMd ? "70px" : "37px",
|
||||
}}
|
||||
>
|
||||
{upMd && <ComplexNavText text1="Все тарифы — " text2="Корзина" />}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: upMd ? "40px" : "20px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd && (
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h4">Вопросы и ответы</Typography>
|
||||
</Box>
|
||||
<TabPanel value={tabIndex} index={0} mt={upMd ? "27px" : "10px"}>
|
||||
<AccordionWrapperBasket
|
||||
content={[
|
||||
{
|
||||
title: "Шаблонизатор",
|
||||
data: [
|
||||
["Дисковое хранилище 5 гб", 390],
|
||||
["Подписка на месяц ", 290],
|
||||
["200 бесплатных генераций", 590]
|
||||
]
|
||||
},
|
||||
{
|
||||
title:"Квиз конструктор",
|
||||
data:[
|
||||
["200 бесплатных генераций", 590],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Квиз конструктор",
|
||||
data: [
|
||||
["Дисковое хранилище 5 гб", 200],
|
||||
["Подписка на месяц ", 300],
|
||||
["200 бесплатных генераций", 1000],
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
export default function BasketPage() {
|
||||
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [tabIndex, setTabIndex] = useState<number>(0);
|
||||
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setTabIndex(newValue);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: upMd ? "25px" : "20px",
|
||||
mb: upMd ? "70px" : "37px",
|
||||
}}
|
||||
>
|
||||
{upMd &&
|
||||
<ComplexNavText text1="Все тарифы — " text2="Корзина" />
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: upMd ? "40px" : "20px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd &&
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
}
|
||||
<Typography variant="h4">Вопросы и ответы</Typography>
|
||||
</Box>
|
||||
<TabPanel value={tabIndex} index={0} mt={upMd ? "27px" : "10px"}>
|
||||
<AccordionWrapperBasket
|
||||
content={[
|
||||
{
|
||||
title:"Шаблонизатор",
|
||||
data:[
|
||||
["Дисковое хранилище 5 гб", 390],
|
||||
["Подписка на месяц ", 290],
|
||||
["200 бесплатных генераций", 590]
|
||||
]
|
||||
},
|
||||
{
|
||||
title:"Квиз конструктор",
|
||||
data:[
|
||||
["Дисковое хранилище 5 гб", 200],
|
||||
["Подписка на месяц ", 300],
|
||||
["200 бесплатных генераций", 1000],
|
||||
|
||||
]
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TotalPrice/>
|
||||
</SectionWrapper >
|
||||
);
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TotalPrice />
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,127 +1,129 @@
|
||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import TariffConstructorCard from "./TariffConstructorCard";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import ComplexNavText from "../../components/ComplexNavText";
|
||||
import ComplexHeader from "../../components/ComplexHeader";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import TariffConstructorCard from "./TariffConstructorCard";
|
||||
import ComplexNavText from "@components/ComplexNavText";
|
||||
import ComplexHeader from "@components/ComplexHeader";
|
||||
|
||||
export default function TariffConstructor() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: upMd ? "25px" : "20px",
|
||||
mb: upMd ? "93px" : "48px",
|
||||
}}
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: upMd ? "25px" : "20px",
|
||||
mb: upMd ? "93px" : "48px",
|
||||
}}
|
||||
>
|
||||
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd && (
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<ComplexHeader text1="Кастомный тариф " text2="Шаблонизатор" />
|
||||
</Box>
|
||||
<TariffConstructorCard
|
||||
time="9 месяцев"
|
||||
quantity="1000 шаблонов"
|
||||
discountText="-60%"
|
||||
totalText="3 190 руб."
|
||||
totalWithoutDiscountText="10 190 руб."
|
||||
/>
|
||||
<ComplexHeader sx={{ mt: upMd ? "80px" : "70px", mb: "40px" }} text1="Кастомный тариф " text2="Опросник" />
|
||||
<TariffConstructorCard
|
||||
time="9 месяцев"
|
||||
quantity="1000 шаблонов"
|
||||
discountText="-60%"
|
||||
totalText="3 190 руб."
|
||||
totalWithoutDiscountText="10 190 руб."
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
mt: upMd ? "80px" : "70px",
|
||||
pt: upMd ? "30px" : undefined,
|
||||
borderTop: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: upMd ? "68.5%" : undefined,
|
||||
pr: upMd ? "15%" : undefined,
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{upMd &&
|
||||
<ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
<Typography variant="h4" mb={upMd ? "18px" : "30px"}>
|
||||
Итоговая цена
|
||||
</Typography>
|
||||
<Typography color={theme.palette.grey3.main}>
|
||||
Текст-заполнитель — это текст, который имеет Текст-заполнитель — это текст, который имеет Текст-заполнитель
|
||||
— это текст, который имеет Текст-заполнитель — это текст, который имеет Текст-заполнитель
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
width: upMd ? "31.5%" : undefined,
|
||||
pl: upMd ? "33px" : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "column" : "row",
|
||||
alignItems: upMd ? "start" : "center",
|
||||
mt: upMd ? "10px" : "30px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color={theme.palette.orange.main}
|
||||
sx={{
|
||||
textDecoration: "line-through",
|
||||
order: upMd ? 1 : 2,
|
||||
}}
|
||||
>
|
||||
{!upMd &&
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
}
|
||||
<ComplexHeader text1="Кастомный тариф " text2="Шаблонизатор" />
|
||||
</Box>
|
||||
<TariffConstructorCard
|
||||
time="9 месяцев"
|
||||
quantity="1000 шаблонов"
|
||||
discountText="-60%"
|
||||
totalText="3 190 руб."
|
||||
totalWithoutDiscountText="10 190 руб."
|
||||
/>
|
||||
<ComplexHeader sx={{ mt: upMd ? "80px" : "70px", mb: "40px" }} text1="Кастомный тариф " text2="Опросник" />
|
||||
<TariffConstructorCard
|
||||
time="9 месяцев"
|
||||
quantity="1000 шаблонов"
|
||||
discountText="-60%"
|
||||
totalText="3 190 руб."
|
||||
totalWithoutDiscountText="10 190 руб."
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
mt: upMd ? "80px" : "70px",
|
||||
pt: upMd ? "30px" : undefined,
|
||||
borderTop: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
|
||||
}}
|
||||
20 190 руб.
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="p1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "26px",
|
||||
lineHeight: "31px",
|
||||
order: upMd ? 2 : 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: upMd ? "68.5%" : undefined,
|
||||
pr: upMd ? "15%" : undefined,
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
mb={upMd ? "18px" : "30px"}
|
||||
>Итоговая цена</Typography>
|
||||
<Typography color={theme.palette.grey3.main}>Текст-заполнитель —
|
||||
это текст, который имеет Текст-заполнитель —
|
||||
это текст, который имеет Текст-заполнитель —
|
||||
это текст, который имеет Текст-заполнитель —
|
||||
это текст, который имеет Текст-заполнитель</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
width: upMd ? "31.5%" : undefined,
|
||||
pl: upMd ? "33px" : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "column" : "row",
|
||||
alignItems: upMd ? "start" : "center",
|
||||
mt: upMd ? "10px" : "30px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color={theme.palette.orange.main}
|
||||
sx={{
|
||||
textDecoration: "line-through",
|
||||
order: upMd ? 1 : 2,
|
||||
}}
|
||||
>20 190 руб.</Typography>
|
||||
<Typography
|
||||
variant="p1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "26px",
|
||||
lineHeight: "31px",
|
||||
order: upMd ? 2 : 1,
|
||||
}}
|
||||
>6 380 руб.</Typography>
|
||||
</Box>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
sx={{
|
||||
mt: "25px",
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>Выбрать</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionWrapper >
|
||||
);
|
||||
}
|
||||
6 380 руб.
|
||||
</Typography>
|
||||
</Box>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
sx={{
|
||||
mt: "25px",
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
Выбрать
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,33 +1,28 @@
|
||||
import { Box } from "@mui/material";
|
||||
import CustomAccordion from "../../components/CustomAccordion";
|
||||
|
||||
import CustomAccordion from "@components/CustomAccordion";
|
||||
|
||||
interface Props {
|
||||
content: [string, string][];
|
||||
content: [string, string][];
|
||||
}
|
||||
|
||||
export default function AccordionWrapper({ content }: Props) {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `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)`,
|
||||
}}
|
||||
>
|
||||
{content.map((accordionItem, index) => (
|
||||
<CustomAccordion
|
||||
key={index}
|
||||
header={accordionItem[0]}
|
||||
text={accordionItem[1]}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{content.map((accordionItem, index) => (
|
||||
<CustomAccordion key={index} header={accordionItem[0]} text={accordionItem[1]} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,99 +1,102 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import PenaLogo from "../../components/PenaLogo";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import PenaLogo from "@components/PenaLogo";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
|
||||
import mainShapeVideo from "../../assets/animations/main.webm";
|
||||
import previewMain from "../../assets/animations/preview_main.png";
|
||||
|
||||
|
||||
export default function Section1() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.lightPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
pt: upMd ? "70px" : "20px",
|
||||
pb: "70px",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
}}
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.lightPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
pt: upMd ? "70px" : "20px",
|
||||
pb: "70px",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexBasis: upMd ? "310px" : undefined,
|
||||
gap: "70px",
|
||||
order: upMd ? 1 : 2,
|
||||
mb: upMd ? undefined : "30px",
|
||||
}}
|
||||
>
|
||||
{upMd && <PenaLogo width={180} />}
|
||||
<Typography variant="h2">Сервисы прокачки маркетинга</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flexShrink: 1,
|
||||
textAlign: "center",
|
||||
order: upMd ? 2 : 1,
|
||||
mx: upMd ? "30px" : 0,
|
||||
// mt: upMd ? undefined : "-70px",
|
||||
// mb: upMd ? undefined : "-30px",
|
||||
alignSelf: "center",
|
||||
aspectRatio: "1 / 1",
|
||||
width: upMd ? undefined : "100%",
|
||||
maxWidth: "301px",
|
||||
maxHeight: "301px",
|
||||
}}
|
||||
>
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
poster={previewMain}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
// transform: upMd ? undefined : "rotate(-90deg)",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexBasis: upMd ? "310px" : undefined,
|
||||
gap: "70px",
|
||||
order: upMd ? 1 : 2,
|
||||
mb: upMd ? undefined : "30px",
|
||||
}}
|
||||
>
|
||||
{upMd && <PenaLogo width={180} />}
|
||||
<Typography variant="h2">Сервисы прокачки маркетинга</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flexShrink: 1,
|
||||
textAlign: "center",
|
||||
order: upMd ? 2 : 1,
|
||||
mx: upMd ? "30px" : 0,
|
||||
// mt: upMd ? undefined : "-70px",
|
||||
// mb: upMd ? undefined : "-30px",
|
||||
alignSelf: "center",
|
||||
aspectRatio: "1 / 1",
|
||||
width: upMd ? undefined : "100%",
|
||||
maxWidth: "301px",
|
||||
maxHeight: "301px",
|
||||
}}
|
||||
>
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
poster={previewMain}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
// transform: upMd ? undefined : "rotate(-90deg)",
|
||||
}}
|
||||
>
|
||||
<source src={mainShapeVideo} type="video/webm" />
|
||||
Your browser doesn't support HTML5 video tag.
|
||||
</video>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexBasis: upMd ? "360px" : undefined,
|
||||
alignItems: "start",
|
||||
alignSelf: upMd ? "center" : "start",
|
||||
mt: upMd ? "70px" : undefined,
|
||||
order: 3,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mb: "11px" }}>
|
||||
<Typography>Покажут эффективность рекламы</Typography>
|
||||
<Typography>Соберут все обращения клиентов</Typography>
|
||||
<Typography>Повысят конверсию сайта</Typography>
|
||||
</Box>
|
||||
<Typography sx={{ mb: "40px" }}>И все это в едином кабинете</Typography>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
color: theme.palette.primary.main,
|
||||
}}
|
||||
>Подробнее</CustomButton>
|
||||
</Box>
|
||||
</SectionWrapper >
|
||||
);
|
||||
};
|
||||
<source src={mainShapeVideo} type="video/webm" />
|
||||
Your browser doesn't support HTML5 video tag.
|
||||
</video>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexBasis: upMd ? "360px" : undefined,
|
||||
alignItems: "start",
|
||||
alignSelf: upMd ? "center" : "start",
|
||||
mt: upMd ? "70px" : undefined,
|
||||
order: 3,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mb: "11px" }}>
|
||||
<Typography>Покажут эффективность рекламы</Typography>
|
||||
<Typography>Соберут все обращения клиентов</Typography>
|
||||
<Typography>Повысят конверсию сайта</Typography>
|
||||
</Box>
|
||||
<Typography sx={{ mb: "40px" }}>И все это в едином кабинете</Typography>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
color: theme.palette.primary.main,
|
||||
}}
|
||||
>
|
||||
Подробнее
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CardWithLink from "../../components/CardWithLink";
|
||||
import UnderlinedLink from "../../components/UnderlinedLink";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
|
||||
import CardWithLink from "@components/CardWithLink";
|
||||
import UnderlinedLink from "@components/UnderlinedLink";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
|
||||
import icon1 from "../../assets/animations/Icon_1.webm";
|
||||
import icon2 from "../../assets/animations/Icon_2.webm";
|
||||
import icon3 from "../../assets/animations/Icon_3.webm";
|
||||
@ -10,110 +12,111 @@ import preview1 from "../../assets/animations/preview_1.png";
|
||||
import preview2 from "../../assets/animations/preview_2.png";
|
||||
import preview3 from "../../assets/animations/preview_3.png";
|
||||
|
||||
|
||||
export default function Section2() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.darkPurple.main,
|
||||
mb: "-90px",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: upMd ? undefined : "center",
|
||||
gap: upMd ? "93px" : "40px",
|
||||
pt: upMd ? "90px" : "50px",
|
||||
pb: "20px",
|
||||
}}
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.darkPurple.main,
|
||||
mb: "-90px",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: upMd ? undefined : "center",
|
||||
gap: upMd ? "93px" : "40px",
|
||||
pt: upMd ? "90px" : "50px",
|
||||
pb: "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: "3.5%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
flexBasis: "31%",
|
||||
maxWidth: "50%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: "3.5%",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
flexBasis: "31%",
|
||||
maxWidth: "50%",
|
||||
}}
|
||||
>Интеграции, избавляющие от рутины</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "30px",
|
||||
alignItems: "start",
|
||||
flexGrow: 1,
|
||||
flexBasis: "65.5%",
|
||||
mt: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
Сервисы помогают предпринимателям, маркетологам и агентствам
|
||||
сделать интернет-маркетинг прозрачным и эффективным. С нами не придется
|
||||
тратить рекламный бюджет впустую и терять клиентов на сайте.
|
||||
</Typography>
|
||||
<UnderlinedLink
|
||||
linkHref="#"
|
||||
text="Подробнее"
|
||||
endIcon={<ArrowForwardIcon sx={{ height: "20px", width: "20px" }} />}
|
||||
sx={{
|
||||
mt: "auto",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: upMd ? "3.5%" : "30px",
|
||||
}}
|
||||
>
|
||||
<CardWithLink
|
||||
shadowType="dark"
|
||||
buttonType="link"
|
||||
height="434px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Шаблонизатор"
|
||||
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={icon1}
|
||||
poster={preview1}
|
||||
/>
|
||||
<CardWithLink
|
||||
shadowType="dark"
|
||||
buttonType="link"
|
||||
height="434px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Опросник"
|
||||
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
|
||||
linkHref="#"
|
||||
video={icon2}
|
||||
poster={preview2}
|
||||
/>
|
||||
<CardWithLink
|
||||
shadowType="dark"
|
||||
buttonType="link"
|
||||
height="434px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Сокращатель ссылок"
|
||||
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
|
||||
linkHref="#"
|
||||
video={icon3}
|
||||
poster={preview3}
|
||||
/>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
Интеграции, избавляющие от рутины
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "30px",
|
||||
alignItems: "start",
|
||||
flexGrow: 1,
|
||||
flexBasis: "65.5%",
|
||||
mt: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
Сервисы помогают предпринимателям, маркетологам и агентствам сделать интернет-маркетинг прозрачным и
|
||||
эффективным. С нами не придется тратить рекламный бюджет впустую и терять клиентов на сайте.
|
||||
</Typography>
|
||||
<UnderlinedLink
|
||||
linkHref="#"
|
||||
text="Подробнее"
|
||||
endIcon={<ArrowForwardIcon sx={{ height: "20px", width: "20px" }} />}
|
||||
sx={{
|
||||
mt: "auto",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: upMd ? "3.5%" : "30px",
|
||||
}}
|
||||
>
|
||||
<CardWithLink
|
||||
shadowType="dark"
|
||||
buttonType="link"
|
||||
height="434px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Шаблонизатор"
|
||||
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={icon1}
|
||||
poster={preview1}
|
||||
/>
|
||||
<CardWithLink
|
||||
shadowType="dark"
|
||||
buttonType="link"
|
||||
height="434px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Опросник"
|
||||
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
|
||||
linkHref="#"
|
||||
video={icon2}
|
||||
poster={preview2}
|
||||
/>
|
||||
<CardWithLink
|
||||
shadowType="dark"
|
||||
buttonType="link"
|
||||
height="434px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Сокращатель ссылок"
|
||||
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
|
||||
linkHref="#"
|
||||
video={icon3}
|
||||
poster={preview3}
|
||||
/>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -6,92 +6,93 @@ import cardPagesBackground3 from "../../assets/card-background/card-pages-backgr
|
||||
import cardPagesBackground4 from "../../assets/card-background/card-pages-background4.png";
|
||||
import cardPagesBackground5 from "../../assets/card-background/card-pages-background5.png";
|
||||
import cardPagesBackground6 from "../../assets/card-background/card-pages-background6.png";
|
||||
import UnderlinedLink from "../../components/UnderlinedLink";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import UnderlinedLink from "@components/UnderlinedLink";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
|
||||
|
||||
export default function Section3() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const downXs = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const downXs = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.lightPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
pt: upMd ? "170px" : "155px",
|
||||
pb: upMd ? "100px" : "70px",
|
||||
width: "fit-content",
|
||||
margin: "auto",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
flexWrap: "wrap",
|
||||
rowGap: upMd ? "58px" : "30px",
|
||||
columnGap: "13.8%",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.lightPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
pt: upMd ? "170px" : "155px",
|
||||
pb: upMd ? "100px" : "70px",
|
||||
width: "fit-content",
|
||||
margin: "auto",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
flexWrap: "wrap",
|
||||
rowGap: upMd ? "58px" : "30px",
|
||||
columnGap: "13.8%",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
maxWidth: "500px",
|
||||
width: upMd ? "43.1%" : undefined,
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
mb: upMd ? "70px" : "30px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
maxWidth: "500px",
|
||||
width: upMd ? "43.1%" : undefined,
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
mb: upMd ? "70px" : "30px",
|
||||
}}
|
||||
>Узнайте, как наши сервисы решают ваши задачи</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
mb: upMd ? "20px" : "30px",
|
||||
}}
|
||||
>
|
||||
<Typography>Покажут эффективность рекламы</Typography>
|
||||
<Typography>Соберут все обращения клиентов</Typography>
|
||||
<Typography>Повысят конверсию сайта</Typography>
|
||||
</Box>
|
||||
<UnderlinedLink
|
||||
linkHref="#"
|
||||
text="Подробнее"
|
||||
endIcon={<ArrowForwardIcon sx={{ height: "20px", width: "20px", display: "inline" }} />}
|
||||
/>
|
||||
</Box>
|
||||
<PromoCard
|
||||
width={upMd ? "43.1%" : undefined}
|
||||
headerText="Общий кабинет"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
|
||||
textOrientation="column"
|
||||
small={downXs}
|
||||
backgroundImage={downXs ? cardPagesBackground4 : cardPagesBackground1}
|
||||
/>
|
||||
<PromoCard
|
||||
width={upMd ? "43.1%" : undefined}
|
||||
headerText="Общий кабинет"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
|
||||
textOrientation="row"
|
||||
small={downXs}
|
||||
backgroundImage={downXs ? cardPagesBackground5 : cardPagesBackground2}
|
||||
/>
|
||||
<PromoCard
|
||||
width={upMd ? "43.1%" : undefined}
|
||||
headerText="Гибкие тарифы"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
|
||||
textOrientation="column"
|
||||
small={downXs}
|
||||
backgroundImage={downXs ? cardPagesBackground6 : cardPagesBackground3}
|
||||
sx={{ mt: upMd ? "102px" : undefined }}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
Узнайте, как наши сервисы решают ваши задачи
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
mb: upMd ? "20px" : "30px",
|
||||
}}
|
||||
>
|
||||
<Typography>Покажут эффективность рекламы</Typography>
|
||||
<Typography>Соберут все обращения клиентов</Typography>
|
||||
<Typography>Повысят конверсию сайта</Typography>
|
||||
</Box>
|
||||
<UnderlinedLink
|
||||
linkHref="#"
|
||||
text="Подробнее"
|
||||
endIcon={<ArrowForwardIcon sx={{ height: "20px", width: "20px", display: "inline" }} />}
|
||||
/>
|
||||
</Box>
|
||||
<PromoCard
|
||||
width={upMd ? "43.1%" : undefined}
|
||||
headerText="Общий кабинет"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
|
||||
textOrientation="column"
|
||||
small={downXs}
|
||||
backgroundImage={downXs ? cardPagesBackground4 : cardPagesBackground1}
|
||||
/>
|
||||
<PromoCard
|
||||
width={upMd ? "43.1%" : undefined}
|
||||
headerText="Общий кабинет"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
|
||||
textOrientation="row"
|
||||
small={downXs}
|
||||
backgroundImage={downXs ? cardPagesBackground5 : cardPagesBackground2}
|
||||
/>
|
||||
<PromoCard
|
||||
width={upMd ? "43.1%" : undefined}
|
||||
headerText="Гибкие тарифы"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
|
||||
textOrientation="column"
|
||||
small={downXs}
|
||||
backgroundImage={downXs ? cardPagesBackground6 : cardPagesBackground3}
|
||||
sx={{ mt: upMd ? "102px" : undefined }}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,59 +1,34 @@
|
||||
import { useMediaQuery, useTheme } from "@mui/material";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import Infographics from "./Infographics";
|
||||
|
||||
|
||||
export default function Section4() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
const itemsFlex = upMd ? "1 0 33.333%" : "1 0 50%";
|
||||
const itemsFlex = upMd ? "1 0 33.333%" : "1 0 50%";
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.darkPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
rowGap: "80px",
|
||||
pt: upMd ? "90px" : "70px",
|
||||
pb: upMd ? "112px" : "76px",
|
||||
}}
|
||||
>
|
||||
<Infographics
|
||||
flex={itemsFlex}
|
||||
bigText="9"
|
||||
text="лет на рынке"
|
||||
/>
|
||||
<Infographics
|
||||
flex={itemsFlex}
|
||||
bigText="18"
|
||||
text="инструментов в едином кабинете"
|
||||
/>
|
||||
<Infographics
|
||||
flex={itemsFlex}
|
||||
bigText="5 467"
|
||||
text="клиентов с нами"
|
||||
/>
|
||||
<Infographics
|
||||
flex={itemsFlex}
|
||||
bigText="15"
|
||||
text="минут на подключение"
|
||||
/>
|
||||
<Infographics
|
||||
flex={itemsFlex}
|
||||
bigText="24/7"
|
||||
text="с вами служба поддержка"
|
||||
/>
|
||||
<Infographics
|
||||
flex={itemsFlex}
|
||||
bigText="1 000"
|
||||
text="рублей в месяц минимальный тариф"
|
||||
/>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.darkPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
rowGap: "80px",
|
||||
pt: upMd ? "90px" : "70px",
|
||||
pb: upMd ? "112px" : "76px",
|
||||
}}
|
||||
>
|
||||
<Infographics flex={itemsFlex} bigText="9" text="лет на рынке" />
|
||||
<Infographics flex={itemsFlex} bigText="18" text="инструментов в едином кабинете" />
|
||||
<Infographics flex={itemsFlex} bigText="5 467" text="клиентов с нами" />
|
||||
<Infographics flex={itemsFlex} bigText="15" text="минут на подключение" />
|
||||
<Infographics flex={itemsFlex} bigText="24/7" text="с вами служба поддержка" />
|
||||
<Infographics flex={itemsFlex} bigText="1 000" text="рублей в месяц минимальный тариф" />
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,59 +1,56 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
|
||||
export default function Section5() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
pt: upMd ? "100px" : "80px",
|
||||
pb: upMd ? "100px" : "80px",
|
||||
}}
|
||||
return (
|
||||
<SectionWrapper
|
||||
component="section"
|
||||
maxWidth="lg"
|
||||
outerContainerSx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
sx={{
|
||||
pt: upMd ? "100px" : "80px",
|
||||
pb: upMd ? "100px" : "80px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplate: upMd ? "auto auto / 1fr 1fr" : "repeat(3, auto) / auto",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" sx={{ mb: upMd ? "62px" : "30px" }}>
|
||||
Остались вопросы?
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
maxWidth: "79.3%",
|
||||
gridRow: upMd ? "span 2" : "",
|
||||
justifySelf: upMd ? "end" : "start",
|
||||
mb: upMd ? undefined : "50px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplate: upMd ? "auto auto / 1fr 1fr" : "repeat(3, auto) / auto",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" sx={{ mb: upMd ? "62px" : "30px" }}>Остались вопросы?</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
maxWidth: "79.3%",
|
||||
gridRow: upMd ? "span 2" : "",
|
||||
justifySelf: upMd ? "end" : "start",
|
||||
mb: upMd ? undefined : "50px",
|
||||
}}
|
||||
>
|
||||
Сервисы помогают предпринимателям, маркетологам и агентствам
|
||||
сделать интернет-маркетинг прозрачным и эффективным. С нами не придется
|
||||
тратить рекламный бюджет впустую и терять клиентов на сайте.
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
alignItems: "start",
|
||||
gap: upMd ? "24px" : "20px",
|
||||
}}
|
||||
>
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
>Подробнее</CustomButton>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
>Подробнее</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionWrapper >
|
||||
);
|
||||
}
|
||||
Сервисы помогают предпринимателям, маркетологам и агентствам сделать интернет-маркетинг прозрачным и
|
||||
эффективным. С нами не придется тратить рекламный бюджет впустую и терять клиентов на сайте.
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
alignItems: "start",
|
||||
gap: upMd ? "24px" : "20px",
|
||||
}}
|
||||
>
|
||||
<CustomButton variant="outlined">Подробнее</CustomButton>
|
||||
<CustomButton variant="contained">Подробнее</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,131 +1,128 @@
|
||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import ComplexNavText from "@components/ComplexNavText";
|
||||
import PaymentMethodCard from "./PaymentMethodCard";
|
||||
|
||||
import mastercardLogo from "../../assets/bank-logo/logo-mastercard.png";
|
||||
import visaLogo from "../../assets/bank-logo/logo-visa.png";
|
||||
import qiwiLogo from "../../assets/bank-logo/logo-qiwi.png";
|
||||
import mirLogo from "../../assets/bank-logo/logo-mir.png";
|
||||
import tinkoffLogo from "../../assets/bank-logo/logo-tinkoff.png";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import ComplexNavText from "../../components/ComplexNavText";
|
||||
|
||||
|
||||
export default function Payment() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: "25px",
|
||||
mb: "70px",
|
||||
}}
|
||||
>
|
||||
{upMd &&
|
||||
<ComplexNavText text1="Все тарифы — " text2="Способ оплаты" />
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd &&
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
}
|
||||
<Typography variant="h4">Способ оплаты</Typography>
|
||||
</Box>
|
||||
{!upMd &&
|
||||
<Typography variant="body2" mb="30px">Выберите способ оплаты</Typography>
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: upMd ? "white" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
borderRadius: "12px",
|
||||
boxShadow: upMd ? `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: "25px",
|
||||
mb: "70px",
|
||||
}}
|
||||
>
|
||||
{upMd && <ComplexNavText text1="Все тарифы — " text2="Способ оплаты" />}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd && (
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h4">Способ оплаты</Typography>
|
||||
</Box>
|
||||
{!upMd && (
|
||||
<Typography variant="body2" mb="30px">
|
||||
Выберите способ оплаты
|
||||
</Typography>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: upMd ? "white" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
borderRadius: "12px",
|
||||
boxShadow: upMd
|
||||
? `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)`
|
||||
:
|
||||
undefined,
|
||||
}}
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: upMd ? "68.5%" : undefined,
|
||||
p: upMd ? "20px" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: upSm ? "row" : "column",
|
||||
flexWrap: "wrap",
|
||||
gap: upMd ? "14px" : "20px",
|
||||
}}
|
||||
>
|
||||
<PaymentMethodCard name="Mastercard" image={mastercardLogo} />
|
||||
<PaymentMethodCard name="Visa" image={visaLogo} />
|
||||
<PaymentMethodCard name="QIWI Кошелек" image={qiwiLogo} />
|
||||
<PaymentMethodCard name="Мир" image={mirLogo} />
|
||||
<PaymentMethodCard name="Тинькофф" image={tinkoffLogo} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "start",
|
||||
color: theme.palette.grey3.main,
|
||||
width: upMd ? "31.5%" : undefined,
|
||||
p: upMd ? "20px" : undefined,
|
||||
pl: upMd ? "33px" : undefined,
|
||||
mt: upMd ? undefined : "30px",
|
||||
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
maxWidth: "85%",
|
||||
}}
|
||||
>
|
||||
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
|
||||
<Typography mb="20px">К оплате</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "20px",
|
||||
lineHeight: "24px",
|
||||
mb: "28px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: upMd ? "68.5%" : undefined,
|
||||
p: upMd ? "20px" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: upSm ? "row" : "column",
|
||||
flexWrap: "wrap",
|
||||
gap: upMd ? "14px" : "20px",
|
||||
}}
|
||||
>
|
||||
<PaymentMethodCard name="Mastercard" image={mastercardLogo} />
|
||||
<PaymentMethodCard name="Visa" image={visaLogo} />
|
||||
<PaymentMethodCard name="QIWI Кошелек" image={qiwiLogo} />
|
||||
<PaymentMethodCard name="Мир" image={mirLogo} />
|
||||
<PaymentMethodCard name="Тинькофф" image={tinkoffLogo} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "start",
|
||||
color: theme.palette.grey3.main,
|
||||
width: upMd ? "31.5%" : undefined,
|
||||
p: upMd ? "20px" : undefined,
|
||||
pl: upMd ? "33px" : undefined,
|
||||
mt: upMd ? undefined : "30px",
|
||||
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
maxWidth: "85%",
|
||||
}}
|
||||
>
|
||||
{upMd &&
|
||||
<Typography mb="56px">
|
||||
Выберите способ оплаты
|
||||
</Typography>
|
||||
}
|
||||
<Typography mb="20px">
|
||||
К оплате
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "20px",
|
||||
lineHeight: "24px",
|
||||
mb: "28px",
|
||||
}}
|
||||
>
|
||||
3 190 руб.
|
||||
</Typography>
|
||||
</Box>
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderColor: theme.palette.brightPurple.main,
|
||||
mt: "auto",
|
||||
}}
|
||||
>Выбрать</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
3 190 руб.
|
||||
</Typography>
|
||||
</Box>
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderColor: theme.palette.brightPurple.main,
|
||||
mt: "auto",
|
||||
}}
|
||||
>
|
||||
Выбрать
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,177 +1,190 @@
|
||||
import { Box, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../components/CustomButton";
|
||||
import InputTextfield from "../components/InputTextfield";
|
||||
import PenaLogo from "../components/PenaLogo";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { apiRequestHandler } from "../utils/api/apiRequestHandler";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useFormik } from "formik";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { ApiError } from "../utils/api/types";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import InputTextfield from "@components/InputTextfield";
|
||||
import PenaLogo from "@components/PenaLogo";
|
||||
|
||||
import { apiRequestHandler } from "@utils/api/apiRequestHandler";
|
||||
|
||||
import { ApiError } from "@utils/api/types";
|
||||
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
interface Values {
|
||||
email: string;
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// TODO
|
||||
function validate(values: Values) {
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.email = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\w-]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
return errors;
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.email = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\w-]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export default function Signin() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const formik = useFormik<Values>({
|
||||
initialValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
},
|
||||
validate,
|
||||
onSubmit: async (values: Values) => {
|
||||
const result = await apiRequestHandler.login({
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
enqueueSnackbar(`Unknown error`);
|
||||
} else {
|
||||
// navigate("/"); // TODO
|
||||
}
|
||||
}
|
||||
});
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const formik = useFormik<Values>({
|
||||
initialValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
},
|
||||
validate,
|
||||
onSubmit: async (values: Values) => {
|
||||
const result = await apiRequestHandler.login({
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
enqueueSnackbar(`Unknown error`);
|
||||
} else {
|
||||
// navigate("/"); // TODO
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
navigate("/");
|
||||
}
|
||||
function handleClose() {
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
height: upMd ? undefined : "100vh",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: upMd ? "600px" : "100%",
|
||||
height: upMd ? "auto" : "100%",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
mt: upMd ? "184px" : undefined,
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
height: upMd ? undefined : "100vh",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: upMd ? "600px" : "100%",
|
||||
height: upMd ? "auto" : "100%",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
mt: upMd ? "184px" : undefined,
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `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)`,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}>
|
||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box sx={{ mt: "65px" }}>
|
||||
<PenaLogo width={upMd ? 233 : 196} />
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "5px",
|
||||
mb: upMd ? "35px" : "33px",
|
||||
}}
|
||||
>Вход в личный кабинет</Typography>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.email,
|
||||
placeholder: "+7 900 000 00 00 или username@penahub.com",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.email && Boolean(formik.errors.email),
|
||||
helperText: formik.touched.email && formik.errors.email,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="email"
|
||||
label="Телефон или E-mail"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
type: "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="password"
|
||||
label="Пароль"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<CustomButton
|
||||
fullWidth
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
textColor: "white",
|
||||
width: "100%",
|
||||
py: "12px",
|
||||
mt: upMd ? undefined : "10px",
|
||||
}}
|
||||
type="submit"
|
||||
disabled={formik.isSubmitting}
|
||||
>Войти</CustomButton>
|
||||
<Link href="#"
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
mb: "30px",
|
||||
mt: upMd ? undefined : "5px",
|
||||
}}
|
||||
>Забыли пароль?</Link>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
mt: "auto",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: theme.palette.brightPurple.main }}>Вы еще не присоединились?</Typography>
|
||||
<Link href="#" sx={{ color: theme.palette.brightPurple.main }}>Регистрация</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box sx={{ mt: "65px" }}>
|
||||
<PenaLogo width={upMd ? 233 : 196} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "5px",
|
||||
mb: upMd ? "35px" : "33px",
|
||||
}}
|
||||
>
|
||||
Вход в личный кабинет
|
||||
</Typography>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.email,
|
||||
placeholder: "+7 900 000 00 00 или username@penahub.com",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.email && Boolean(formik.errors.email),
|
||||
helperText: formik.touched.email && formik.errors.email,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="email"
|
||||
label="Телефон или E-mail"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
type: "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="password"
|
||||
label="Пароль"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<CustomButton
|
||||
fullWidth
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
textColor: "white",
|
||||
width: "100%",
|
||||
py: "12px",
|
||||
mt: upMd ? undefined : "10px",
|
||||
}}
|
||||
type="submit"
|
||||
disabled={formik.isSubmitting}
|
||||
>
|
||||
Войти
|
||||
</CustomButton>
|
||||
<Link
|
||||
href="#"
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
mb: "30px",
|
||||
mt: upMd ? undefined : "5px",
|
||||
}}
|
||||
>
|
||||
Забыли пароль?
|
||||
</Link>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
mt: "auto",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: theme.palette.brightPurple.main }}>Вы еще не присоединились?</Typography>
|
||||
<Link href="#" sx={{ color: theme.palette.brightPurple.main }}>
|
||||
Регистрация
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,221 +1,228 @@
|
||||
import { Box, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../components/CustomButton";
|
||||
import InputTextfield from "../components/InputTextfield";
|
||||
import PenaLogo from "../components/PenaLogo";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { apiRequestHandler } from "../utils/api/apiRequestHandler";
|
||||
import { apiRequestHandler } from "@utils/api/apiRequestHandler";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useFormik } from "formik";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { ApiError } from "../utils/api/types";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import InputTextfield from "@components/InputTextfield";
|
||||
import PenaLogo from "@components/PenaLogo";
|
||||
import { ApiError } from "@utils/api/types";
|
||||
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
interface Values {
|
||||
login: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
login: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
}
|
||||
|
||||
// TODO
|
||||
function validate(values: Values) {
|
||||
const errors = {} as any;
|
||||
if (!values.login) {
|
||||
errors.login = "Required";
|
||||
} else if (!/^[\w-]{3,25}$/i.test(values.login)) {
|
||||
errors.login = "Invalid username";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\w-]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
if (values.password !== values.repeatPassword) {
|
||||
errors.repeatPassword = "Passwords do not match";
|
||||
}
|
||||
return errors;
|
||||
const errors = {} as any;
|
||||
if (!values.login) {
|
||||
errors.login = "Required";
|
||||
} else if (!/^[\w-]{3,25}$/i.test(values.login)) {
|
||||
errors.login = "Invalid username";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\w-]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
if (values.password !== values.repeatPassword) {
|
||||
errors.repeatPassword = "Passwords do not match";
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export default function Signup() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const formik = useFormik<Values>({
|
||||
initialValues: {
|
||||
login: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
},
|
||||
validate,
|
||||
onSubmit: async (values: Values) => {
|
||||
const result = await apiRequestHandler.register({
|
||||
email: values.email,
|
||||
login: values.login,
|
||||
password: values.password,
|
||||
phoneNumber: values.phoneNumber,
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
return;
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
enqueueSnackbar(`Unknown error`);
|
||||
return;
|
||||
} else {
|
||||
// navigate("/signin"); // TODO
|
||||
}
|
||||
}
|
||||
});
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const formik = useFormik<Values>({
|
||||
initialValues: {
|
||||
login: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
},
|
||||
validate,
|
||||
onSubmit: async (values: Values) => {
|
||||
const result = await apiRequestHandler.register({
|
||||
email: values.email,
|
||||
login: values.login,
|
||||
password: values.password,
|
||||
phoneNumber: values.phoneNumber,
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
return;
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
enqueueSnackbar(`Unknown error`);
|
||||
return;
|
||||
} else {
|
||||
// navigate("/signin"); // TODO
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
navigate("/");
|
||||
}
|
||||
function handleClose() {
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
height: upMd ? undefined : "100vh",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: upMd ? "600px" : "100%",
|
||||
height: upMd ? "auto" : "100%",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
mt: upMd ? "79px" : undefined,
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
height: upMd ? undefined : "100vh",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: upMd ? "600px" : "100%",
|
||||
height: upMd ? "auto" : "100%",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
mt: upMd ? "79px" : undefined,
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: `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)`,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box sx={{ mt: "65px" }}>
|
||||
<PenaLogo width={upMd ? 233 : 196} />
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "5px",
|
||||
mb: upMd ? "35px" : "33px",
|
||||
}}
|
||||
>Регистрация</Typography>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.login,
|
||||
placeholder: "username",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.login && Boolean(formik.errors.login),
|
||||
helperText: formik.touched.login && formik.errors.login,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="login"
|
||||
label="Login"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.email,
|
||||
placeholder: "username@penahub.com",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.email && Boolean(formik.errors.email),
|
||||
helperText: formik.touched.email && formik.errors.email,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="email"
|
||||
label="E-mail"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.phoneNumber,
|
||||
placeholder: "+7 900 000 00 00",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber),
|
||||
helperText: formik.touched.phoneNumber && formik.errors.phoneNumber,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="phoneNumber"
|
||||
label="Телефон"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
type: "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="password"
|
||||
label="Пароль"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.repeatPassword,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
||||
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||
type: "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="repeatPassword"
|
||||
label="Повторить пароль"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<CustomButton
|
||||
fullWidth
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
textColor: "white",
|
||||
width: "100%",
|
||||
py: "12px",
|
||||
mt: upMd ? undefined : "10px",
|
||||
}}
|
||||
type="submit"
|
||||
disabled={formik.isSubmitting}
|
||||
>Войти</CustomButton>
|
||||
<Link
|
||||
href="#"
|
||||
sx={{
|
||||
color: theme.palette.brightPurple.main,
|
||||
mt: "auto",
|
||||
}}
|
||||
>Вход в личный кабинет</Link>
|
||||
</Box>
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box sx={{ mt: "65px" }}>
|
||||
<PenaLogo width={upMd ? 233 : 196} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "5px",
|
||||
mb: upMd ? "35px" : "33px",
|
||||
}}
|
||||
>
|
||||
Регистрация
|
||||
</Typography>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.login,
|
||||
placeholder: "username",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.login && Boolean(formik.errors.login),
|
||||
helperText: formik.touched.login && formik.errors.login,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="login"
|
||||
label="Login"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.email,
|
||||
placeholder: "username@penahub.com",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.email && Boolean(formik.errors.email),
|
||||
helperText: formik.touched.email && formik.errors.email,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="email"
|
||||
label="E-mail"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.phoneNumber,
|
||||
placeholder: "+7 900 000 00 00",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber),
|
||||
helperText: formik.touched.phoneNumber && formik.errors.phoneNumber,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="phoneNumber"
|
||||
label="Телефон"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
type: "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="password"
|
||||
label="Пароль"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.repeatPassword,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
||||
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||
type: "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
id="repeatPassword"
|
||||
label="Повторить пароль"
|
||||
gap={upMd ? "15px" : "10px"}
|
||||
/>
|
||||
<CustomButton
|
||||
fullWidth
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
textColor: "white",
|
||||
width: "100%",
|
||||
py: "12px",
|
||||
mt: upMd ? undefined : "10px",
|
||||
}}
|
||||
type="submit"
|
||||
disabled={formik.isSubmitting}
|
||||
>
|
||||
Войти
|
||||
</CustomButton>
|
||||
<Link
|
||||
href="#"
|
||||
sx={{
|
||||
color: theme.palette.brightPurple.main,
|
||||
mt: "auto",
|
||||
}}
|
||||
>
|
||||
Вход в личный кабинет
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,137 +1,144 @@
|
||||
import { Box, Typography, FormControl, InputBase, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import { apiRequestHandler } from "../../utils/api/apiRequestHandler";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ApiError } from "../../utils/api/types";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
|
||||
import { apiRequestHandler } from "@utils/api/apiRequestHandler";
|
||||
import { ApiError } from "@utils/api/types";
|
||||
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
export default function CreateTicket() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
const [ticketName, setTicketName] = useState<string>("");
|
||||
const [ticketBody, setTicketBody] = useState<string>("");
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
const [ticketName, setTicketName] = useState<string>("");
|
||||
const [ticketBody, setTicketBody] = useState<string>("");
|
||||
|
||||
async function handleCreateTicket() {
|
||||
const result = await apiRequestHandler.createTicket({
|
||||
Title: ticketName,
|
||||
Message: ticketBody,
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
enqueueSnackbar(`Unknown error`);
|
||||
} else {
|
||||
navigate(`/support/${result.Ticket}`);
|
||||
}
|
||||
async function handleCreateTicket() {
|
||||
const result = await apiRequestHandler.createTicket({
|
||||
Title: ticketName,
|
||||
Message: ticketBody,
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
enqueueSnackbar(`Unknown error`);
|
||||
} else {
|
||||
navigate(`/support/${result.Ticket}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: upMd ? "white" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
maxHeight: upMd ? "443px" : undefined,
|
||||
borderRadius: "12px",
|
||||
p: upMd ? "20px" : undefined,
|
||||
gap: upMd ? "40px" : "20px",
|
||||
boxShadow: upMd ?
|
||||
`0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: upMd ? "white" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
maxHeight: upMd ? "443px" : undefined,
|
||||
borderRadius: "12px",
|
||||
p: upMd ? "20px" : undefined,
|
||||
gap: upMd ? "40px" : "20px",
|
||||
boxShadow: upMd
|
||||
? `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)`
|
||||
:
|
||||
undefined,
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "start",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant={upMd ? "h5" : "body2"} mb={upMd ? "30px" : "20px"}>
|
||||
Написать обращение
|
||||
</Typography>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<InputBase
|
||||
value={ticketName}
|
||||
fullWidth
|
||||
placeholder="Заголовок обращения"
|
||||
id="ticket-header"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "10px",
|
||||
p: 0,
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
pt: "10px",
|
||||
pb: "10px",
|
||||
px: "19px",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => setTicketName(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
mt: "16px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
>
|
||||
<InputBase
|
||||
value={ticketBody}
|
||||
fullWidth
|
||||
placeholder="Текст обращения"
|
||||
id="ticket-body"
|
||||
multiline
|
||||
sx={{
|
||||
p: 0,
|
||||
height: "284px",
|
||||
alignItems: "start",
|
||||
overflow: "auto",
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
pt: "13px",
|
||||
pb: "13px",
|
||||
px: "19px",
|
||||
height: "300px",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => setTicketBody(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ alignSelf: upMd ? "end" : "start" }}>
|
||||
<CustomButton
|
||||
onClick={handleCreateTicket}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "start",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant={upMd ? "h5" : "body2"} mb={upMd ? "30px" : "20px"}>Написать обращение</Typography>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<InputBase
|
||||
value={ticketName}
|
||||
fullWidth
|
||||
placeholder="Заголовок обращения"
|
||||
id="ticket-header"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "10px",
|
||||
p: 0,
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
pt: "10px",
|
||||
pb: "10px",
|
||||
px: "19px",
|
||||
}
|
||||
}}
|
||||
onChange={e => setTicketName(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<Box sx={{
|
||||
overflow: "hidden",
|
||||
mt: "16px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "10px",
|
||||
}}>
|
||||
<InputBase
|
||||
value={ticketBody}
|
||||
fullWidth
|
||||
placeholder="Текст обращения"
|
||||
id="ticket-body"
|
||||
multiline
|
||||
sx={{
|
||||
p: 0,
|
||||
height: "284px",
|
||||
alignItems: "start",
|
||||
overflow: "auto",
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
pt: "13px",
|
||||
pb: "13px",
|
||||
px: "19px",
|
||||
height: "300px",
|
||||
}
|
||||
}}
|
||||
onChange={e => setTicketBody(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ alignSelf: upMd ? "end" : "start" }}>
|
||||
<CustomButton
|
||||
onClick={handleCreateTicket}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>Отправить</CustomButton>
|
||||
</Box>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
Отправить
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,59 +1,58 @@
|
||||
import { Typography, Box, useTheme, useMediaQuery, IconButton } from "@mui/material";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import ComplexNavText from "../../components/ComplexNavText";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import ComplexNavText from "@components/ComplexNavText";
|
||||
import SupportChat from "./SupportChat";
|
||||
import CreateTicket from "./CreateTicket";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import TicketList from "./TicketList";
|
||||
|
||||
|
||||
export default function Support() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const ticketId = useParams().ticketId;
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const ticketId = useParams().ticketId;
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
pt: upMd ? "25px" : "20px",
|
||||
pb: upMd ? "82px" : "43px",
|
||||
height: "100%",
|
||||
}}
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
pt: upMd ? "25px" : "20px",
|
||||
pb: upMd ? "82px" : "43px",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{upMd && <ComplexNavText text1="Все тарифы — " text2="Запрос в службу техподдержки" />}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd && (
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h4">Запрос в службу техподдержки</Typography>
|
||||
</Box>
|
||||
{ticketId ? (
|
||||
<SupportChat />
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: upMd ? "40px" : "60px",
|
||||
}}
|
||||
>
|
||||
{upMd &&
|
||||
<ComplexNavText text1="Все тарифы — " text2="Запрос в службу техподдержки" />
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd &&
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
}
|
||||
<Typography variant="h4">Запрос в службу техподдержки</Typography>
|
||||
</Box>
|
||||
{ticketId ?
|
||||
<SupportChat />
|
||||
:
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: upMd ? "40px" : "60px",
|
||||
}}
|
||||
>
|
||||
<CreateTicket />
|
||||
<TicketList />
|
||||
</Box>
|
||||
}
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
<CreateTicket />
|
||||
<TicketList />
|
||||
</Box>
|
||||
)}
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,269 +1,309 @@
|
||||
import { Box, Fab, FormControl, IconButton, InputAdornment, InputBase, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import Message from "./Message";
|
||||
import SendIcon from "../../components/icons/SendIcon";
|
||||
import { apiRequestHandler } from "../../utils/api/apiRequestHandler";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { ApiError, TicketMessage } from "../../utils/api/types";
|
||||
import { useSnackbar } from "notistack";
|
||||
import {
|
||||
Box,
|
||||
Fab,
|
||||
FormControl,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputBase,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
|
||||
import { throttle } from "../../utils/decorators";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import SendIcon from "@components/icons/SendIcon";
|
||||
import Message from "./Message";
|
||||
|
||||
import { apiRequestHandler } from "@utils/api/apiRequestHandler";
|
||||
import { ApiError, TicketMessage } from "@utils/api/types";
|
||||
import { throttle } from "@utils/decorators";
|
||||
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
export default function SupportChat() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [messageText, setMessageText] = useState<string>("");
|
||||
const [messages, setMessages] = useState<TicketMessage[]>([]);
|
||||
const ticketId = useParams().ticketId;
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const chatBoxRef = useRef<HTMLDivElement>();
|
||||
const [isPreventAutoscroll, setIsPreventAutoscroll] = useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [messageText, setMessageText] = useState<string>("");
|
||||
const [messages, setMessages] = useState<TicketMessage[]>([]);
|
||||
const ticketId = useParams().ticketId;
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const chatBoxRef = useRef<HTMLDivElement>();
|
||||
const [isPreventAutoscroll, setIsPreventAutoscroll] = useState<boolean>(false);
|
||||
|
||||
function scrollToBottom() {
|
||||
if (!chatBoxRef.current) return;
|
||||
function scrollToBottom() {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
chatBoxRef.current.scroll({
|
||||
left: 0,
|
||||
top: chatBoxRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
chatBoxRef.current.scroll({
|
||||
left: 0,
|
||||
top: chatBoxRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(function refreshChatScrollTop() {
|
||||
if (!chatBoxRef.current) return;
|
||||
useEffect(function refreshChatScrollTop() {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
const chatBox = chatBoxRef.current;
|
||||
const scrollHandler = () => setIsPreventAutoscroll(chatBox.scrollTop + chatBox.clientHeight * 2 < chatBox.scrollHeight);
|
||||
const chatBox = chatBoxRef.current;
|
||||
const scrollHandler = () =>
|
||||
setIsPreventAutoscroll(chatBox.scrollTop + chatBox.clientHeight * 2 < chatBox.scrollHeight);
|
||||
|
||||
const throttledScrollHandler = throttle(scrollHandler, 200);
|
||||
chatBox.addEventListener("scroll", throttledScrollHandler);
|
||||
const throttledScrollHandler = throttle(scrollHandler, 200);
|
||||
chatBox.addEventListener("scroll", throttledScrollHandler);
|
||||
|
||||
return () => {
|
||||
chatBox.removeEventListener("scroll", throttledScrollHandler);
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
chatBox.removeEventListener("scroll", throttledScrollHandler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// TODO При подписке на SSE сервер уже отправляет все сообщения тикета
|
||||
useEffect(function getMessages() {
|
||||
if (!ticketId) return;
|
||||
// TODO При подписке на SSE сервер уже отправляет все сообщения тикета
|
||||
useEffect(
|
||||
function getMessages() {
|
||||
if (!ticketId) return;
|
||||
|
||||
const abortController = new AbortController();
|
||||
apiRequestHandler.getMessages({
|
||||
const abortController = new AbortController();
|
||||
apiRequestHandler
|
||||
.getMessages(
|
||||
{
|
||||
amt: 100,
|
||||
page: 0,
|
||||
srch: "",
|
||||
ticket: ticketId,
|
||||
}, abortController.signal).then(result => {
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Api error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else {
|
||||
setMessages(result);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [enqueueSnackbar, ticketId]);
|
||||
|
||||
useEffect(function scrollOnMessage() {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
if (!isPreventAutoscroll) scrollToBottom();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [messages]);
|
||||
|
||||
useEffect(function subscribeToMessages() {
|
||||
if (!ticketId) return;
|
||||
const unsubscribe = apiRequestHandler.subscribeToTicket({
|
||||
ticketId,
|
||||
onMessage(event) {
|
||||
// console.log("SSE received:", event.data);
|
||||
try {
|
||||
const newMessage = JSON.parse(event.data) as TicketMessage;
|
||||
setMessages(prev => prev.findIndex(message => message.id === newMessage.id) === -1 ? [...prev.slice(), newMessage] : prev);
|
||||
} catch (error) {
|
||||
console.log("SSE is not JSON", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [ticketId]);
|
||||
|
||||
async function handleSendMessage() {
|
||||
if (!ticketId) return;
|
||||
|
||||
const result = await apiRequestHandler.sendTicketMessage({
|
||||
ticket: ticketId,
|
||||
message: messageText,
|
||||
lang: "ru",
|
||||
files: [],
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
},
|
||||
abortController.signal
|
||||
)
|
||||
.then((result) => {
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Api error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
} else if (result instanceof Error) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else {
|
||||
setMessageText("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setMessages(result);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: upMd ? "white" : undefined,
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
maxHeight: upMd ? "443px" : undefined,
|
||||
borderRadius: "12px",
|
||||
p: upMd ? "20px" : undefined,
|
||||
gap: "40px",
|
||||
boxShadow: upMd ?
|
||||
`0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
},
|
||||
[enqueueSnackbar, ticketId]
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function scrollOnMessage() {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
if (!isPreventAutoscroll) scrollToBottom();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
},
|
||||
[messages]
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function subscribeToMessages() {
|
||||
if (!ticketId) return;
|
||||
const unsubscribe = apiRequestHandler.subscribeToTicket({
|
||||
ticketId,
|
||||
onMessage(event) {
|
||||
// console.log("SSE received:", event.data);
|
||||
try {
|
||||
const newMessage = JSON.parse(event.data) as TicketMessage;
|
||||
setMessages((prev) =>
|
||||
prev.findIndex((message) => message.id === newMessage.id) === -1 ? [...prev.slice(), newMessage] : prev
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("SSE is not JSON", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
},
|
||||
[ticketId]
|
||||
);
|
||||
|
||||
async function handleSendMessage() {
|
||||
if (!ticketId) return;
|
||||
|
||||
const result = await apiRequestHandler.sendTicketMessage({
|
||||
ticket: ticketId,
|
||||
message: messageText,
|
||||
lang: "ru",
|
||||
files: [],
|
||||
});
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Api error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else {
|
||||
setMessageText("");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: upMd ? "white" : undefined,
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
maxHeight: upMd ? "443px" : undefined,
|
||||
borderRadius: "12px",
|
||||
p: upMd ? "20px" : undefined,
|
||||
gap: "40px",
|
||||
boxShadow: upMd
|
||||
? `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)`
|
||||
:
|
||||
undefined,
|
||||
}}
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "start",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant={upMd ? "h5" : "body2"} mb={"4px"}>
|
||||
Заголовок
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "14px",
|
||||
lineHeight: "17px",
|
||||
color: theme.palette.grey2.main,
|
||||
mb: upMd ? "9px" : "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
Создан: 15.09.22 08:39
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#ECECF3",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "10px",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
minHeight: "345px",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
flexGrow: 1,
|
||||
borderBottom: `1px solid ${theme.palette.grey2.main}`,
|
||||
height: "200px",
|
||||
}}
|
||||
>
|
||||
{isPreventAutoscroll && (
|
||||
<Fab
|
||||
size="small"
|
||||
onClick={scrollToBottom}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "start",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
position: "absolute",
|
||||
left: "10px",
|
||||
bottom: "10px",
|
||||
}}
|
||||
>
|
||||
<ArrowDownwardIcon />
|
||||
</Fab>
|
||||
)}
|
||||
<Box
|
||||
ref={chatBoxRef}
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
gap: upMd ? "20px" : "16px",
|
||||
px: upMd ? "20px" : "5px",
|
||||
py: upMd ? "20px" : "13px",
|
||||
overflowY: "auto",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography variant={upMd ? "h5" : "body2"} mb={"4px"}>Заголовок</Typography>
|
||||
<Typography sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "14px",
|
||||
lineHeight: "17px",
|
||||
color: theme.palette.grey2.main,
|
||||
mb: upMd ? "9px" : "20px",
|
||||
}}>Создан: 15.09.22 08:39</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#ECECF3",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "10px",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
minHeight: "345px",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
flexGrow: 1,
|
||||
borderBottom: `1px solid ${theme.palette.grey2.main}`,
|
||||
height: "200px",
|
||||
}}>
|
||||
{isPreventAutoscroll &&
|
||||
<Fab
|
||||
size="small"
|
||||
onClick={scrollToBottom}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: "10px",
|
||||
bottom: "10px",
|
||||
}}
|
||||
>
|
||||
<ArrowDownwardIcon />
|
||||
</Fab>
|
||||
}
|
||||
<Box
|
||||
ref={chatBoxRef}
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
gap: upMd ? "20px" : "16px",
|
||||
px: upMd ? "20px" : "5px",
|
||||
py: upMd ? "20px" : "13px",
|
||||
overflowY: "auto",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{messages.map(message =>
|
||||
<Message
|
||||
key={message.id}
|
||||
text={message.message}
|
||||
time={new Date(message.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||
isSelf={true}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<FormControl>
|
||||
<InputBase
|
||||
value={messageText}
|
||||
fullWidth
|
||||
placeholder="Текст обращения"
|
||||
id="message"
|
||||
multiline
|
||||
sx={{
|
||||
width: "100%",
|
||||
p: 0,
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
pt: upMd ? "13px" : "28px",
|
||||
pb: upMd ? "13px" : "24px",
|
||||
px: "19px",
|
||||
maxHeight: "calc(19px * 5)",
|
||||
}
|
||||
}}
|
||||
onChange={e => setMessageText(e.target.value)}
|
||||
endAdornment={!upMd &&
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={handleSendMessage}
|
||||
sx={{
|
||||
height: "45px",
|
||||
width: "45px",
|
||||
mr: "13px",
|
||||
p: 0,
|
||||
}}
|
||||
>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{messages.map((message) => (
|
||||
<Message
|
||||
key={message.id}
|
||||
text={message.message}
|
||||
time={new Date(message.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||
isSelf={true}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
{upMd &&
|
||||
<Box sx={{ alignSelf: "end" }}>
|
||||
<CustomButton
|
||||
onClick={handleSendMessage}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>Отправить</CustomButton>
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
<FormControl>
|
||||
<InputBase
|
||||
value={messageText}
|
||||
fullWidth
|
||||
placeholder="Текст обращения"
|
||||
id="message"
|
||||
multiline
|
||||
sx={{
|
||||
width: "100%",
|
||||
p: 0,
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
pt: upMd ? "13px" : "28px",
|
||||
pb: upMd ? "13px" : "24px",
|
||||
px: "19px",
|
||||
maxHeight: "calc(19px * 5)",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => setMessageText(e.target.value)}
|
||||
endAdornment={
|
||||
!upMd && (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={handleSendMessage}
|
||||
sx={{
|
||||
height: "45px",
|
||||
width: "45px",
|
||||
mr: "13px",
|
||||
p: 0,
|
||||
}}
|
||||
>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
</Box>
|
||||
{upMd && (
|
||||
<Box sx={{ alignSelf: "end" }}>
|
||||
<CustomButton
|
||||
onClick={handleSendMessage}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
Отправить
|
||||
</CustomButton>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,83 +1,94 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { useMemo } from "react";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
body: string;
|
||||
time: string;
|
||||
ticketId: string;
|
||||
name: string;
|
||||
body: string;
|
||||
time: string;
|
||||
ticketId: string;
|
||||
}
|
||||
|
||||
export default function TicketCard({ name, body, time, ticketId }: Props) {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
const timeText = useMemo(() => (
|
||||
<Typography sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "14px",
|
||||
lineHeight: "17px",
|
||||
color: theme.palette.grey2.main,
|
||||
mt: "2px",
|
||||
mb: "5px",
|
||||
}}>{time}</Typography>
|
||||
), [theme.palette.grey2.main, time]);
|
||||
const timeText = useMemo(
|
||||
() => (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "14px",
|
||||
lineHeight: "17px",
|
||||
color: theme.palette.grey2.main,
|
||||
mt: "2px",
|
||||
mb: "5px",
|
||||
}}
|
||||
>
|
||||
{time}
|
||||
</Typography>
|
||||
),
|
||||
[theme.palette.grey2.main, time]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
p: "20px",
|
||||
backgroundColor: "white",
|
||||
borderRadius: "12px",
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
justifyContent: "space-between",
|
||||
gap: upMd ? "40px" : "20px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
p: "20px",
|
||||
backgroundColor: "white",
|
||||
borderRadius: "12px",
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
justifyContent: "space-between",
|
||||
gap: upMd ? "40px" : "20px",
|
||||
boxShadow: `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)`,
|
||||
}}
|
||||
}}
|
||||
>
|
||||
{!upMd && timeText}
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
mb: "20px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{!upMd && timeText}
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
mb: "20px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>{name}</Typography>
|
||||
<Typography color={theme.palette.grey3.main}>{body}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: upMd ? "end" : "start",
|
||||
justifyContent: "space-between",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{upMd && timeText}
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
component={RouterLink}
|
||||
to={`/support/${ticketId}`}
|
||||
sx={{
|
||||
py: "9px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
borderColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>Перейти</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography color={theme.palette.grey3.main}>{body}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: upMd ? "end" : "start",
|
||||
justifyContent: "space-between",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{upMd && timeText}
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
component={RouterLink}
|
||||
to={`/support/${ticketId}`}
|
||||
sx={{
|
||||
py: "9px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
borderColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
Перейти
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,120 +1,134 @@
|
||||
import { CircularProgress, List, ListItem, Box, useTheme, Pagination } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiRequestHandler } from "../../utils/api/apiRequestHandler";
|
||||
import { ApiError, Ticket } from "../../utils/api/types";
|
||||
|
||||
import TicketCard from "./TicketCard";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
import { apiRequestHandler } from "@utils/api/apiRequestHandler";
|
||||
import { ApiError, Ticket } from "@utils/api/types";
|
||||
|
||||
const TICKETS_PER_PAGE = 10;
|
||||
|
||||
export default function TicketList() {
|
||||
const theme = useTheme();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [tickets, setTickets] = useState<Ticket[]>([]);
|
||||
const [ticketCount, setTicketCount] = useState<number | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [tickets, setTickets] = useState<Ticket[]>([]);
|
||||
const [ticketCount, setTicketCount] = useState<number | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(function fetchTickets() {
|
||||
setIsLoading(true);
|
||||
const abortController = new AbortController();
|
||||
useEffect(
|
||||
function fetchTickets() {
|
||||
setIsLoading(true);
|
||||
const abortController = new AbortController();
|
||||
|
||||
apiRequestHandler.getTickets({ amt: TICKETS_PER_PAGE, page: currentPage, srch: "", status: "open" }, abortController.signal)
|
||||
.then(result => {
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
} else {
|
||||
setTickets(result.data);
|
||||
setTicketCount(result.count);
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [currentPage, enqueueSnackbar]);
|
||||
|
||||
useEffect(function subscribeToTickets() {
|
||||
const unsubscribe = apiRequestHandler.subscribeToAllTickets({
|
||||
onMessage(event) {
|
||||
console.log("SSE received:", event.data);
|
||||
try {
|
||||
const newTicket = JSON.parse(event.data) as Ticket;
|
||||
const existingTicketIndex = tickets.findIndex(ticket => ticket.id === newTicket.id);
|
||||
if (existingTicketIndex !== -1) {
|
||||
setTickets(prevTickets => {
|
||||
const newTickets = prevTickets.slice();
|
||||
newTickets.splice(existingTicketIndex, 1, newTicket);
|
||||
return newTickets;
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTickets(prevTickets => [newTicket, ...prevTickets.slice(0, TICKETS_PER_PAGE - 1)]);
|
||||
} catch (error) {
|
||||
console.log("SSE is not JSON", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
}
|
||||
apiRequestHandler
|
||||
.getTickets({ amt: TICKETS_PER_PAGE, page: currentPage, srch: "", status: "open" }, abortController.signal)
|
||||
.then((result) => {
|
||||
if (result instanceof ApiError) {
|
||||
enqueueSnackbar(`Error: ${result.message}`);
|
||||
} else if (result instanceof Error) {
|
||||
console.log(result);
|
||||
} else {
|
||||
setTickets(result.data);
|
||||
setTicketCount(result.count);
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [tickets]);
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
},
|
||||
[currentPage, enqueueSnackbar]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
gap: "40px",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
<List sx={{
|
||||
p: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "40px",
|
||||
opacity: isLoading ? 0.4 : 1,
|
||||
transitionProperty: "opacity",
|
||||
transitionDuration: "200ms",
|
||||
}}>
|
||||
{tickets.map(ticket =>
|
||||
<ListItem key={ticket.id} disablePadding>
|
||||
<TicketCard
|
||||
name={ticket.title}
|
||||
body={ticket.top_message.message}
|
||||
time={new Date(ticket.updated_at).toLocaleDateString()}
|
||||
ticketId={ticket.id}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
{isLoading &&
|
||||
<Box sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: "80px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<CircularProgress sx={{ color: theme.palette.brightPurple.main }} size={60} />
|
||||
</Box>
|
||||
}
|
||||
</List>
|
||||
{ticketCount !== null && ticketCount > TICKETS_PER_PAGE &&
|
||||
<Pagination
|
||||
count={Math.ceil(ticketCount / TICKETS_PER_PAGE)}
|
||||
page={currentPage + 1}
|
||||
onChange={(e, value) => setCurrentPage(value - 1)}
|
||||
sx={{ alignSelf: "center" }}
|
||||
/>
|
||||
useEffect(
|
||||
function subscribeToTickets() {
|
||||
const unsubscribe = apiRequestHandler.subscribeToAllTickets({
|
||||
onMessage(event) {
|
||||
console.log("SSE received:", event.data);
|
||||
try {
|
||||
const newTicket = JSON.parse(event.data) as Ticket;
|
||||
const existingTicketIndex = tickets.findIndex((ticket) => ticket.id === newTicket.id);
|
||||
if (existingTicketIndex !== -1) {
|
||||
setTickets((prevTickets) => {
|
||||
const newTickets = prevTickets.slice();
|
||||
newTickets.splice(existingTicketIndex, 1, newTicket);
|
||||
return newTickets;
|
||||
});
|
||||
return;
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
setTickets((prevTickets) => [newTicket, ...prevTickets.slice(0, TICKETS_PER_PAGE - 1)]);
|
||||
} catch (error) {
|
||||
console.log("SSE is not JSON", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
},
|
||||
[tickets]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "40px",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<List
|
||||
sx={{
|
||||
p: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "40px",
|
||||
opacity: isLoading ? 0.4 : 1,
|
||||
transitionProperty: "opacity",
|
||||
transitionDuration: "200ms",
|
||||
}}
|
||||
>
|
||||
{tickets.map((ticket) => (
|
||||
<ListItem key={ticket.id} disablePadding>
|
||||
<TicketCard
|
||||
name={ticket.title}
|
||||
body={ticket.top_message.message}
|
||||
time={new Date(ticket.updated_at).toLocaleDateString()}
|
||||
ticketId={ticket.id}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
{isLoading && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: "80px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CircularProgress sx={{ color: theme.palette.brightPurple.main }} size={60} />
|
||||
</Box>
|
||||
)}
|
||||
</List>
|
||||
{ticketCount !== null && ticketCount > TICKETS_PER_PAGE && (
|
||||
<Pagination
|
||||
count={Math.ceil(ticketCount / TICKETS_PER_PAGE)}
|
||||
page={currentPage + 1}
|
||||
onChange={(e, value) => setCurrentPage(value - 1)}
|
||||
sx={{ alignSelf: "center" }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Box, SxProps, Theme } from "@mui/material";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
import CustomButton from "@components/CustomButton";
|
||||
|
||||
import { IconsCreate } from "../../lib/IconsCreate";
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Box, SxProps, Theme, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
|
||||
interface Props {
|
||||
icon: React.ReactNode;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Box, SxProps, Theme, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import CustomButton from "../../components/CustomButton";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
|
||||
interface Props {
|
||||
icon: React.ReactNode;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { useMediaQuery, useTheme, Box, Link, Typography } from "@mui/material";
|
||||
import { Outlet, Route, Routes } from "react-router-dom";
|
||||
|
||||
import CardWithLink from "../../components/CardWithLink";
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import CustomIcon from "../../components/icons/CustomIcon";
|
||||
import CardWithLink from "@components/CardWithLink";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import CustomIcon from "@components/icons/CustomIcon";
|
||||
import TariffCard from "./TariffCard";
|
||||
|
||||
import video1 from "../../assets/animations/Icon_1.webm";
|
||||
import video2 from "../../assets/animations/Icon_2.webm";
|
||||
import video3 from "../../assets/animations/Icon_3.webm";
|
||||
@ -91,37 +92,37 @@ export default function Tariffs() {
|
||||
}}
|
||||
>
|
||||
<CardWithLink
|
||||
shadowType="light"
|
||||
buttonType="button"
|
||||
height="520px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Шаблонизатор"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={video1}
|
||||
shadowType="light"
|
||||
buttonType="button"
|
||||
height="520px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Шаблонизатор"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={video1}
|
||||
/>
|
||||
<CardWithLink
|
||||
shadowType="light"
|
||||
buttonType="button"
|
||||
height="520px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Опросник"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={video2}
|
||||
shadowType="light"
|
||||
buttonType="button"
|
||||
height="520px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Опросник"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={video2}
|
||||
/>
|
||||
<CardWithLink
|
||||
shadowType="light"
|
||||
buttonType="button"
|
||||
height="520px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Сокращатель ссылок"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={video3}
|
||||
shadowType="light"
|
||||
buttonType="button"
|
||||
height="520px"
|
||||
width={upMd ? "31%" : "100%"}
|
||||
headerText="Сокращатель ссылок"
|
||||
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "
|
||||
isHighlighted
|
||||
linkHref="#"
|
||||
video={video3}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useMediaQuery, useTheme, Box, Typography, List, ListItem } from "@mui/material";
|
||||
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import { TariffCardTimeAndVolume } from "./TariffCardTimeAndVolume";
|
||||
import { FreeTariffCard } from "./FreeTariffCard";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useMediaQuery, useTheme, Box, Typography, List, ListItem } from "@mui/material";
|
||||
|
||||
import SectionWrapper from "../../components/SectionWrapper";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import { TariffCardTimeAndVolume } from "./TariffCardTimeAndVolume";
|
||||
import { FreeTariffCard } from "./FreeTariffCard";
|
||||
|
||||
|
@ -1,158 +1,148 @@
|
||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CustomButton from "../components/CustomButton";
|
||||
import WalletIcon from "../components/icons/WalletIcon";
|
||||
import SectionWrapper from "../components/SectionWrapper";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import ComplexNavText from "../components/ComplexNavText";
|
||||
|
||||
import CustomButton from "@components/CustomButton";
|
||||
import WalletIcon from "@components/icons/WalletIcon";
|
||||
import SectionWrapper from "@components/SectionWrapper";
|
||||
import ComplexNavText from "@components/ComplexNavText";
|
||||
|
||||
export default function Wallet() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
const footnotes = (
|
||||
<Box
|
||||
component="ol"
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
pt: "10px",
|
||||
pl: "25px",
|
||||
mt: 0,
|
||||
mb: upMd ? "3px" : "73px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="li"
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Текст для сносок: текст-заполнитель —
|
||||
это текст, который имеет текст-заполнитель —
|
||||
это текст, который имеет
|
||||
</Typography>
|
||||
<Typography
|
||||
component="li"
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Текст для сносок: тель —
|
||||
это текст, который имеет текст-заполнитель —
|
||||
это текст, который имеет
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
const footnotes = (
|
||||
<Box
|
||||
component="ol"
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
pt: "10px",
|
||||
pl: "25px",
|
||||
mt: 0,
|
||||
mb: upMd ? "3px" : "73px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="li"
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Текст для сносок: текст-заполнитель — это текст, который имеет текст-заполнитель — это текст, который имеет
|
||||
</Typography>
|
||||
<Typography
|
||||
component="li"
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Текст для сносок: тель — это текст, который имеет текст-заполнитель — это текст, который имеет
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: "25px",
|
||||
mb: "70px",
|
||||
}}
|
||||
>
|
||||
{upMd &&
|
||||
<ComplexNavText text1="Все тарифы — " text2="Мой кошелёк" />
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd &&
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
}
|
||||
<Typography variant="h4">Мой кошелёк</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: "9%",
|
||||
borderRadius: "12px",
|
||||
mb: "40px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
return (
|
||||
<SectionWrapper
|
||||
maxWidth="lg"
|
||||
sx={{
|
||||
mt: "25px",
|
||||
mb: "70px",
|
||||
}}
|
||||
>
|
||||
{upMd && <ComplexNavText text1="Все тарифы — " text2="Мой кошелёк" />}
|
||||
<Box
|
||||
sx={{
|
||||
mt: "20px",
|
||||
mb: "40px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{!upMd && (
|
||||
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h4">Мой кошелёк</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
flexDirection: upMd ? "row" : "column",
|
||||
gap: "9%",
|
||||
borderRadius: "12px",
|
||||
mb: "40px",
|
||||
boxShadow: `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)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: upMd ? "59.5%" : undefined,
|
||||
p: "20px",
|
||||
pb: upMd ? undefined : "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: theme.palette.grey3.main, mb: "30px" }}>Баланс 10.04.2022</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "22px",
|
||||
pb: "40px",
|
||||
borderBottom: `1px solid ${theme.palette.grey2.main}`,
|
||||
}}
|
||||
>
|
||||
<WalletIcon bgcolor="#FEDFD0" color={theme.palette.orange.main} />
|
||||
<Typography variant="h5">10 304 руб.</Typography>
|
||||
</Box>
|
||||
{upMd && footnotes}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
color: theme.palette.grey3.main,
|
||||
width: upMd ? "31.5%" : undefined,
|
||||
p: "20px",
|
||||
pl: upMd ? "33px" : undefined,
|
||||
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "22px",
|
||||
maxWidth: "85%",
|
||||
mb: "32px",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
Текст-заполнитель —
|
||||
это текст, который имеет
|
||||
</Typography>
|
||||
<Typography>
|
||||
Текст-заполнитель —
|
||||
это текст, который имеет
|
||||
</Typography>
|
||||
</Box>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
textColor: "white",
|
||||
mt: "auto",
|
||||
}}
|
||||
>Пополнить</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{!upMd && footnotes}
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: upMd ? "59.5%" : undefined,
|
||||
p: "20px",
|
||||
pb: upMd ? undefined : "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: theme.palette.grey3.main, mb: "30px" }}>Баланс 10.04.2022</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "22px",
|
||||
pb: "40px",
|
||||
borderBottom: `1px solid ${theme.palette.grey2.main}`,
|
||||
}}
|
||||
>
|
||||
<WalletIcon bgcolor="#FEDFD0" color={theme.palette.orange.main} />
|
||||
<Typography variant="h5">10 304 руб.</Typography>
|
||||
</Box>
|
||||
{upMd && footnotes}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
color: theme.palette.grey3.main,
|
||||
width: upMd ? "31.5%" : undefined,
|
||||
p: "20px",
|
||||
pl: upMd ? "33px" : undefined,
|
||||
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "22px",
|
||||
maxWidth: "85%",
|
||||
mb: "32px",
|
||||
}}
|
||||
>
|
||||
<Typography>Текст-заполнитель — это текст, который имеет</Typography>
|
||||
<Typography>Текст-заполнитель — это текст, который имеет</Typography>
|
||||
</Box>
|
||||
<CustomButton
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
textColor: "white",
|
||||
mt: "auto",
|
||||
}}
|
||||
>
|
||||
Пополнить
|
||||
</CustomButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{!upMd && footnotes}
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@root/*": ["./*"],
|
||||
"@utils/*": ["./utils/*"],
|
||||
"@components/*": ["./utils/components/*"]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@root/*": ["./*"],
|
||||
"@utils/*": ["./utils/*"],
|
||||
"@components/*": ["./components/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user