fix: conflicts resolved
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
BIN
public/favicon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
71
public/favicon.svg
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="43.585045"
|
||||||
|
height="39.566273"
|
||||||
|
viewBox="0 0 63.268614 57.700814"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
sodipodi:docname="favicon.svg"
|
||||||
|
inkscape:export-filename="favicon.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview8"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showguides="true" />
|
||||||
|
<g
|
||||||
|
clip-path="url(#clip0_316_1239)"
|
||||||
|
id="g8"
|
||||||
|
transform="translate(-1.6513393,-3.2563442)">
|
||||||
|
<g
|
||||||
|
id="g9"
|
||||||
|
transform="translate(0.27803262,-0.00871564)">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M 25.9138,3.31956 C 18.594,2.47167 13.5439,10.3345 8.84663,16.0182 4.72431,21.0062 1.6549,26.6402 1.29838,33.1042 0.919075,39.9813 2.16658,47.1435 6.85174,52.1873 11.6777,57.3827 18.9068,60.6653 25.9138,59.604 32.3391,58.6308 35.1822,51.5749 39.9658,47.1716 45.16,42.3905 54.837,40.1668 54.7027,33.1042 54.5683,26.0308 44.3552,24.6463 39.441,19.5621 34.3509,14.2959 33.1853,4.16185 25.9138,3.31956 Z"
|
||||||
|
fill="#7e2aea"
|
||||||
|
id="path1" />
|
||||||
|
<circle
|
||||||
|
cx="44.125999"
|
||||||
|
cy="56.918098"
|
||||||
|
r="4.0390601"
|
||||||
|
fill="#7e2aea"
|
||||||
|
id="circle1" />
|
||||||
|
<circle
|
||||||
|
cx="40.086498"
|
||||||
|
cy="12.1038"
|
||||||
|
r="1.53869"
|
||||||
|
fill="#7e2aea"
|
||||||
|
id="circle2" />
|
||||||
|
<path
|
||||||
|
d="m 64.699,31.4509 c -0.4487,-4.3618 -2.5007,-8.4017 -5.7585,-11.3366 -3.2577,-2.9349 -7.4891,-4.5558 -11.8739,-4.5485 -0.6225,3e-4 -1.2446,0.0329 -1.8638,0.0976 -4.3599,0.4578 -8.3958,2.5137 -11.3293,5.7713 -2.9336,3.2577 -4.557,7.4861 -4.5571,11.8699 v 0 25.3412 h 7.6024 v -10.77 c 2.9724,2.0679 6.5079,3.1735 10.1288,3.1676 0.6226,-2e-4 1.2447,-0.0327 1.8639,-0.0975 2.3167,-0.2435 4.5629,-0.9409 6.6101,-2.0525 2.0472,-1.1116 3.8555,-2.6155 5.3215,-4.4259 1.466,-1.8104 2.5611,-3.8918 3.2227,-6.1254 0.6616,-2.2336 0.8767,-4.5757 0.6332,-6.8924 z m -9.764,8.2359 c -0.8351,1.0374 -1.8677,1.8988 -3.038,2.5343 -1.1704,0.6355 -2.4552,1.0325 -3.78,1.168 -0.3553,0.0369 -0.7122,0.0555 -1.0694,0.0558 -2.2991,-0.0021 -4.5293,-0.7858 -6.3243,-2.2224 -1.7951,-1.4366 -3.0484,-3.4408 -3.5544,-5.6836 -0.5059,-2.2428 -0.2343,-4.5909 0.7702,-6.6591 1.0045,-2.0681 2.6822,-3.7333 4.7578,-4.7222 2.0756,-0.9889 4.4257,-1.2429 6.6647,-0.7202 2.2389,0.5228 4.2336,1.7911 5.6567,3.5969 1.4231,1.8058 2.19,4.0417 2.1749,6.3408 -0.0151,2.2991 -0.8114,4.5248 -2.2582,6.3117 z"
|
||||||
|
fill="#000000"
|
||||||
|
id="path2" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs
|
||||||
|
id="defs8">
|
||||||
|
<clipPath
|
||||||
|
id="clip0_316_1239">
|
||||||
|
<rect
|
||||||
|
width="179.509"
|
||||||
|
height="69.487198"
|
||||||
|
fill="#ffffff"
|
||||||
|
id="rect8"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/favicon192.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
public/favicon512.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -1,47 +1,28 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="ru">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta name="description" content="Web site created using create-react-app" />
|
<title>Pena Hub</title>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<meta name="description" content=" Заказная IT-разработка: создание уникальных и эффективных решений для вашего бизнеса. Проектирование, разработка и интеграция IT-систем под индивидуальные требования клиентов. "/>
|
||||||
<!--
|
<meta name="keywords" content=" Заказная IT-разработка, заказная разработка, индивидуальное решение, бизнес-решение, программирование, программное обеспечение, веб-дизайн, мобильные приложения, IT-интеграция, технологические решения "/>
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" sizes="any"/><!-- 32×32 -->
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" type="image/svg+xml"/>
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.png"/><!-- 180×180 -->
|
||||||
-->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap" rel="stylesheet">
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<title>React App</title>
|
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
<!-- <script src="https://markknol.github.io/console-log-viewer/console-log-viewer.js"></script> -->
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB |
@ -1,23 +1,10 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "PenaHub",
|
||||||
"name": "Create React App Sample",
|
"name": "Pena Hub",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{ "src": "/favicon192.png", "type": "image/png", "sizes": "192x192" },
|
||||||
"src": "favicon.ico",
|
{ "src": "/favicon512.png", "type": "image/png", "sizes": "512x512" }
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
],
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
|
@ -8,6 +8,7 @@ import type {
|
|||||||
SendDocumentsArgs,
|
SendDocumentsArgs,
|
||||||
UpdateDocumentsArgs,
|
UpdateDocumentsArgs,
|
||||||
} from "@root/model/auth"
|
} from "@root/model/auth"
|
||||||
|
import { AxiosError } from "axios"
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/verification"
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/verification"
|
||||||
|
|
||||||
@ -22,8 +23,19 @@ export async function verification(
|
|||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
verificationResponse.files = verificationResponse.files.map((obj) => {
|
||||||
|
obj.url = obj.url.replace("https://hub.pena.digital", process.env.REACT_APP_DOMAIN?.toString() || "").replace("https://shub.pena.digital", process.env.REACT_APP_DOMAIN?.toString() || "")
|
||||||
|
return obj
|
||||||
|
})
|
||||||
|
console.log(verificationResponse)
|
||||||
|
|
||||||
return [verificationResponse]
|
return [verificationResponse]
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
|
const err = nativeError as AxiosError
|
||||||
|
if (err.response?.status === 404) {
|
||||||
|
return [null, `нет данных`]
|
||||||
|
}
|
||||||
|
console.log(nativeError)
|
||||||
const [error] = parseAxiosError(nativeError)
|
const [error] = parseAxiosError(nativeError)
|
||||||
|
|
||||||
return [null, `Ошибка запроса верификации. ${error}`]
|
return [null, `Ошибка запроса верификации. ${error}`]
|
||||||
|
@ -1,46 +1,67 @@
|
|||||||
import { makeRequest } from "@frontend/kitui"
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet"
|
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error"
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer"
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
||||||
|
|
||||||
const testPaymentBody: SendPaymentRequest = {
|
const testPaymentBody: SendPaymentRequest = {
|
||||||
type: "bankCard",
|
type: "bankCard",
|
||||||
amount: 15020,
|
amount: 15020,
|
||||||
currency: "RUB",
|
currency: "RUB",
|
||||||
bankCard: {
|
bankCard: {
|
||||||
number: "RUB",
|
number: "RUB",
|
||||||
expiryYear: "2021",
|
expiryYear: "2021",
|
||||||
expiryMonth: "05",
|
expiryMonth: "05",
|
||||||
csc: "05",
|
csc: "05",
|
||||||
cardholder: "IVAN IVANOV",
|
cardholder: "IVAN IVANOV",
|
||||||
},
|
},
|
||||||
phoneNumber: "79000000000",
|
phoneNumber: "79000000000",
|
||||||
login: "login_test",
|
login: "login_test",
|
||||||
returnUrl: window.location.origin + "/wallet",
|
returnUrl: window.location.origin + "/wallet",
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function sendPayment(
|
export async function sendPayment({
|
||||||
{body = testPaymentBody, fromSquiz = false}: {body?: SendPaymentRequest, fromSquiz:boolean}
|
body = testPaymentBody,
|
||||||
): Promise<[SendPaymentResponse | null, string?]> {
|
fromSquiz = false,
|
||||||
if (fromSquiz) body.returnUrl = "squiz.pena.digital/list?action=fromhub"
|
}: {
|
||||||
try {
|
body?: SendPaymentRequest;
|
||||||
const sendPaymentResponse = await makeRequest<
|
fromSquiz: boolean;
|
||||||
|
}): Promise<[SendPaymentResponse | null, string?]> {
|
||||||
|
if (fromSquiz) body.returnUrl = "squiz.pena.digital/list?action=fromhub";
|
||||||
|
try {
|
||||||
|
const sendPaymentResponse = await makeRequest<
|
||||||
SendPaymentRequest,
|
SendPaymentRequest,
|
||||||
SendPaymentResponse
|
SendPaymentResponse
|
||||||
>({
|
>({
|
||||||
url: apiUrl + "/wallet",
|
url: apiUrl + "/wallet",
|
||||||
contentType: true,
|
contentType: true,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
body,
|
body,
|
||||||
})
|
});
|
||||||
|
|
||||||
return [sendPaymentResponse]
|
return [sendPaymentResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError)
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null, `Ошибка оплаты. ${error}`]
|
return [null, `Ошибка оплаты. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sendRSPayment = async (): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
await makeRequest<never, string>({
|
||||||
|
url: apiUrl + "/wallet/rspay",
|
||||||
|
method: "POST",
|
||||||
|
useToken: true,
|
||||||
|
withCredentials: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return `Ошибка оплаты. ${error}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
BIN
src/assets/bank-logo/rs-pay.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
@ -15,21 +15,20 @@ export default function ChatMessage({ unAuthenticated = false, isSelf, text, cre
|
|||||||
|
|
||||||
const messageBackgroundColor = isSelf ? "white" : unAuthenticated ? "#EFF0F5" : theme.palette.gray.main
|
const messageBackgroundColor = isSelf ? "white" : unAuthenticated ? "#EFF0F5" : theme.palette.gray.main
|
||||||
|
|
||||||
const date = new Date(createdAt)
|
const date = new Date(createdAt);
|
||||||
|
const today = isDateToday(date);
|
||||||
const time = date.toLocaleString([], {
|
const time = date.toLocaleString([], {
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
...(!isDateToday(date) && { year: "2-digit", month: "2-digit", day: "2-digit" })
|
...(!today && { year: "2-digit", month: "2-digit", day: "2-digit" })
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignSelf: isSelf ? "end" : "start",
|
|
||||||
gap: "9px",
|
gap: "9px",
|
||||||
pl: isSelf ? undefined : "8px",
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
pr: isSelf ? "8px" : undefined,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
@ -39,6 +38,7 @@ export default function ChatMessage({ unAuthenticated = false, isSelf, text, cre
|
|||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
lineHeight: "17px",
|
lineHeight: "17px",
|
||||||
order: isSelf ? 1 : 2,
|
order: isSelf ? 1 : 2,
|
||||||
|
margin: isSelf ? "0 0 0 auto" : "0 auto 0 0",
|
||||||
color: theme.palette.gray.main,
|
color: theme.palette.gray.main,
|
||||||
mb: "-4px",
|
mb: "-4px",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
@ -51,9 +51,10 @@ export default function ChatMessage({ unAuthenticated = false, isSelf, text, cre
|
|||||||
order: isSelf ? 2 : 1,
|
order: isSelf ? 2 : 1,
|
||||||
p: upMd ? "18px" : "12px",
|
p: upMd ? "18px" : "12px",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
maxWidth: "464px",
|
|
||||||
color: (isSelf || unAuthenticated) ? theme.palette.gray.dark : "white",
|
color: (isSelf || unAuthenticated) ? theme.palette.gray.dark : "white",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
maxWidth: `calc(100% - ${today ? 45 : 110}px)`,
|
||||||
|
overflowWrap: "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
@ -6,6 +6,7 @@ type CustomSliderProps = {
|
|||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
onChange: (value: number | number[]) => void;
|
onChange: (value: number | number[]) => void;
|
||||||
|
firstStep: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomSlider = ({
|
export const CustomSlider = ({
|
||||||
@ -13,11 +14,16 @@ export const CustomSlider = ({
|
|||||||
min = 0,
|
min = 0,
|
||||||
max = 100,
|
max = 100,
|
||||||
onChange,
|
onChange,
|
||||||
|
firstStep
|
||||||
}: CustomSliderProps) => {
|
}: CustomSliderProps) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const [step, setStep] = useState<number>(1)
|
const [step, setStep] = useState<number>(1)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (value <= firstStep) {
|
||||||
|
return setStep(firstStep)
|
||||||
|
}
|
||||||
|
|
||||||
if (value < 100) {
|
if (value < 100) {
|
||||||
return setStep(10)
|
return setStep(10)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ const name: Record<string, string> = {
|
|||||||
templategen: "Шаблонизатор",
|
templategen: "Шаблонизатор",
|
||||||
squiz: "Опросник",
|
squiz: "Опросник",
|
||||||
reducer: "Скоращатель ссылок",
|
reducer: "Скоращатель ссылок",
|
||||||
custom: "Кастомные тарифы",
|
custom: "Мои тарифы",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -33,14 +33,15 @@ import {
|
|||||||
useEventListener,
|
useEventListener,
|
||||||
createTicket,
|
createTicket,
|
||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import { sendTicketMessage } from "@root/api/ticket";
|
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
|
||||||
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Chat({ sx }: Props) {
|
export default function Chat({ open = false, sx }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const [messageField, setMessageField] = useState<string>("");
|
const [messageField, setMessageField] = useState<string>("");
|
||||||
@ -135,6 +136,16 @@ export default function Chat({ sx }: Props) {
|
|||||||
[lastMessageId]
|
[lastMessageId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
const newMessages = messages.filter(({ shown }) => shown.me !== 1);
|
||||||
|
|
||||||
|
newMessages.map(async ({ id }) => {
|
||||||
|
await shownMessage(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [open, messages]);
|
||||||
|
|
||||||
async function handleSendMessage() {
|
async function handleSendMessage() {
|
||||||
if (!messageField || isMessageSending) return;
|
if (!messageField || isMessageSending) return;
|
||||||
|
|
||||||
@ -200,130 +211,134 @@ export default function Chat({ sx }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<>
|
||||||
sx={{
|
{open && (
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
height: "clamp(250px, calc(100vh - 90px), 600px)",
|
|
||||||
backgroundColor: "#944FEE",
|
|
||||||
borderRadius: "8px",
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "9px",
|
|
||||||
pl: "22px",
|
|
||||||
pt: "12px",
|
|
||||||
pb: "20px",
|
|
||||||
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UserCircleIcon />
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mt: "5px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "3px",
|
height: "clamp(250px, calc(100vh - 90px), 600px)",
|
||||||
|
backgroundColor: "#944FEE",
|
||||||
|
borderRadius: "8px",
|
||||||
|
...sx,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>Мария</Typography>
|
<Box
|
||||||
<Typography
|
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "16px",
|
display: "flex",
|
||||||
lineHeight: "19px",
|
gap: "9px",
|
||||||
|
pl: "22px",
|
||||||
|
pt: "12px",
|
||||||
|
pb: "20px",
|
||||||
|
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
онлайн-консультант
|
<UserCircleIcon />
|
||||||
</Typography>
|
<Box
|
||||||
</Box>
|
sx={{
|
||||||
</Box>
|
mt: "5px",
|
||||||
<Box
|
display: "flex",
|
||||||
sx={{
|
flexDirection: "column",
|
||||||
flexGrow: 1,
|
gap: "3px",
|
||||||
backgroundColor: "white",
|
}}
|
||||||
borderRadius: "8px",
|
>
|
||||||
display: "flex",
|
<Typography>Мария</Typography>
|
||||||
flexDirection: "column",
|
<Typography
|
||||||
}}
|
sx={{
|
||||||
>
|
fontSize: "16px",
|
||||||
<Box
|
lineHeight: "19px",
|
||||||
ref={chatBoxRef}
|
}}
|
||||||
sx={{
|
>
|
||||||
display: "flex",
|
онлайн-консультант
|
||||||
width: "100%",
|
</Typography>
|
||||||
flexBasis: 0,
|
</Box>
|
||||||
flexDirection: "column",
|
</Box>
|
||||||
gap: upMd ? "20px" : "16px",
|
<Box
|
||||||
px: upMd ? "20px" : "5px",
|
|
||||||
py: upMd ? "20px" : "13px",
|
|
||||||
overflowY: "auto",
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sessionData &&
|
|
||||||
messages.map((message) => (
|
|
||||||
<ChatMessage
|
|
||||||
unAuthenticated
|
|
||||||
key={message.id}
|
|
||||||
text={message.message}
|
|
||||||
createdAt={message.created_at}
|
|
||||||
isSelf={sessionData.sessionId === message.user_id}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
|
||||||
<InputBase
|
|
||||||
value={messageField}
|
|
||||||
fullWidth
|
|
||||||
placeholder="Введите сообщение..."
|
|
||||||
id="message"
|
|
||||||
multiline
|
|
||||||
onKeyDown={handleTextfieldKeyPress}
|
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
flexGrow: 1,
|
||||||
p: 0,
|
backgroundColor: "white",
|
||||||
|
borderRadius: "8px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
}}
|
}}
|
||||||
inputProps={{
|
>
|
||||||
sx: {
|
<Box
|
||||||
fontWeight: 400,
|
ref={chatBoxRef}
|
||||||
fontSize: "16px",
|
sx={{
|
||||||
lineHeight: "19px",
|
display: "flex",
|
||||||
pt: upMd ? "30px" : "28px",
|
width: "100%",
|
||||||
pb: upMd ? "30px" : "24px",
|
flexBasis: 0,
|
||||||
px: "19px",
|
flexDirection: "column",
|
||||||
maxHeight: "calc(19px * 5)",
|
gap: upMd ? "20px" : "16px",
|
||||||
color: "black",
|
px: upMd ? "20px" : "5px",
|
||||||
},
|
py: upMd ? "20px" : "13px",
|
||||||
}}
|
overflowY: "auto",
|
||||||
onChange={(e) => setMessageField(e.target.value)}
|
flexGrow: 1,
|
||||||
endAdornment={
|
}}
|
||||||
<InputAdornment position="end">
|
>
|
||||||
<IconButton
|
{sessionData &&
|
||||||
disabled={isMessageSending}
|
messages.map((message) => (
|
||||||
onClick={handleSendMessage}
|
<ChatMessage
|
||||||
sx={{
|
unAuthenticated
|
||||||
height: "53px",
|
key={message.id}
|
||||||
width: "53px",
|
text={message.message}
|
||||||
mr: "13px",
|
createdAt={message.created_at}
|
||||||
p: 0,
|
isSelf={sessionData.sessionId === message.user_id}
|
||||||
opacity: isMessageSending ? 0.3 : 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SendIcon
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
))}
|
||||||
</InputAdornment>
|
</Box>
|
||||||
}
|
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
||||||
/>
|
<InputBase
|
||||||
</FormControl>
|
value={messageField}
|
||||||
</Box>
|
fullWidth
|
||||||
</Box>
|
placeholder="Введите сообщение..."
|
||||||
|
id="message"
|
||||||
|
multiline
|
||||||
|
onKeyDown={handleTextfieldKeyPress}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
p: 0,
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
pt: upMd ? "30px" : "28px",
|
||||||
|
pb: upMd ? "30px" : "24px",
|
||||||
|
px: "19px",
|
||||||
|
maxHeight: "calc(19px * 5)",
|
||||||
|
color: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={(e) => setMessageField(e.target.value)}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
disabled={isMessageSending}
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
sx={{
|
||||||
|
height: "53px",
|
||||||
|
width: "53px",
|
||||||
|
mr: "13px",
|
||||||
|
p: 0,
|
||||||
|
opacity: isMessageSending ? 0.3 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SendIcon
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,94 +1,110 @@
|
|||||||
import { Box, Fab, Typography } from "@mui/material"
|
import { useState } from "react";
|
||||||
import { useState } from "react"
|
import { Box, Fab, Typography, Badge, useTheme } from "@mui/material";
|
||||||
import CircleDoubleDown from "./CircleDoubleDownIcon"
|
|
||||||
import Chat from "./Chat"
|
import CircleDoubleDown from "./CircleDoubleDownIcon";
|
||||||
|
import Chat from "./Chat";
|
||||||
|
|
||||||
|
import { useUnauthTicketStore } from "@root/stores/unauthTicket";
|
||||||
|
|
||||||
export default function FloatingSupportChat() {
|
export default function FloatingSupportChat() {
|
||||||
const [isChatOpened, setIsChatOpened] = useState<boolean>(false)
|
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
const { messages } = useUnauthTicketStore((state) => state);
|
||||||
|
|
||||||
const animation = {
|
const animation = {
|
||||||
"@keyframes runningStripe": {
|
"@keyframes runningStripe": {
|
||||||
"0%": {
|
"0%": {
|
||||||
left: "10%",
|
left: "10%",
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
"10%": {
|
"10%": {
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
},
|
},
|
||||||
"50%": {
|
"50%": {
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
transform: "translate(400px, 0)",
|
transform: "translate(400px, 0)",
|
||||||
},
|
},
|
||||||
"80%": {
|
"80%": {
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
},
|
},
|
||||||
|
|
||||||
"100%": {
|
"100%": {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
left: "100%",
|
left: "100%",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
right: "20px",
|
right: "20px",
|
||||||
bottom: "10px",
|
bottom: "10px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "8px",
|
gap: "8px",
|
||||||
width: "clamp(200px, 100% - 40px, 454px)",
|
width: "clamp(200px, 100% - 40px, 454px)",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isChatOpened && (
|
<Chat
|
||||||
<Chat
|
open={isChatOpened}
|
||||||
sx={{
|
sx={{ alignSelf: "start", width: "clamp(200px, 100%, 400px)" }}
|
||||||
alignSelf: "start",
|
/>
|
||||||
width: "clamp(200px, 100%, 400px)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Fab
|
|
||||||
disableRipple
|
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.7)",
|
|
||||||
pl: "11px",
|
|
||||||
pr: !isChatOpened ? "15px" : "11px",
|
|
||||||
gap: "11px",
|
|
||||||
height: "54px",
|
|
||||||
borderRadius: "27px",
|
|
||||||
alignSelf: "end",
|
|
||||||
overflow: "hidden",
|
|
||||||
"&:hover": {
|
|
||||||
background: "rgba(255, 255, 255, 0.7)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
variant={"extended"}
|
|
||||||
onClick={() => setIsChatOpened((prev) => !prev)}
|
|
||||||
>
|
|
||||||
{!isChatOpened && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
bgcolor: "#FFFFFF",
|
|
||||||
height: "100px",
|
|
||||||
width: "25px",
|
|
||||||
animation: "runningStripe linear 3s infinite",
|
|
||||||
transform: " skew(-10deg) rotate(70deg) skewX(20deg) skewY(10deg)",
|
|
||||||
boxShadow: "0px 3px 12px rgba(126, 42, 234, 0.1)",
|
|
||||||
opacity: "0.4",
|
|
||||||
...animation,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CircleDoubleDown isUp={isChatOpened} />
|
<Fab
|
||||||
{!isChatOpened && <Typography sx={{ zIndex: "10000" }}>Задайте нам вопрос</Typography>}
|
disableRipple
|
||||||
</Fab>
|
sx={{
|
||||||
</Box>
|
position: "relative",
|
||||||
)
|
backgroundColor: "rgba(255, 255, 255, 0.7)",
|
||||||
|
pl: "11px",
|
||||||
|
pr: !isChatOpened ? "15px" : "11px",
|
||||||
|
gap: "11px",
|
||||||
|
height: "54px",
|
||||||
|
borderRadius: "27px",
|
||||||
|
alignSelf: "end",
|
||||||
|
overflow: "hidden",
|
||||||
|
"&:hover": {
|
||||||
|
background: "rgba(255, 255, 255, 0.7)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
variant={"extended"}
|
||||||
|
onClick={() => setIsChatOpened((prev) => !prev)}
|
||||||
|
>
|
||||||
|
{!isChatOpened && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
bgcolor: "#FFFFFF",
|
||||||
|
height: "100px",
|
||||||
|
width: "25px",
|
||||||
|
animation: "runningStripe linear 3s infinite",
|
||||||
|
transform:
|
||||||
|
" skew(-10deg) rotate(70deg) skewX(20deg) skewY(10deg)",
|
||||||
|
boxShadow: "0px 3px 12px rgba(126, 42, 234, 0.1)",
|
||||||
|
opacity: "0.4",
|
||||||
|
...animation,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Badge
|
||||||
|
badgeContent={messages.filter(({ shown }) => shown.me !== 1).length}
|
||||||
|
sx={{
|
||||||
|
"& .MuiBadge-badge": {
|
||||||
|
display: isChatOpened ? "none" : "flex",
|
||||||
|
color: "#FFFFFF",
|
||||||
|
background: theme.palette.purple.main,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircleDoubleDown isUp={isChatOpened} />
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
{!isChatOpened && (
|
||||||
|
<Typography sx={{ zIndex: "10000" }}>Задайте нам вопрос</Typography>
|
||||||
|
)}
|
||||||
|
</Fab>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,24 @@ import { useState } from "react"
|
|||||||
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material"
|
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material"
|
||||||
|
|
||||||
import type { ChangeEvent } from "react"
|
import type { ChangeEvent } from "react"
|
||||||
|
import {Privilege} from "@frontend/kitui"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
value: number;
|
value: number;
|
||||||
adornmentText: string;
|
adornmentText: string;
|
||||||
|
privilege: Privilege;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NumberInputWithUnitAdornment({ id, value, adornmentText, onChange }: Props) {
|
const sliderSettingsByType = {
|
||||||
|
день: { max: 365, min: 0 },
|
||||||
|
шаблон: { max: 5000, min: 0 },
|
||||||
|
МБ: { max: 5000, min: 0 },
|
||||||
|
заявка: { max: 5000, min: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NumberInputWithUnitAdornment({ id, value, adornmentText, privilege, onChange }: Props) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const [changed, setChanged] = useState<boolean>(false)
|
const [changed, setChanged] = useState<boolean>(false)
|
||||||
|
|
||||||
@ -20,7 +29,10 @@ export default function NumberInputWithUnitAdornment({ id, value, adornmentText,
|
|||||||
size="small"
|
size="small"
|
||||||
placeholder="Введите вручную"
|
placeholder="Введите вручную"
|
||||||
id={id}
|
id={id}
|
||||||
value={changed ? (value !== 0 ? value : "") : ""}
|
onBlur={(e) => {e.target.value = String(Number(String(e.target.value).replace(/^0+(?=\d\.)/, '')))
|
||||||
|
console.log("сработало", e.target.value)
|
||||||
|
}}
|
||||||
|
value={changed ? (value !== sliderSettingsByType[privilege.value]?.min ? parseInt(String(value), 10) : sliderSettingsByType[privilege.value]?.min) : ""}
|
||||||
onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
|
onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
setChanged(true)
|
setChanged(true)
|
||||||
@ -34,7 +46,7 @@ export default function NumberInputWithUnitAdornment({ id, value, adornmentText,
|
|||||||
|
|
||||||
|
|
||||||
if (!isFinite(newNumber) || newNumber < 0) {
|
if (!isFinite(newNumber) || newNumber < 0) {
|
||||||
onChange(0)
|
onChange(sliderSettingsByType[privilege.value]?.min)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,10 @@ interface Props {
|
|||||||
name?: string;
|
name?: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
href?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TemplCardPhoneLight({name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = card1Image }: Props) {
|
export default function TemplCardPhoneLight({name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = card1Image, href = "#" }: Props) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600))
|
const isMobile = useMediaQuery(theme.breakpoints.down(600))
|
||||||
@ -54,7 +55,11 @@ export default function TemplCardPhoneLight({name="PenaDoc", desc="Самый у
|
|||||||
{desc}
|
{desc}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Button variant="pena-contained-light">Подробнее</Button>
|
<Button
|
||||||
|
variant="pena-contained-light"
|
||||||
|
target={"_blank"}
|
||||||
|
href={href}>
|
||||||
|
Подробнее</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
@ -9,9 +9,10 @@ interface Props {
|
|||||||
name?: string;
|
name?: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
href?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = cardImageBig }: Props) {
|
export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = cardImageBig, href="#" }: Props) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
|
||||||
|
|
||||||
@ -21,8 +22,7 @@ export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc="
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
py: "40px",
|
p: "40px 20px 20px 20px",
|
||||||
px: "20px",
|
|
||||||
backgroundColor: light ? "#E6E6EB" : "#434657",
|
backgroundColor: light ? "#E6E6EB" : "#434657",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
...sx,
|
...sx,
|
||||||
@ -33,12 +33,19 @@ export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc="
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "start",
|
alignItems: "start",
|
||||||
|
justifyContent: "space-between"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">{name}</Typography>
|
<Typography variant="h5">{name}</Typography>
|
||||||
<Typography sx={{ marginTop: isTablet ? "10px" : "20px" }} maxWidth="552px">
|
<Typography maxWidth="552px">
|
||||||
{desc}
|
{desc}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Button sx={{ width: "180px", height: "44px", p: 0 }}
|
||||||
|
variant="pena-contained-light"
|
||||||
|
target={"_blank"}
|
||||||
|
href={href}>
|
||||||
|
Подробнее
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<img
|
<img
|
||||||
src={image}
|
src={image}
|
||||||
|
@ -5,6 +5,7 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
|||||||
import Faq from "./pages/Faq/Faq"
|
import Faq from "./pages/Faq/Faq"
|
||||||
import Wallet from "./pages/Wallet"
|
import Wallet from "./pages/Wallet"
|
||||||
import Payment from "./pages/Payment/Payment"
|
import Payment from "./pages/Payment/Payment"
|
||||||
|
import QuizPayment from "./pages/QuizPayment/QuizPayment"
|
||||||
import Support from "./pages/Support/Support"
|
import Support from "./pages/Support/Support"
|
||||||
import AccountSettings from "./pages/AccountSettings/AccountSettings"
|
import AccountSettings from "./pages/AccountSettings/AccountSettings"
|
||||||
import Landing from "./pages/Landing/Landing"
|
import Landing from "./pages/Landing/Landing"
|
||||||
@ -70,6 +71,7 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(location)
|
||||||
if (location.state?.redirectTo)
|
if (location.state?.redirectTo)
|
||||||
return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />
|
return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />
|
||||||
|
|
||||||
@ -82,7 +84,6 @@ const App = () => {
|
|||||||
<Route path="/recover" element={<RecoverDialog />} />
|
<Route path="/recover" element={<RecoverDialog />} />
|
||||||
<Route path="/changepwd" element={<RecoverPassword />} />
|
<Route path="/changepwd" element={<RecoverPassword />} />
|
||||||
<Route path="/changepwd/expired" element={<OutdatedLink />} />
|
<Route path="/changepwd/expired" element={<OutdatedLink />} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
)}
|
)}
|
||||||
<Routes location={location.state?.backgroundLocation || location}>
|
<Routes location={location.state?.backgroundLocation || location}>
|
||||||
@ -92,6 +93,7 @@ const App = () => {
|
|||||||
<Route path="/recover" element={<Navigate to="/" replace state={{ redirectTo: "/recover" }} />} />
|
<Route path="/recover" element={<Navigate to="/" replace state={{ redirectTo: "/recover" }} />} />
|
||||||
<Route path="/changepwd" element={<Navigate to="/" replace state={{ redirectTo: window.location.pathname + window.location.search }} />} />
|
<Route path="/changepwd" element={<Navigate to="/" replace state={{ redirectTo: window.location.pathname + window.location.search }} />} />
|
||||||
<Route path="/changepwd/expired" element={<Navigate to="/" replace state={{ redirectTo: "/changepwd/expired" }} />} />
|
<Route path="/changepwd/expired" element={<Navigate to="/" replace state={{ redirectTo: "/changepwd/expired" }} />} />
|
||||||
|
|
||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
<Route element={<ProtectedLayout />}>
|
<Route element={<ProtectedLayout />}>
|
||||||
<Route path="/tariffs" element={<Tariffs />} />
|
<Route path="/tariffs" element={<Tariffs />} />
|
||||||
@ -110,6 +112,7 @@ const App = () => {
|
|||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/ppdd" element={<PPofData/>}/>
|
<Route path="/ppdd" element={<PPofData/>}/>
|
||||||
|
<Route path="/quizpayment" element={<QuizPayment/>} />
|
||||||
<Route element={<Docs />}>
|
<Route element={<Docs />}>
|
||||||
<Route path={"/docs/oferta"} element={<Oferta />}/>
|
<Route path={"/docs/oferta"} element={<Oferta />}/>
|
||||||
<Route path={"/docs/privacy"} element={<PrivacyPolicy />}/>
|
<Route path={"/docs/privacy"} element={<PrivacyPolicy />}/>
|
||||||
|
@ -28,6 +28,7 @@ export default function JuridicalDocumentsDialog() {
|
|||||||
const isOpen = useUserStore((state) => state.isDocumentsDialogOpen)
|
const isOpen = useUserStore((state) => state.isDocumentsDialogOpen)
|
||||||
const verificationStatus = useUserStore((state) => state.verificationStatus)
|
const verificationStatus = useUserStore((state) => state.verificationStatus)
|
||||||
const documents = useUserStore((state) => state.documents)//загруженные юзером файлы
|
const documents = useUserStore((state) => state.documents)//загруженные юзером файлы
|
||||||
|
console.log(documents)
|
||||||
const documentsUrl = useUserStore((state) => state.documentsUrl)//ссылки с бекенда
|
const documentsUrl = useUserStore((state) => state.documentsUrl)//ссылки с бекенда
|
||||||
const userId = useUserStore((state) => state.userId) ?? ""
|
const userId = useUserStore((state) => state.userId) ?? ""
|
||||||
|
|
||||||
|
@ -48,6 +48,9 @@ export const verify = async (id: string) => {
|
|||||||
const [verificationResult, verificationError] = await verification(id)
|
const [verificationResult, verificationError] = await verification(id)
|
||||||
|
|
||||||
if (verificationError) {
|
if (verificationError) {
|
||||||
|
|
||||||
|
if (verificationError === "нет данных") return
|
||||||
|
|
||||||
setVerificationStatus(VerificationStatus.NOT_VERIFICATED)
|
setVerificationStatus(VerificationStatus.NOT_VERIFICATED)
|
||||||
|
|
||||||
devlog("Error fetching user", verificationError)
|
devlog("Error fetching user", verificationError)
|
||||||
|
22
src/pages/ApologyPage.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
export const ApologyPage = ({ message }: { message: string }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{message || "что-то пошло не так"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -14,7 +14,7 @@ const name: Record<string, string> = {
|
|||||||
templategen: "Шаблонизатор",
|
templategen: "Шаблонизатор",
|
||||||
squiz: "Опросник",
|
squiz: "Опросник",
|
||||||
reducer: "Сокращатель ссылок",
|
reducer: "Сокращатель ссылок",
|
||||||
custom: "Кастомные тарифы",
|
custom: "Мои тарифы",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -18,7 +18,8 @@ import EmailIcon from '@mui/icons-material/Email';
|
|||||||
import {enqueueSnackbar} from "notistack"
|
import {enqueueSnackbar} from "notistack"
|
||||||
import { makeRequest } from "@frontend/kitui"
|
import { makeRequest } from "@frontend/kitui"
|
||||||
|
|
||||||
const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
|
const subPages = ["Платежи"]
|
||||||
|
// const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
|
||||||
|
|
||||||
export default function History() {
|
export default function History() {
|
||||||
const [selectedItem, setSelectedItem] = useState<number>(0)
|
const [selectedItem, setSelectedItem] = useState<number>(0)
|
||||||
@ -93,6 +94,7 @@ export default function History() {
|
|||||||
}
|
}
|
||||||
onError={handleComponentError}
|
onError={handleComponentError}
|
||||||
>
|
>
|
||||||
|
{historyData?.length === 0 && <Typography textAlign="center" >Нет данных</Typography>}
|
||||||
{historyData?.filter((e) => {
|
{historyData?.filter((e) => {
|
||||||
e.createdAt = extractDateFromString(e.createdAt)
|
e.createdAt = extractDateFromString(e.createdAt)
|
||||||
return(!e.isDeleted && e.key === "payCart" && Array.isArray(e.rawDetails[0].Value)
|
return(!e.isDeleted && e.key === "payCart" && Array.isArray(e.rawDetails[0].Value)
|
||||||
|
@ -47,7 +47,10 @@ export default function Section5() {
|
|||||||
gap: upMd ? "24px" : "20px",
|
gap: upMd ? "24px" : "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button sx={{ width: "180px", height: "44px", p: 0 }} variant="pena-contained-light">
|
<Button sx={{ width: "180px", height: "44px", p: 0 }}
|
||||||
|
variant="pena-contained-light"
|
||||||
|
target={"_blank"}
|
||||||
|
href={"https://pena.digital/"}>
|
||||||
Наши услуги
|
Наши услуги
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,241 +1,292 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material"
|
} from "@mui/material";
|
||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack"
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
import SectionWrapper from "@components/SectionWrapper"
|
import SectionWrapper from "@components/SectionWrapper";
|
||||||
import PaymentMethodCard from "./PaymentMethodCard"
|
import PaymentMethodCard from "./PaymentMethodCard";
|
||||||
import mastercardLogo from "../../assets/bank-logo/logo-mastercard.png"
|
import mastercardLogo from "@root/assets/bank-logo/logo-mastercard.png";
|
||||||
import visaLogo from "../../assets/bank-logo/logo-visa.png"
|
import visaLogo from "@root/assets/bank-logo/logo-visa.png";
|
||||||
import qiwiLogo from "../../assets/bank-logo/logo-qiwi.png"
|
import qiwiLogo from "@root/assets/bank-logo/logo-qiwi.png";
|
||||||
import mirLogo from "../../assets/bank-logo/logo-mir.png"
|
import mirLogo from "@root/assets/bank-logo/logo-mir.png";
|
||||||
import tinkoffLogo from "../../assets/bank-logo/logo-tinkoff.png"
|
import tinkoffLogo from "@root/assets/bank-logo/logo-tinkoff.png";
|
||||||
import { cardShadow } from "@root/utils/theme"
|
import rsPayLogo from "@root/assets/bank-logo/rs-pay.png";
|
||||||
import { useEffect, useLayoutEffect, useState } from "react"
|
import { cardShadow } from "@root/utils/theme";
|
||||||
import InputTextfield from "@root/components/InputTextfield"
|
import { useEffect, useLayoutEffect, useState } from "react";
|
||||||
import { sendPayment } from "@root/api/wallet"
|
import InputTextfield from "@root/components/InputTextfield";
|
||||||
import { getMessageFromFetchError } from "@frontend/kitui"
|
import { sendPayment, sendRSPayment } from "@root/api/wallet";
|
||||||
import { enqueueSnackbar } from "notistack"
|
import { getMessageFromFetchError } from "@frontend/kitui";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter"
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useLocation, useNavigate } from "react-router-dom"
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker"
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
|
||||||
|
import { useUserStore } from "@root/stores/user";
|
||||||
|
import { VerificationStatus } from "@root/model/account";
|
||||||
|
import { WarnModal } from "./WarnModal";
|
||||||
|
|
||||||
const paymentMethods = [
|
type PaymentMethod = {
|
||||||
{ name: "Mastercard", image: mastercardLogo },
|
label: string;
|
||||||
{ name: "Visa", image: visaLogo },
|
name: string;
|
||||||
{ name: "QIWI Кошелек", image: qiwiLogo },
|
image: string;
|
||||||
{ name: "Мир", image: mirLogo },
|
unpopular?: boolean;
|
||||||
{ name: "Тинькофф", image: tinkoffLogo },
|
};
|
||||||
] as const
|
|
||||||
|
|
||||||
type PaymentMethod = (typeof paymentMethods)[number]["name"];
|
const paymentMethods: PaymentMethod[] = [
|
||||||
|
{ label: "Mastercard", name: "mastercard", image: mastercardLogo },
|
||||||
|
{ label: "Visa", name: "visa", image: visaLogo },
|
||||||
|
{ label: "QIWI Кошелек", name: "qiwi", image: qiwiLogo },
|
||||||
|
{ label: "Мир", name: "mir", image: mirLogo },
|
||||||
|
{ label: "Тинькофф", name: "tinkoff", image: tinkoffLogo },
|
||||||
|
];
|
||||||
|
|
||||||
|
type PaymentMethodType = (typeof paymentMethods)[number]["name"];
|
||||||
|
|
||||||
export default function Payment() {
|
export default function Payment() {
|
||||||
const theme = useTheme()
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
||||||
useState<PaymentMethod | null>(null)
|
useState<PaymentMethodType | null>(null);
|
||||||
const [paymentValueField, setPaymentValueField] = useState<string>("0")
|
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
|
||||||
const [paymentLink, setPaymentLink] = useState<string>("")
|
const [paymentValueField, setPaymentValueField] = useState<string>("0");
|
||||||
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false)
|
const [paymentLink, setPaymentLink] = useState<string>("");
|
||||||
const location = useLocation()
|
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
|
||||||
|
const location = useLocation();
|
||||||
|
const verificationStatus = useUserStore((state) => state.verificationStatus);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const notEnoughMoneyAmount =
|
const notEnoughMoneyAmount =
|
||||||
(location.state?.notEnoughMoneyAmount as number) ?? 0
|
(location.state?.notEnoughMoneyAmount as number) ?? 0;
|
||||||
|
|
||||||
const paymentValue = parseFloat(paymentValueField) * 100
|
const paymentValue = parseFloat(paymentValueField) * 100;
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
setPaymentValueField((notEnoughMoneyAmount / 100).toString())
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
|
||||||
const fromSquiz = params.get("action")
|
|
||||||
if (fromSquiz === "squizpay") {
|
|
||||||
setIsFromSquiz(true)
|
|
||||||
setPaymentValueField(params.get("dif") || "0")
|
|
||||||
}
|
|
||||||
console.log(fromSquiz)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setPaymentLink("")
|
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
|
||||||
}, [selectedPaymentMethod])
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const fromSquiz = params.get("action");
|
||||||
|
if (fromSquiz === "squizpay") {
|
||||||
|
setIsFromSquiz(true);
|
||||||
|
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString());
|
||||||
|
}
|
||||||
|
history.pushState(null, document.title, "/payment");
|
||||||
|
console.log(fromSquiz);
|
||||||
|
}, []);
|
||||||
|
|
||||||
async function handleChoosePaymentClick() {
|
useEffect(() => {
|
||||||
if (Number(paymentValueField) !== 0) {
|
setPaymentLink("");
|
||||||
const [sendPaymentResponse, sendPaymentError] = await sendPayment({fromSquiz})
|
}, [selectedPaymentMethod]);
|
||||||
|
|
||||||
if (sendPaymentError) {
|
async function handleChoosePaymentClick() {
|
||||||
return enqueueSnackbar(sendPaymentError)
|
if (Number(paymentValueField) === 0) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (sendPaymentResponse) {
|
if (selectedPaymentMethod !== "rspay") {
|
||||||
setPaymentLink(sendPaymentResponse.link)
|
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
|
||||||
}
|
fromSquiz,
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const handleCustomBackNavigation = useHistoryTracker()
|
if (sendPaymentError) {
|
||||||
|
return enqueueSnackbar(sendPaymentError);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
if (sendPaymentResponse) {
|
||||||
<SectionWrapper
|
setPaymentLink(sendPaymentResponse.link);
|
||||||
maxWidth="lg"
|
}
|
||||||
sx={{
|
|
||||||
mt: "25px",
|
return;
|
||||||
mb: "70px",
|
}
|
||||||
px: isTablet ? (upMd ? "40px" : "18px") : "20px",
|
|
||||||
}}
|
}
|
||||||
>
|
|
||||||
<Box
|
const handleCustomBackNavigation = useHistoryTracker();
|
||||||
sx={{
|
|
||||||
mt: "20px",
|
return (
|
||||||
mb: "40px",
|
<SectionWrapper
|
||||||
display: "flex",
|
maxWidth="lg"
|
||||||
gap: "10px",
|
sx={{
|
||||||
}}
|
mt: "25px",
|
||||||
>
|
mb: "70px",
|
||||||
{!upMd && (
|
px: isTablet ? (upMd ? "40px" : "18px") : "20px",
|
||||||
<IconButton
|
}}
|
||||||
onClick={handleCustomBackNavigation}
|
>
|
||||||
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
|
<Box
|
||||||
>
|
sx={{
|
||||||
<ArrowBackIcon />
|
mt: "20px",
|
||||||
</IconButton>
|
mb: "40px",
|
||||||
)}
|
display: "flex",
|
||||||
<Typography variant="h4">Способ оплаты</Typography>
|
gap: "10px",
|
||||||
</Box>
|
}}
|
||||||
{!upMd && (
|
>
|
||||||
<Typography variant="body2" mb="30px">
|
{!upMd && (
|
||||||
|
<IconButton
|
||||||
|
onClick={handleCustomBackNavigation}
|
||||||
|
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Typography variant="h4">Способ оплаты</Typography>
|
||||||
|
</Box>
|
||||||
|
{!upMd && (
|
||||||
|
<Typography variant="body2" mb="30px">
|
||||||
Выберите способ оплаты
|
Выберите способ оплаты
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: upMd ? "white" : undefined,
|
backgroundColor: upMd ? "white" : undefined,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: upMd ? "row" : "column",
|
flexDirection: upMd ? "row" : "column",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow: upMd ? cardShadow : undefined,
|
boxShadow: upMd ? cardShadow : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: upMd ? "68.5%" : undefined,
|
width: upMd ? "68.5%" : undefined,
|
||||||
p: upMd ? "20px" : undefined,
|
p: upMd ? "20px" : undefined,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: upSm ? "row" : "column",
|
flexDirection: upSm ? "row" : "column",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
gap: upMd ? "14px" : "20px",
|
gap: upMd ? "14px" : "20px",
|
||||||
alignContent: "start",
|
alignContent: "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{paymentMethods.map((method) => (
|
{paymentMethods.map(({ name, label, image, unpopular = false }) => (
|
||||||
<PaymentMethodCard
|
<PaymentMethodCard
|
||||||
isSelected={selectedPaymentMethod === method.name}
|
isSelected={selectedPaymentMethod === name}
|
||||||
key={method.name}
|
key={name}
|
||||||
name={method.name}
|
label={label}
|
||||||
image={method.image}
|
image={image}
|
||||||
onClick={() => setSelectedPaymentMethod(method.name)}
|
onClick={() => setSelectedPaymentMethod(name)}
|
||||||
/>
|
unpopular={unpopular}
|
||||||
))}
|
/>
|
||||||
</Box>
|
))}
|
||||||
<Box
|
<PaymentMethodCard
|
||||||
sx={{
|
isSelected={false}
|
||||||
display: "flex",
|
label={"Расчётный счёт"}
|
||||||
flexDirection: "column",
|
image={rsPayLogo}
|
||||||
justifyContent: "space-between",
|
onClick={async() => {
|
||||||
alignItems: "start",
|
|
||||||
color: theme.palette.gray.dark,
|
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
width: upMd ? "31.5%" : undefined,
|
setWarnModalOpen(true);
|
||||||
p: upMd ? "20px" : undefined,
|
|
||||||
pl: upMd ? "33px" : undefined,
|
return;
|
||||||
mt: upMd ? undefined : "30px",
|
}
|
||||||
borderLeft: upMd
|
|
||||||
? `1px solid ${theme.palette.gray.main}`
|
const sendRSPaymentError = await sendRSPayment();
|
||||||
: undefined,
|
|
||||||
}}
|
if (sendRSPaymentError) {
|
||||||
>
|
return enqueueSnackbar(sendRSPaymentError);
|
||||||
<Box
|
}
|
||||||
sx={{
|
|
||||||
display: "flex",
|
enqueueSnackbar(
|
||||||
flexDirection: "column",
|
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
|
||||||
maxWidth: "85%",
|
);
|
||||||
}}
|
|
||||||
>
|
navigate("/settings");
|
||||||
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
|
}}
|
||||||
<Typography mb="20px">К оплате</Typography>
|
unpopular={true}
|
||||||
{paymentLink ? (
|
/>
|
||||||
<Typography
|
</Box>
|
||||||
sx={{
|
<Box
|
||||||
fontWeight: 500,
|
sx={{
|
||||||
fontSize: "20px",
|
display: "flex",
|
||||||
lineHeight: "48px",
|
flexDirection: "column",
|
||||||
mb: "28px",
|
justifyContent: "space-between",
|
||||||
}}
|
alignItems: "start",
|
||||||
>
|
color: theme.palette.gray.dark,
|
||||||
{currencyFormatter.format(paymentValue / 100)}
|
width: upMd ? "31.5%" : undefined,
|
||||||
</Typography>
|
p: upMd ? "20px" : undefined,
|
||||||
) : (
|
pl: upMd ? "33px" : undefined,
|
||||||
<InputTextfield
|
mt: upMd ? undefined : "30px",
|
||||||
TextfieldProps={{
|
borderLeft: upMd
|
||||||
placeholder: "К оплате",
|
? `1px solid ${theme.palette.gray.main}`
|
||||||
value: paymentValueField,
|
: undefined,
|
||||||
type: "number",
|
}}
|
||||||
}}
|
>
|
||||||
onChange={(e) => setPaymentValueField(e.target.value)}
|
<Box
|
||||||
id="payment-amount"
|
sx={{
|
||||||
gap={upMd ? "16px" : "10px"}
|
display: "flex",
|
||||||
color={"#F2F3F7"}
|
flexDirection: "column",
|
||||||
FormInputSx={{ mb: "28px" }}
|
maxWidth: "85%",
|
||||||
/>
|
}}
|
||||||
)}
|
>
|
||||||
</Box>
|
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
|
||||||
{paymentLink ? (
|
<Typography mb="20px">К оплате</Typography>
|
||||||
<Button
|
{paymentLink ? (
|
||||||
variant="pena-outlined-light"
|
<Typography
|
||||||
component="a"
|
sx={{
|
||||||
href={paymentLink}
|
fontWeight: 500,
|
||||||
sx={{
|
fontSize: "20px",
|
||||||
mt: "auto",
|
lineHeight: "48px",
|
||||||
color: "black",
|
mb: "28px",
|
||||||
border: `1px solid ${theme.palette.purple.main}`,
|
}}
|
||||||
"&:hover": {
|
>
|
||||||
backgroundColor: theme.palette.purple.dark,
|
{currencyFormatter.format(paymentValue / 100)}
|
||||||
border: `1px solid ${theme.palette.purple.dark}`,
|
</Typography>
|
||||||
},
|
) : (
|
||||||
}}
|
<InputTextfield
|
||||||
>
|
TextfieldProps={{
|
||||||
|
placeholder: "К оплате",
|
||||||
|
value: paymentValueField,
|
||||||
|
type: "number",
|
||||||
|
}}
|
||||||
|
onChange={(e) => setPaymentValueField(e.target.value)}
|
||||||
|
id="payment-amount"
|
||||||
|
gap={upMd ? "16px" : "10px"}
|
||||||
|
color={"#F2F3F7"}
|
||||||
|
FormInputSx={{ mb: "28px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{paymentLink ? (
|
||||||
|
<Button
|
||||||
|
variant="pena-outlined-light"
|
||||||
|
component="a"
|
||||||
|
href={paymentLink}
|
||||||
|
sx={{
|
||||||
|
mt: "auto",
|
||||||
|
color: "black",
|
||||||
|
border: `1px solid ${theme.palette.purple.main}`,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: theme.palette.purple.dark,
|
||||||
|
border: `1px solid ${theme.palette.purple.dark}`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
Оплатить
|
Оплатить
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
variant="pena-outlined-light"
|
variant="pena-outlined-light"
|
||||||
disabled={!isFinite(paymentValue)}
|
disabled={!isFinite(paymentValue)}
|
||||||
onClick={handleChoosePaymentClick}
|
onClick={handleChoosePaymentClick}
|
||||||
sx={{
|
sx={{
|
||||||
mt: "auto",
|
mt: "auto",
|
||||||
color: "black",
|
color: "black",
|
||||||
border: `1px solid ${theme.palette.purple.main}`,
|
border: `1px solid ${theme.palette.purple.main}`,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
color: "white",
|
color: "white",
|
||||||
},
|
},
|
||||||
"&:active": {
|
"&:active": {
|
||||||
color: "white",
|
color: "white",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Выбрать
|
Выбрать
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</SectionWrapper>
|
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
|
||||||
)
|
</SectionWrapper>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,55 @@
|
|||||||
import { Button, Typography, useMediaQuery, useTheme } from "@mui/material"
|
import { Button, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
label: string;
|
||||||
image: string;
|
image: string;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
onClick: () => void;
|
unpopular?: boolean;
|
||||||
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PaymentMethodCard({ name, image, isSelected, onClick }: Props) {
|
export default function PaymentMethodCard({
|
||||||
const theme = useTheme()
|
label,
|
||||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
|
image,
|
||||||
|
isSelected,
|
||||||
|
unpopular,
|
||||||
|
onClick,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
width: upSm ? "237px" : "100%",
|
width: upSm ? "237px" : "100%",
|
||||||
p: "20px",
|
p: "20px",
|
||||||
pr: "10px",
|
pr: "10px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "start",
|
justifyContent: "start",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
backgroundColor: theme.palette.background.default,
|
filter: unpopular ? "saturate(0.6) brightness(0.85)" : null,
|
||||||
border: isSelected ? `1px solid ${theme.palette.purple.main}` : `1px solid ${theme.palette.gray.main}`,
|
backgroundColor: theme.palette.background.default,
|
||||||
gap: "20px",
|
border: isSelected
|
||||||
alignItems: "center",
|
? `1px solid ${theme.palette.purple.main}`
|
||||||
flexWrap: "wrap",
|
: `1px solid ${theme.palette.gray.main}`,
|
||||||
boxShadow: isSelected ? `0 0 0 1.5px ${theme.palette.purple.main};` : "none",
|
gap: "15px",
|
||||||
"&:hover": {
|
alignItems: "center",
|
||||||
backgroundColor: theme.palette.purple.main,
|
flexWrap: "wrap",
|
||||||
border: `1px solid ${theme.palette.purple.main}`,
|
boxShadow: isSelected
|
||||||
"& > p": {
|
? `0 0 0 1.5px ${theme.palette.purple.main};`
|
||||||
color: "white",
|
: "none",
|
||||||
}
|
"&:hover": {
|
||||||
},
|
backgroundColor: theme.palette.purple.main,
|
||||||
}}
|
border: `1px solid ${theme.palette.purple.main}`,
|
||||||
onClick={onClick}
|
"& > p": {
|
||||||
>
|
color: "white",
|
||||||
<img src={image} alt="payment method" />
|
},
|
||||||
<Typography sx={{ color: theme.palette.gray.dark }}>{name}</Typography>
|
},
|
||||||
</Button>
|
}}
|
||||||
)
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<img src={image} alt="payment method" />
|
||||||
|
<Typography sx={{ color: theme.palette.gray.dark }}>{label}</Typography>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
61
src/pages/Payment/WarnModal.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Modal, Box, Typography, Button, useTheme } from "@mui/material";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
type WarnModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (isOpen: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WarnModal = ({ open, setOpen }: WarnModalProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
margin: "10px",
|
||||||
|
padding: "25px",
|
||||||
|
maxWidth: "600px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
textAlign: "center",
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||||
|
Верификация не пройдена.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "20px",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="pena-outlined-purple" onClick={() => setOpen(false)}>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="pena-outlined-purple"
|
||||||
|
onClick={() => navigate("/settings")}
|
||||||
|
>
|
||||||
|
Пройти верификацию
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
72
src/pages/QuizPayment/QuizPayment.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import axios, { AxiosResponse } from "axios"
|
||||||
|
import { ApologyPage } from "../ApologyPage"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { clearAuthToken, getMessageFromFetchError, setAuthToken, useUserAccountFetcher, useUserFetcher } from "@frontend/kitui";
|
||||||
|
import { clearUserData, setUser, setUserAccount, useUserStore } from "@root/stores/user";
|
||||||
|
|
||||||
|
|
||||||
|
function refresh(token: string) {
|
||||||
|
return axios<never, AxiosResponse<{ accessToken: string; }>>(process.env.REACT_APP_DOMAIN + "/auth/refresh", {
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer " + token,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const action = params.get("action")
|
||||||
|
const dif = params.get("dif")
|
||||||
|
const token = params.get("data")
|
||||||
|
const userId = params.get("userid")
|
||||||
|
|
||||||
|
|
||||||
|
let first = true
|
||||||
|
|
||||||
|
export default function QuizPayment() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [message, setMessage] = useState("Идёт загрузка")
|
||||||
|
|
||||||
|
console.log("Я начал работать")
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
history.pushState(null, document.title, "/quizpayment");
|
||||||
|
try {
|
||||||
|
first = false
|
||||||
|
if (action && dif && token) {
|
||||||
|
(async () => {
|
||||||
|
// const data = await refresh(token)
|
||||||
|
console.log(token)
|
||||||
|
setAuthToken(token)
|
||||||
|
// setAuthToken(data.data.accessToken)
|
||||||
|
console.log("делаем юзера")
|
||||||
|
|
||||||
|
useUserFetcher({
|
||||||
|
url: process.env.REACT_APP_DOMAIN + `/user/${userId}`,
|
||||||
|
userId,
|
||||||
|
onNewUser: (user) => {
|
||||||
|
setUser(user)
|
||||||
|
navigate(`/payment?action=${action}&dif=${dif}`, { replace: true })
|
||||||
|
|
||||||
|
},
|
||||||
|
onError: () => { },
|
||||||
|
})
|
||||||
|
return
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
setMessage("Произошла ошибка")
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.href = "https://quiz.pena.digital/tariffs";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ApologyPage message={message} />
|
||||||
|
)
|
||||||
|
};
|
@ -44,6 +44,7 @@ function TariffConstructor() {
|
|||||||
>
|
>
|
||||||
{Object.entries(customTariffs).filter(([serviceKey]) => serviceKey === "squiz").map(([serviceKey, privileges], index) => {
|
{Object.entries(customTariffs).filter(([serviceKey]) => serviceKey === "squiz").map(([serviceKey, privileges], index) => {
|
||||||
console.log("serviceKey ",serviceKey)
|
console.log("serviceKey ",serviceKey)
|
||||||
|
console.log(Object.entries(customTariffs))
|
||||||
return <Box key={index}>
|
return <Box key={index}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -65,7 +66,7 @@ function TariffConstructor() {
|
|||||||
<ArrowBackIcon />
|
<ArrowBackIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
<ComplexHeader text1="Мой тариф " text2={serviceNameByKey[serviceKey]} />
|
<ComplexHeader text1="Мой тариф " text2={serviceNameByKey[serviceKey] === "Опросник" ? "PenaQuiz" : serviceNameByKey[serviceKey]} />
|
||||||
</Box>
|
</Box>
|
||||||
<CustomTariffCard serviceKey={serviceKey} privileges={privileges} />
|
<CustomTariffCard serviceKey={serviceKey} privileges={privileges} />
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -13,11 +13,12 @@ import { useEffect, useState } from "react"
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
const sliderSettingsByType = {
|
const sliderSettingsByType = {
|
||||||
день: { max: 365, min: 30 },
|
день: { max: 365, min: 0 },
|
||||||
шаблон: { max: 5000, min: 100 },
|
шаблон: { max: 5000, min: 0 },
|
||||||
МБ: { max: 5000, min: 100 },
|
МБ: { max: 5000, min: 0 },
|
||||||
|
заявка: { max: 5000, min: 0 }
|
||||||
}
|
}
|
||||||
type PrivilegeName = "день" | "шаблон" | "МБ"
|
type PrivilegeName = "день" | "шаблон" | "МБ" | "заявка"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
privilege: Privilege;
|
privilege: Privilege;
|
||||||
@ -26,10 +27,10 @@ interface Props {
|
|||||||
export default function TariffPrivilegeSlider({ privilege }: Props) {
|
export default function TariffPrivilegeSlider({ privilege }: Props) {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
||||||
const userValue = useCustomTariffsStore((state) => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? 0
|
const userValue = useCustomTariffsStore((state) => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? sliderSettingsByType[privilege.value]?.min
|
||||||
const discounts = useDiscountStore((state) => state.discounts)
|
const discounts = useDiscountStore((state) => state.discounts)
|
||||||
const currentCartTotal = useCartStore((state) => state.cart.priceAfterDiscounts)
|
const currentCartTotal = useCartStore((state) => state.cart.priceAfterDiscounts)
|
||||||
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0
|
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? sliderSettingsByType[privilege.value]?.min
|
||||||
const isUserNko = useUserStore(state => state.userAccount?.status) === "nko"
|
const isUserNko = useUserStore(state => state.userAccount?.status) === "nko"
|
||||||
const [value, setValue] = useState<number>(userValue)
|
const [value, setValue] = useState<number>(userValue)
|
||||||
const throttledValue = useThrottle(value, 200)
|
const throttledValue = useThrottle(value, 200)
|
||||||
@ -67,10 +68,17 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
|
|||||||
|
|
||||||
|
|
||||||
const setNotSmallNumber = useDebouncedCallback(() => {
|
const setNotSmallNumber = useDebouncedCallback(() => {
|
||||||
if (value === 0) return
|
if (value === sliderSettingsByType[privilege.value]?.min) return
|
||||||
if (Number(value) < Number(sliderSettingsByType[privilege.value]?.min)) {
|
if (Number(value) < Number(sliderSettingsByType[privilege.value]?.min)) {
|
||||||
setValue(sliderSettingsByType[privilege.value]?.min)
|
setValue(sliderSettingsByType[privilege.value]?.min)
|
||||||
}
|
}
|
||||||
|
if (privilege.value === "день" && Number(value) < 30 && Number(value) !== 0) {
|
||||||
|
setValue(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privilege.value !== "день" && Number(value) < 100 && Number(value) !== 0) {
|
||||||
|
setValue(100)
|
||||||
|
}
|
||||||
}, 600)
|
}, 600)
|
||||||
|
|
||||||
const quantityElement = (
|
const quantityElement = (
|
||||||
@ -99,6 +107,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
|
|||||||
<NumberInputWithUnitAdornment
|
<NumberInputWithUnitAdornment
|
||||||
id={"privilege_input_" + privilege._id}
|
id={"privilege_input_" + privilege._id}
|
||||||
value={value}
|
value={value}
|
||||||
|
privilege={privilege}
|
||||||
adornmentText={getDeclension(0, privilege.value)}
|
adornmentText={getDeclension(0, privilege.value)}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
@ -152,9 +161,10 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
|
|||||||
</Box>
|
</Box>
|
||||||
<CustomSlider
|
<CustomSlider
|
||||||
value={value}
|
value={value}
|
||||||
min={0}
|
min={sliderSettingsByType[privilege.value]?.min }
|
||||||
max={sliderSettingsByType[privilege.value]?.max || 100}
|
max={sliderSettingsByType[privilege.value]?.max || 100}
|
||||||
onChange={handleSliderChange(privilege.value)}
|
onChange={handleSliderChange(privilege.value)}
|
||||||
|
firstStep={privilege.value === "день" ? 30 : 100}
|
||||||
/>
|
/>
|
||||||
{!upMd && quantityElement}
|
{!upMd && quantityElement}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -85,7 +85,22 @@ export default function Tariffs() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/*{upMd ? <WideTemplCard sx={{ marginTop: "55px" }} /> : <TemplCardPhoneLight />}*/}
|
{/*{upMd ? <WideTemplCard sx={{ marginTop: "55px" }} /> : <TemplCardPhoneLight />}*/}
|
||||||
{upMd ? <WideTemplCard sx={{ marginTop: "55px" }} name={"PenaQuiz"} desc={"Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"} image={CardImage2}/> : <TemplCardPhoneLight name={"PenaQuiz"} desc={"Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"} image={CardImage2}/>}
|
{upMd ?
|
||||||
|
<WideTemplCard
|
||||||
|
sx={{ marginTop: "55px" }}
|
||||||
|
name={"PenaQuiz"}
|
||||||
|
desc={"Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"}
|
||||||
|
image={CardImage2}
|
||||||
|
href={"https://quiz.pena.digital"}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<TemplCardPhoneLight
|
||||||
|
name={"PenaQuiz"}
|
||||||
|
desc={"Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"}
|
||||||
|
image={CardImage2}
|
||||||
|
href={"https://quiz.pena.digital"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -178,11 +178,11 @@ function TariffPage() {
|
|||||||
{StepperText[unit]}
|
{StepperText[unit]}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{isMobile ? (
|
{/* {isMobile ? (
|
||||||
<Select items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
|
<Select items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
|
||||||
) : (
|
) : (
|
||||||
<Tabs items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
|
<Tabs items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
|
||||||
)}
|
)} */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
justifyContent: "left",
|
justifyContent: "left",
|
||||||
@ -195,20 +195,20 @@ function TariffPage() {
|
|||||||
>
|
>
|
||||||
{createTariffElements(filteredTariffs, true)}
|
{createTariffElements(filteredTariffs, true)}
|
||||||
</Box>
|
</Box>
|
||||||
{recentlyPurchased.length > 0 && (
|
{/*{recentlyPurchased.length > 0 && (*/}
|
||||||
<>
|
{/* <>*/}
|
||||||
<Typography
|
{/* <Typography*/}
|
||||||
sx={{
|
{/* sx={{*/}
|
||||||
mt: "40px",
|
{/* mt: "40px",*/}
|
||||||
fontSize: isMobile ? "24px" : "36px",
|
{/* fontSize: isMobile ? "24px" : "36px",*/}
|
||||||
fontWeight: "500",
|
{/* fontWeight: "500",*/}
|
||||||
}}
|
{/* }}*/}
|
||||||
>
|
{/* >*/}
|
||||||
Ранее вы
|
{/* Ранее вы*/}
|
||||||
</Typography>
|
{/* </Typography>*/}
|
||||||
<Slider items={createTariffElements(recentlyPurchased)} />
|
{/* <Slider items={createTariffElements(recentlyPurchased)} />*/}
|
||||||
</>
|
{/* </>*/}
|
||||||
)}
|
{/*)}*/}
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ const validationSchema = object({
|
|||||||
.email("Введите корректный email"),
|
.email("Введите корректный email"),
|
||||||
password: string()
|
password: string()
|
||||||
.min(8, "Минимум 8 символов")
|
.min(8, "Минимум 8 символов")
|
||||||
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
.matches(/^[.,:;\-_+!&()<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/, "Некорректные символы")
|
||||||
.required("Поле обязательно"),
|
.required("Поле обязательно"),
|
||||||
repeatPassword: string()
|
repeatPassword: string()
|
||||||
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
||||||
|
@ -2,5 +2,5 @@ export const serviceNameByKey: Record<string, string | undefined> = {
|
|||||||
templategen: "Шаблонизатор",
|
templategen: "Шаблонизатор",
|
||||||
squiz: "Опросник",
|
squiz: "Опросник",
|
||||||
reducer: "Сокращатель ссылок",
|
reducer: "Сокращатель ссылок",
|
||||||
custom: "Кастомные тарифы",
|
custom: "Мои тарифы",
|
||||||
}
|
}
|
||||||
|