add husky && eslinter

This commit is contained in:
Nastya 2023-11-06 02:33:40 +03:00
parent 69e677ccaa
commit d69d27acb4
164 changed files with 12282 additions and 11471 deletions

34
.eslintrc.json Normal file

@ -0,0 +1,34 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"react"
],
"rules": {
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"never"
]
}
}

4
.husky/pre-commit Executable file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn eslint . --fix

@ -3,4 +3,4 @@ module.exports = {
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
],
};
}

@ -1,4 +1,4 @@
const CracoAlias = require("craco-alias");
const CracoAlias = require("craco-alias")
module.exports = {
plugins: [
@ -14,4 +14,4 @@ module.exports = {
}
}
]
};
}

@ -1,4 +1,4 @@
import { defineConfig } from "cypress";
import { defineConfig } from "cypress"
export default defineConfig({
e2e: {
@ -8,4 +8,4 @@ export default defineConfig({
supportFile: false,
defaultCommandTimeout: 100,
},
});
})

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

@ -9,7 +9,8 @@
"test:cart": "craco test src/utils/calcCart --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
"eject": "craco eject",
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
"cypress": "cypress open"
"cypress": "cypress open",
"prepare": "husky install"
},
"dependencies": {
"@emotion/react": "^11.10.5",
@ -23,6 +24,7 @@
"classnames": "^2.3.2",
"cypress": "^12.17.3",
"formik": "^2.2.9",
"husky": "^8.0.3",
"immer": "^10.0.2",
"isomorphic-fetch": "^3.0.0",
"notistack": "^3.0.1",
@ -48,17 +50,15 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-slick": "^0.23.10",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"craco-alias": "^3.0.1",
"eslint": "^8.53.0",
"eslint-plugin-react": "^7.33.2",
"jest": "^29.5.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.3"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",

@ -2,4 +2,4 @@ export const _mocsk_: { name: string; type: "templ" | "squiz" | "reducer" }[] =
{ name: "Шаблонизатор", type: "templ" },
{ name: "Опросник", type: "squiz" },
{ name: "Сокращатель ссылок", type: "reducer" },
];
]

@ -1,5 +1,5 @@
import { showCaseTime } from "./showCaseTime";
import { showCaseVolume } from "./showCaseVolume";
import { showCaseTime } from "./showCaseTime"
import { showCaseVolume } from "./showCaseVolume"
export const showCaseObject: Record<
string,
@ -23,4 +23,4 @@ export const showCaseObject: Record<
templ: { volume: showCaseVolume, time: showCaseTime },
squiz: { volume: showCaseVolume, time: showCaseTime },
reducer: { volume: showCaseVolume, time: showCaseTime },
};
}

@ -1,8 +1,8 @@
import Infinity from "../assets/Icons/tariffs-time/Infinity.svg";
import OneIcons from "../assets/Icons/tariffs-time/OneIcons.svg";
import ThreeIcons from "../assets/Icons/tariffs-time/ThreeIcons.svg";
import SixIcons from "../assets/Icons/tariffs-time/SixIcons.svg";
import NineIcons from "../assets/Icons/tariffs-time/NineIcons.svg";
import Infinity from "../assets/Icons/tariffs-time/Infinity.svg"
import OneIcons from "../assets/Icons/tariffs-time/OneIcons.svg"
import ThreeIcons from "../assets/Icons/tariffs-time/ThreeIcons.svg"
import SixIcons from "../assets/Icons/tariffs-time/SixIcons.svg"
import NineIcons from "../assets/Icons/tariffs-time/NineIcons.svg"
export const showCaseTime = [
{
@ -50,4 +50,4 @@ export const showCaseTime = [
amount: 10,
price: 1000,
},
];
]

@ -1,8 +1,8 @@
import OneIcons from "../assets/Icons/tariffs-volume/OneIcons.svg";
import TwoIcons from "../assets/Icons/tariffs-volume/TwoIcons.svg";
import ThreeIcons from "../assets/Icons/tariffs-volume/ThreeIcons.svg";
import FourIcons from "../assets/Icons/tariffs-volume/FourIcons.svg";
import FiveIcons from "../assets/Icons/tariffs-volume/FiveIcons.svg";
import OneIcons from "../assets/Icons/tariffs-volume/OneIcons.svg"
import TwoIcons from "../assets/Icons/tariffs-volume/TwoIcons.svg"
import ThreeIcons from "../assets/Icons/tariffs-volume/ThreeIcons.svg"
import FourIcons from "../assets/Icons/tariffs-volume/FourIcons.svg"
import FiveIcons from "../assets/Icons/tariffs-volume/FiveIcons.svg"
export const showCaseVolume = [
{
@ -50,4 +50,4 @@ export const showCaseVolume = [
amount: 10,
price: 1000,
},
];
]

@ -1,18 +1,18 @@
import { makeRequest } from "@frontend/kitui";
import { makeRequest } from "@frontend/kitui"
import { parseAxiosError } from "@root/utils/parse-error";
import { parseAxiosError } from "@root/utils/parse-error"
import type {
LoginRequest,
LoginResponse,
RegisterRequest,
RegisterResponse,
} from "@frontend/kitui";
} from "@frontend/kitui"
const apiUrl =
process.env.NODE_ENV === "production"
? "/auth"
: "https://hub.pena.digital/auth";
: "https://hub.pena.digital/auth"
export async function register(
login: string,
@ -28,13 +28,13 @@ export async function register(
body: { login, password, phoneNumber },
useToken: false,
withCredentials: true,
});
})
return [registerResponse];
return [registerResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
return [null, `Не удалось зарегестрировать аккаунт. ${error}`]
}
}
@ -48,13 +48,13 @@ export async function login(
body: { login, password },
useToken: false,
withCredentials: true,
});
})
return [loginResponse];
return [loginResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось войти. ${error}`];
return [null, `Не удалось войти. ${error}`]
}
}
@ -65,12 +65,12 @@ export async function logout(): Promise<[unknown, string?]> {
method: "POST",
useToken: true,
withCredentials: true,
});
})
return [logoutResponse];
return [logoutResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось выйти. ${error}`];
return [null, `Не удалось выйти. ${error}`]
}
}

@ -1,12 +1,12 @@
import { UserAccount, makeRequest } from "@frontend/kitui";
import { AxiosError } from 'axios';
import { UserAccount, makeRequest } from "@frontend/kitui"
import { AxiosError } from "axios"
import { parseAxiosError } from "@root/utils/parse-error";
import { parseAxiosError } from "@root/utils/parse-error"
const apiUrl =
process.env.NODE_ENV === "production"
? "/customer"
: "https://hub.pena.digital/customer";
: "https://hub.pena.digital/customer"
export async function patchCart(
tariffId: string
@ -16,14 +16,14 @@ export async function patchCart(
url: apiUrl + `/cart?id=${tariffId}`,
method: "PATCH",
useToken: true,
});
})
return [patchCartResponse.cart];
return [patchCartResponse.cart]
} catch (nativeError) {
let [error, status] = parseAxiosError(nativeError);
let [error, status] = parseAxiosError(nativeError)
if (status === 400 && error.indexOf("invalid id") !== -1) error = "Данный тариф более недоступен"
return [[], `Не удалось добавить товар в корзину. ${error}`];
return [[], `Не удалось добавить товар в корзину. ${error}`]
}
}
@ -35,13 +35,13 @@ export async function deleteCart(
url: apiUrl + `/cart?id=${tariffId}`,
method: "DELETE",
useToken: true,
});
})
return [deleteCartResponse.cart];
return [deleteCartResponse.cart]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [[], `Не удалось удалить товар из корзины. ${error}`];
return [[], `Не удалось удалить товар из корзины. ${error}`]
}
}
@ -51,13 +51,13 @@ export async function payCart(): Promise<[UserAccount | null, string?]> {
url: apiUrl + "/cart/pay",
method: "POST",
useToken: true,
});
})
return [payCartResponse];
return [payCartResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось оплатить товар из корзины. ${error}`];
return [null, `Не удалось оплатить товар из корзины. ${error}`]
}
}
@ -75,12 +75,12 @@ export async function patchCurrency(
body: {
currency,
},
});
})
return [patchCurrencyResponse];
return [patchCurrencyResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось изменить валюту. ${error}`];
return [null, `Не удалось изменить валюту. ${error}`]
}
}

@ -1,5 +1,5 @@
import { Tariff, makeRequest } from "@frontend/kitui";
import { parseAxiosError } from "@root/utils/parse-error";
import { Tariff, makeRequest } from "@frontend/kitui"
import { parseAxiosError } from "@root/utils/parse-error"
export interface GetHistoryResponse {
totalPages: number;
@ -26,12 +26,12 @@ export async function getHistory(): Promise<[GetHistoryResponse | null, string?]
url: "https://hub.pena.digital/customer/history?page=1&limit=100&type=payCart",
method: "get",
useToken: true,
});
})
return [historyResponse];
return [historyResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось получить историю. ${error}`];
return [null, `Не удалось получить историю. ${error}`]
}
}

@ -1,10 +1,10 @@
import { makeRequest } from "@frontend/kitui";
import { makeRequest } from "@frontend/kitui"
import { parseAxiosError } from "@root/utils/parse-error";
import { parseAxiosError } from "@root/utils/parse-error"
import type { GetDiscountsResponse } from "@root/model/discount";
import type { GetDiscountsResponse } from "@root/model/discount"
const apiUrl = process.env.NODE_ENV === "production" ? "/price" : "https://hub.pena.digital/price";
const apiUrl = process.env.NODE_ENV === "production" ? "/price" : "https://hub.pena.digital/price"
export async function getDiscounts(signal: AbortSignal | undefined): Promise<[GetDiscountsResponse | null, string?]> {
try {
@ -13,12 +13,12 @@ export async function getDiscounts(signal: AbortSignal | undefined): Promise<[Ge
method: "get",
useToken: true,
signal,
});
})
return [discountsResponse];
return [discountsResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Ошибка получения списка скидок. ${error}`];
return [null, `Ошибка получения списка скидок. ${error}`]
}
}

@ -1,11 +1,11 @@
import { Tariff, makeRequest } from "@frontend/kitui";
import { CreateTariffBody } from "@root/model/customTariffs";
import { parseAxiosError } from "@root/utils/parse-error";
import { Tariff, makeRequest } from "@frontend/kitui"
import { CreateTariffBody } from "@root/model/customTariffs"
import { parseAxiosError } from "@root/utils/parse-error"
import type { ServiceKeyToPrivilegesMap } from "@root/model/privilege";
import type { GetTariffsResponse } from "@root/model/tariff";
import type { ServiceKeyToPrivilegesMap } from "@root/model/privilege"
import type { GetTariffsResponse } from "@root/model/tariff"
const apiUrl = process.env.NODE_ENV === "production" ? "/strator" : "https://hub.pena.digital/strator";
const apiUrl = process.env.NODE_ENV === "production" ? "/strator" : "https://hub.pena.digital/strator"
export async function getTariffs(
apiPage: number,
@ -18,13 +18,13 @@ export async function getTariffs(
method: "get",
useToken: true,
signal,
});
})
return [tariffsResponse];
return [tariffsResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось получить список тарифов. ${error}`];
return [null, `Не удалось получить список тарифов. ${error}`]
}
}
@ -35,13 +35,13 @@ export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff |
method: "post",
useToken: true,
body: tariff,
});
})
return [createTariffResponse];
return [createTariffResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось создать тариф. ${error}`];
return [null, `Не удалось создать тариф. ${error}`]
}
}
@ -51,13 +51,13 @@ export async function getTariffById(tariffId: string): Promise<[Tariff | null, s
url: `${apiUrl}/tariff/${tariffId}`,
method: "get",
useToken: true,
});
})
return [getTariffByIdResponse];
return [getTariffByIdResponse]
} catch (nativeError) {
const [error, status] = parseAxiosError(nativeError);
const [error, status] = parseAxiosError(nativeError)
return [null, `Не удалось получить тарифы. ${error}`, status];
return [null, `Не удалось получить тарифы. ${error}`, status]
}
}
@ -70,12 +70,12 @@ export async function getCustomTariffs(
signal,
method: "get",
useToken: true,
});
})
return [getCustomTariffsResponse];
return [getCustomTariffsResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось получить кастомные тарифы. ${error}`];
return [null, `Не удалось получить кастомные тарифы. ${error}`]
}
}

@ -1,12 +1,12 @@
import { makeRequest } from "@frontend/kitui";
import { parseAxiosError } from "@root/utils/parse-error";
import { makeRequest } from "@frontend/kitui"
import { parseAxiosError } from "@root/utils/parse-error"
import { SendTicketMessageRequest } from "@frontend/kitui";
import { SendTicketMessageRequest } from "@frontend/kitui"
const apiUrl =
process.env.NODE_ENV === "production"
? "/heruvym"
: "https://hub.pena.digital/heruvym";
: "https://hub.pena.digital/heruvym"
export async function sendTicketMessage(
ticketId: string,
@ -21,13 +21,13 @@ export async function sendTicketMessage(
method: "POST",
useToken: true,
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
});
})
return [sendTicketMessageResponse];
return [sendTicketMessageResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось отправить сообщение. ${error}`];
return [null, `Не удалось отправить сообщение. ${error}`]
}
}
@ -38,12 +38,12 @@ export async function shownMessage(id: string): Promise<[null, string?]> {
method: "POST",
useToken: true,
body: { id },
});
})
return [shownMessageResponse];
return [shownMessageResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось прочесть сообщение. ${error}`];
return [null, `Не удалось прочесть сообщение. ${error}`]
}
}

@ -1,11 +1,11 @@
import { User, makeRequest } from "@frontend/kitui";
import { PatchUserRequest } from "@root/model/user";
import { parseAxiosError } from "@root/utils/parse-error";
import { User, makeRequest } from "@frontend/kitui"
import { PatchUserRequest } from "@root/model/user"
import { parseAxiosError } from "@root/utils/parse-error"
const apiUrl =
process.env.NODE_ENV === "production"
? "/user"
: "https://hub.pena.digital/user";
: "https://hub.pena.digital/user"
export async function patchUser(
user: PatchUserRequest
@ -18,12 +18,12 @@ export async function patchUser(
useToken: true,
withCredentials: false,
body: user,
});
})
return [patchUserResponse];
return [patchUserResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Не удалось изменить пользователя. ${error}`];
return [null, `Не удалось изменить пользователя. ${error}`]
}
}

@ -1,18 +1,18 @@
import { makeRequest } from "@frontend/kitui";
import { makeRequest } from "@frontend/kitui"
import { jsonToFormdata } from "@root/utils/jsonToFormdata";
import { parseAxiosError } from "@root/utils/parse-error";
import { jsonToFormdata } from "@root/utils/jsonToFormdata"
import { parseAxiosError } from "@root/utils/parse-error"
import type {
Verification,
SendDocumentsArgs,
UpdateDocumentsArgs,
} from "@root/model/auth";
} from "@root/model/auth"
const apiUrl =
process.env.NODE_ENV === "production"
? "/verification"
: "https://hub.pena.digital/verification";
: "https://hub.pena.digital/verification"
export async function verification(
userId: string
@ -23,13 +23,13 @@ export async function verification(
method: "GET",
useToken: true,
withCredentials: true,
});
})
return [verificationResponse];
return [verificationResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Ошибка запроса верификации. ${error}`];
return [null, `Ошибка запроса верификации. ${error}`]
}
}
@ -43,13 +43,13 @@ export async function sendDocuments(
useToken: true,
withCredentials: true,
body: jsonToFormdata({ ...documents, egrule: documents.inn }),
});
})
return [sendDocumentsResponse];
return [sendDocumentsResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Ошибка отправки документов. ${error}`];
return [null, `Ошибка отправки документов. ${error}`]
}
}
@ -65,12 +65,12 @@ export async function updateDocuments(
body: jsonToFormdata(
documents.inn ? { ...documents, egrule: documents.inn } : documents
),
});
})
return [updateDocumentsResponse];
return [updateDocumentsResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Ошибка обновления документов. ${error}`];
return [null, `Ошибка обновления документов. ${error}`]
}
}

@ -1,11 +1,11 @@
import { makeRequest } from "@frontend/kitui";
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
import { parseAxiosError } from "@root/utils/parse-error";
import { makeRequest } from "@frontend/kitui"
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet"
import { parseAxiosError } from "@root/utils/parse-error"
const apiUrl =
process.env.NODE_ENV === "production"
? "/customer"
: "https://hub.pena.digital/customer";
: "https://hub.pena.digital/customer"
const testPaymentBody: SendPaymentRequest = {
type: "bankCard",
@ -21,7 +21,7 @@ const testPaymentBody: SendPaymentRequest = {
phoneNumber: "79000000000",
login: "login_test",
returnUrl: window.location.origin + "/wallet",
};
}
export async function sendPayment(
body: SendPaymentRequest = testPaymentBody
@ -37,12 +37,12 @@ export async function sendPayment(
useToken: true,
withCredentials: false,
body,
});
})
return [sendPaymentResponse];
return [sendPaymentResponse]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const [error] = parseAxiosError(nativeError)
return [null, `Ошибка оплаты. ${error}`];
return [null, `Ошибка оплаты. ${error}`]
}
}

@ -1,6 +1,6 @@
import { Box, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material";
import { PenaLink } from "@frontend/kitui";
import { Link as RouterLink } from "react-router-dom";
import { Box, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material"
import { PenaLink } from "@frontend/kitui"
import { Link as RouterLink } from "react-router-dom"
interface Props {
image?: string;
@ -12,7 +12,7 @@ interface Props {
}
export default function CardWithLink({ image, headerText, text, linkHref, isHighlighted = false, sx }: Props) {
const theme = useTheme();
const theme = useTheme()
return (
<Box
@ -67,5 +67,5 @@ export default function CardWithLink({ image, headerText, text, linkHref, isHigh
Подробнее
</PenaLink>
</Box>
);
)
}

@ -1,5 +1,5 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { isDateToday } from "@root/utils/date";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"
import { isDateToday } from "@root/utils/date"
interface Props {
@ -10,17 +10,17 @@ interface Props {
}
export default function ChatMessage({ unAuthenticated = false, isSelf, text, createdAt }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
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 time = date.toLocaleString([], {
hour: "2-digit",
minute: "2-digit",
...(!isDateToday(date) && { year: "2-digit", month: "2-digit", day: "2-digit" })
});
})
return (
<Box
@ -47,7 +47,7 @@ export default function ChatMessage({ unAuthenticated = false, isSelf, text, cre
<Box
sx={{
backgroundColor: messageBackgroundColor,
border: unAuthenticated ? `1px solid #E3E3E3` : `1px solid ${theme.palette.gray.main}`,
border: unAuthenticated ? "1px solid #E3E3E3" : `1px solid ${theme.palette.gray.main}`,
order: isSelf ? 2 : 1,
p: upMd ? "18px" : "12px",
borderRadius: "8px",
@ -86,5 +86,5 @@ export default function ChatMessage({ unAuthenticated = false, isSelf, text, cre
>{text}</Typography>
</Box>
</Box >
);
)
}

@ -1,4 +1,4 @@
import { SxProps, Theme, Typography, useTheme } from "@mui/material";
import { SxProps, Theme, Typography, useTheme } from "@mui/material"
interface Props {
@ -8,7 +8,7 @@ interface Props {
}
export default function ComplexHeader({ text1, text2, sx }: Props) {
const theme = useTheme();
const theme = useTheme()
return (
<Typography variant="h4" sx={sx}>
@ -27,5 +27,5 @@ export default function ComplexHeader({ text1, text2, sx }: Props) {
</Typography>
}
</Typography>
);
)
}

@ -1,7 +1,7 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ExpandIcon from "./icons/ExpandIcon";
import type { ReactNode } from "react";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"
import { useState } from "react"
import ExpandIcon from "./icons/ExpandIcon"
import type { ReactNode } from "react"
interface Props {
header: ReactNode;
@ -13,10 +13,10 @@ interface Props {
}
export default function CustomAccordion({ header, text, divide = false, price, last, first }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upXs = useMediaQuery(theme.breakpoints.up("xs"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upXs = useMediaQuery(theme.breakpoints.up("xs"))
const [isExpanded, setIsExpanded] = useState<boolean>(false)
return (
<Box
@ -99,5 +99,5 @@ export default function CustomAccordion({ header, text, divide = false, price, l
</Box>
)}
</Box>
);
)
}

@ -1,7 +1,7 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ExpandIcon from "./icons/ExpandIcon";
import type { ReactNode } from "react";
import { Box, useMediaQuery, useTheme } from "@mui/material"
import { useState } from "react"
import ExpandIcon from "./icons/ExpandIcon"
import type { ReactNode } from "react"
interface Props {
header: ReactNode;
@ -12,10 +12,10 @@ interface Props {
}
export default function CustomSaveAccordion({ header, divide = false, privilege, last, first }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upXs = useMediaQuery(theme.breakpoints.up("xs"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upXs = useMediaQuery(theme.breakpoints.up("xs"))
const [isExpanded, setIsExpanded] = useState<boolean>(false)
return (
<Box
@ -73,5 +73,5 @@ export default function CustomSaveAccordion({ header, divide = false, privilege,
</Box>
{isExpanded && privilege}
</Box>
);
)
}

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Slider, useTheme } from "@mui/material";
import { useState, useEffect } from "react"
import { Slider, useTheme } from "@mui/material"
type CustomSliderProps = {
value: number;
@ -14,31 +14,31 @@ export const CustomSlider = ({
max = 100,
onChange,
}: CustomSliderProps) => {
const theme = useTheme();
const [step, setStep] = useState<number>(1);
const theme = useTheme()
const [step, setStep] = useState<number>(1)
useEffect(() => {
if (value < 100) {
return setStep(10);
return setStep(10)
}
if (value < 500) {
return setStep(20);
return setStep(20)
}
if (value < 2000) {
return setStep(50);
return setStep(50)
}
setStep(150);
}, [value]);
setStep(150)
}, [value])
const handleChange = ({ type }: Event, newValue: number | number[]) => {
// Для корректной работы слайдера в FireFox
if (type !== "change") {
onChange(newValue);
onChange(newValue)
}
}
};
return (
<Slider
@ -72,5 +72,5 @@ export const CustomSlider = ({
},
}}
/>
);
};
)
}

@ -1,5 +1,5 @@
import { Tab } from "@mui/material";
import { styled } from "@mui/material";
import { Tab } from "@mui/material"
import { styled } from "@mui/material"
export const CustomTab = styled(Tab)(({ theme }) => ({
@ -16,4 +16,4 @@ export const CustomTab = styled(Tab)(({ theme }) => ({
"&:first-of-type": {
paddingLeft: 0,
}
}));
}))

@ -1,38 +1,38 @@
import { useState } from "react";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import ExpandIcon from "@components/icons/ExpandIcon";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { removeTariffFromCart } from "@root/stores/user";
import { enqueueSnackbar } from "notistack";
import { useState } from "react"
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"
import ExpandIcon from "@components/icons/ExpandIcon"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { removeTariffFromCart } from "@root/stores/user"
import { enqueueSnackbar } from "notistack"
import {
CloseButton,
TariffCartData,
getMessageFromFetchError,
} from "@frontend/kitui";
} from "@frontend/kitui"
import type { MouseEvent } from "react";
import type { MouseEvent } from "react"
interface Props {
tariffCartData: TariffCartData;
}
export default function CustomTariffAccordion({ tariffCartData }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const [isExpanded, setIsExpanded] = useState<boolean>(false)
function handleDeleteClick(event: MouseEvent<HTMLButtonElement>) {
event.stopPropagation();
event.stopPropagation()
removeTariffFromCart(tariffCartData.id)
.then(() => {
enqueueSnackbar("Тариф удален");
enqueueSnackbar("Тариф удален")
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
})
}
return (
@ -155,5 +155,5 @@ export default function CustomTariffAccordion({ tariffCartData }: Props) {
))}
</Box>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState } from "react"
import {
Box,
SvgIcon,
@ -7,72 +7,72 @@ import {
CircularProgress,
useMediaQuery,
useTheme,
} from "@mui/material";
} from "@mui/material"
import ClearIcon from "@mui/icons-material/Clear";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { useUserStore, removeTariffFromCart, setCart } from "@root/stores/user";
import { enqueueSnackbar } from "notistack";
import { ServiceCartData, getMessageFromFetchError } from "@frontend/kitui";
import ExpandIcon from "@components/icons/ExpandIcon";
import { deleteCart } from "@root/api/cart";
import ClearIcon from "@mui/icons-material/Clear"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { useUserStore, removeTariffFromCart, setCart } from "@root/stores/user"
import { enqueueSnackbar } from "notistack"
import { ServiceCartData, getMessageFromFetchError } from "@frontend/kitui"
import ExpandIcon from "@components/icons/ExpandIcon"
import { deleteCart } from "@root/api/cart"
import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg";
import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg"
import type { MouseEvent } from "react";
import CustomTariffAccordion from "@root/components/CustomTariffAccordion";
import type { MouseEvent } from "react"
import CustomTariffAccordion from "@root/components/CustomTariffAccordion"
const name: Record<string, string> = {
templategen: "Шаблонизатор",
squiz: "Опросник",
reducer: "Скоращатель ссылок",
custom: "Кастомные тарифы",
};
}
interface Props {
serviceData: ServiceCartData;
}
export default function CustomWrapperDrawer({ serviceData }: Props) {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const userAccount = useUserStore((state) => state.userAccount);
const [isExpanded, setIsExpanded] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState<boolean>(false)
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const userAccount = useUserStore((state) => state.userAccount)
function handleItemDeleteClick(tariffId: string) {
removeTariffFromCart(tariffId)
.then(() => {
enqueueSnackbar("Тариф удален");
enqueueSnackbar("Тариф удален")
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
})
}
const deleteService = async (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
let cartItems: string[] = userAccount?.cart || [];
const errors: string[] = [];
event.stopPropagation()
let cartItems: string[] = userAccount?.cart || []
const errors: string[] = []
setIsLoading(true);
setIsLoading(true)
for (const { id } of serviceData.tariffs) {
const [cartItemsResponse, deleteError] = await deleteCart(id);
const [cartItemsResponse, deleteError] = await deleteCart(id)
if (deleteError) {
errors.push(deleteError);
errors.push(deleteError)
} else {
cartItems = cartItemsResponse;
cartItems = cartItemsResponse
}
}
setCart(cartItems);
setIsLoading(false);
errors.forEach(enqueueSnackbar);
};
setCart(cartItems)
setIsLoading(false)
errors.forEach(enqueueSnackbar)
}
return (
<Box sx={{ display: "flex", alignItems: "center" }}>
@ -174,7 +174,7 @@ export default function CustomWrapperDrawer({ serviceData }: Props) {
</Box>
{isExpanded &&
serviceData.tariffs.map((tariff) => {
const privilege = tariff.privileges[0];
const privilege = tariff.privileges[0]
return tariff.privileges.length > 1 ? (
<CustomTariffAccordion
@ -235,10 +235,10 @@ export default function CustomWrapperDrawer({ serviceData }: Props) {
/>
</Box>
</Box>
);
)
})}
</Box>
</Box>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { useState, useRef } from "react";
import { useState, useRef } from "react"
import {
Typography,
Drawer,
@ -8,81 +8,81 @@ import {
IconButton,
Badge,
Button,
} from "@mui/material";
import SectionWrapper from "./SectionWrapper";
import CustomWrapperDrawer from "./CustomWrapperDrawer";
import { NotificationsModal } from "./NotificationsModal";
import { Loader } from "./Loader";
import { useCart } from "@root/utils/hooks/useCart";
import { currencyFormatter } from "@root/utils/currencyFormatter";
} from "@mui/material"
import SectionWrapper from "./SectionWrapper"
import CustomWrapperDrawer from "./CustomWrapperDrawer"
import { NotificationsModal } from "./NotificationsModal"
import { Loader } from "./Loader"
import { useCart } from "@root/utils/hooks/useCart"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import {
closeCartDrawer,
openCartDrawer,
useCartStore,
} from "@root/stores/cart";
import { setUserAccount, useUserStore } from "@root/stores/user";
import { useTicketStore } from "@root/stores/tickets";
} from "@root/stores/cart"
import { setUserAccount, useUserStore } from "@root/stores/user"
import { useTicketStore } from "@root/stores/tickets"
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg";
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg";
import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg";
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg"
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg"
import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg"
import { payCart } from "@root/api/cart";
import { enqueueSnackbar } from "notistack";
import { Link, useNavigate } from "react-router-dom";
import { withErrorBoundary } from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError";
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { payCart } from "@root/api/cart"
import { enqueueSnackbar } from "notistack"
import { Link, useNavigate } from "react-router-dom"
import { withErrorBoundary } from "react-error-boundary"
import { handleComponentError } from "@root/utils/handleComponentError"
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"
function Drawers() {
const [openNotificationsModal, setOpenNotificationsModal] =
useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const bellRef = useRef<HTMLButtonElement | null>(null);
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isDrawerOpen = useCartStore((state) => state.isDrawerOpen);
const cart = useCart();
const userAccount = useUserStore((state) => state.userAccount);
const tickets = useTicketStore((state) => state.tickets);
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0);
useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(false)
const bellRef = useRef<HTMLButtonElement | null>(null)
const navigate = useNavigate()
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const isDrawerOpen = useCartStore((state) => state.isDrawerOpen)
const cart = useCart()
const userAccount = useUserStore((state) => state.userAccount)
const tickets = useTicketStore((state) => state.tickets)
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0)
const notificationsCount = tickets.filter(
({ user, top_message }) =>
user !== top_message.user_id && top_message.shown.me !== 1
).length;
).length
async function handlePayClick() {
setLoading(true);
setLoading(true)
const [payCartResponse, payCartError] = await payCart();
const [payCartResponse, payCartError] = await payCart()
if (payCartError) {
if (payCartError.includes("insufficient funds: ")) {
const notEnoughMoneyAmount = parseInt(
payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, "")
);
)
setNotEnoughMoneyAmount(notEnoughMoneyAmount);
setNotEnoughMoneyAmount(notEnoughMoneyAmount)
}
setLoading(false);
setLoading(false)
return enqueueSnackbar(payCartError);
return enqueueSnackbar(payCartError)
}
if (payCartResponse) {
setUserAccount(payCartResponse);
setUserAccount(payCartResponse)
}
setLoading(false);
closeCartDrawer();
setLoading(false)
closeCartDrawer()
}
function handleReplenishWallet() {
navigate("/payment", { state: { notEnoughMoneyAmount } });
navigate("/payment", { state: { notEnoughMoneyAmount } })
}
return (
@ -325,7 +325,7 @@ function Drawers() {
</SectionWrapper>
</Drawer>
</Box>
);
)
}
export default withErrorBoundary(Drawers, {

@ -9,8 +9,8 @@ import {
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { TicketMessage } from "@frontend/kitui";
} from "@mui/material"
import { TicketMessage } from "@frontend/kitui"
import {
addOrUpdateUnauthMessages,
useUnauthTicketStore,
@ -19,47 +19,47 @@ import {
setUnauthSessionData,
setIsMessageSending,
setUnauthTicketMessageFetchState,
} from "@root/stores/unauthTicket";
import { enqueueSnackbar } from "notistack";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import ChatMessage from "../ChatMessage";
import SendIcon from "../icons/SendIcon";
import UserCircleIcon from "./UserCircleIcon";
import { throttle } from "@frontend/kitui";
} from "@root/stores/unauthTicket"
import { enqueueSnackbar } from "notistack"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import ChatMessage from "../ChatMessage"
import SendIcon from "../icons/SendIcon"
import UserCircleIcon from "./UserCircleIcon"
import { throttle } from "@frontend/kitui"
import {
useTicketMessages,
getMessageFromFetchError,
useSSESubscription,
useEventListener,
createTicket,
} from "@frontend/kitui";
import { sendTicketMessage } from "@root/api/ticket";
} from "@frontend/kitui"
import { sendTicketMessage } from "@root/api/ticket"
interface Props {
sx?: SxProps<Theme>;
}
export default function Chat({ sx }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [messageField, setMessageField] = useState<string>("");
const sessionData = useUnauthTicketStore((state) => state.sessionData);
const messages = useUnauthTicketStore((state) => state.messages);
const messageApiPage = useUnauthTicketStore((state) => state.apiPage);
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const [messageField, setMessageField] = useState<string>("")
const sessionData = useUnauthTicketStore((state) => state.sessionData)
const messages = useUnauthTicketStore((state) => state.messages)
const messageApiPage = useUnauthTicketStore((state) => state.apiPage)
const messagesPerPage = useUnauthTicketStore(
(state) => state.messagesPerPage
);
)
const isMessageSending = useUnauthTicketStore(
(state) => state.isMessageSending
);
)
const isPreventAutoscroll = useUnauthTicketStore(
(state) => state.isPreventAutoscroll
);
const lastMessageId = useUnauthTicketStore((state) => state.lastMessageId);
)
const lastMessageId = useUnauthTicketStore((state) => state.lastMessageId)
const fetchState = useUnauthTicketStore(
(state) => state.unauthTicketMessageFetchState
);
const chatBoxRef = useRef<HTMLDivElement>(null);
)
const chatBoxRef = useRef<HTMLDivElement>(null)
useTicketMessages({
url: "https://hub.pena.digital/heruvym/getMessages",
@ -69,67 +69,67 @@ export default function Chat({ sx }: Props) {
messageApiPage,
onSuccess: useCallback((messages) => {
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
chatBoxRef.current.scrollTop = 1;
addOrUpdateUnauthMessages(messages);
chatBoxRef.current.scrollTop = 1
addOrUpdateUnauthMessages(messages)
}, []),
onError: useCallback((error: Error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
}, []),
onFetchStateChange: setUnauthTicketMessageFetchState,
});
})
useSSESubscription<TicketMessage>({
enabled: Boolean(sessionData),
url: `https://hub.pena.digital/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
onNewData: addOrUpdateUnauthMessages,
onDisconnect: useCallback(() => {
setUnauthIsPreventAutoscroll(false);
setUnauthIsPreventAutoscroll(false)
}, []),
marker: "ticket",
});
})
const throttledScrollHandler = useMemo(
() =>
throttle(() => {
const chatBox = chatBoxRef.current;
if (!chatBox) return;
const chatBox = chatBoxRef.current
if (!chatBox) return
const scrollBottom =
chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight;
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight;
setUnauthIsPreventAutoscroll(isPreventAutoscroll);
chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight
setUnauthIsPreventAutoscroll(isPreventAutoscroll)
if (fetchState !== "idle") return;
if (fetchState !== "idle") return
if (chatBox.scrollTop < chatBox.clientHeight) {
incrementUnauthMessageApiPage();
incrementUnauthMessageApiPage()
}
}, 200),
[fetchState]
);
)
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
useEventListener("scroll", throttledScrollHandler, chatBoxRef)
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return;
if (!chatBoxRef.current) return
if (!isPreventAutoscroll) {
setTimeout(() => {
scrollToBottom();
}, 50);
scrollToBottom()
}, 50)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[lastMessageId]
);
)
async function handleSendMessage() {
if (!messageField || isMessageSending) return;
if (!messageField || isMessageSending) return
if (!sessionData) {
setIsMessageSending(true);
setIsMessageSending(true)
createTicket({
url: "https://hub.pena.digital/heruvym/create",
body: {
@ -142,52 +142,52 @@ export default function Chat({ sx }: Props) {
setUnauthSessionData({
ticketId: response.Ticket,
sessionId: response.sess,
});
})
})
.catch((error) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
const errorMessage = getMessageFromFetchError(error)
if (errorMessage) enqueueSnackbar(errorMessage)
})
.finally(() => {
setMessageField("");
setIsMessageSending(false);
});
setMessageField("")
setIsMessageSending(false)
})
} else {
setIsMessageSending(true);
setIsMessageSending(true)
const [_, sendTicketMessageError] = await sendTicketMessage(
sessionData.ticketId,
messageField
);
)
if (sendTicketMessageError) {
enqueueSnackbar(sendTicketMessageError);
enqueueSnackbar(sendTicketMessageError)
}
setMessageField("");
setIsMessageSending(false);
setMessageField("")
setIsMessageSending(false)
}
}
function scrollToBottom(behavior?: ScrollBehavior) {
if (!chatBoxRef.current) return;
if (!chatBoxRef.current) return
const chatBox = chatBoxRef.current;
const chatBox = chatBoxRef.current
chatBox.scroll({
left: 0,
top: chatBox.scrollHeight,
behavior,
});
})
}
const handleTextfieldKeyPress: React.KeyboardEventHandler<
HTMLInputElement | HTMLTextAreaElement
> = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
e.preventDefault()
handleSendMessage()
}
}
};
return (
<Box
@ -315,5 +315,5 @@ export default function Chat({ sx }: Props) {
</FormControl>
</Box>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
@ -22,5 +22,5 @@ export default function CircleDoubleDown({ isUp = false }: Props) {
<path d="M12.9004 14L16.9004 10L20.9004 14" stroke="#252734" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
)
}

@ -1,10 +1,10 @@
import { Box, Fab, Typography } from "@mui/material";
import { useState } from "react";
import CircleDoubleDown from "./CircleDoubleDownIcon";
import Chat from "./Chat";
import { Box, Fab, Typography } from "@mui/material"
import { useState } from "react"
import CircleDoubleDown from "./CircleDoubleDownIcon"
import Chat from "./Chat"
export default function FloatingSupportChat() {
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
const [isChatOpened, setIsChatOpened] = useState<boolean>(false)
const animation = {
"@keyframes runningStripe": {
@ -29,7 +29,7 @@ export default function FloatingSupportChat() {
left: "100%",
},
},
};
}
return (
<Box
sx={{
@ -90,5 +90,5 @@ export default function FloatingSupportChat() {
{!isChatOpened && <Typography sx={{ zIndex: "10000" }}>Задайте нам вопрос</Typography>}
</Fab>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
export default function UserCircleIcon() {
@ -18,5 +18,5 @@ export default function UserCircleIcon() {
<path d="M7.97461 25.425C8.727 23.943 9.87506 22.6983 11.2915 21.8289C12.708 20.9595 14.3376 20.4992 15.9996 20.4992C17.6616 20.4992 19.2912 20.9595 20.7077 21.8289C22.1242 22.6983 23.2722 23.943 24.0246 25.425" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
)
}

@ -1,11 +1,11 @@
import { Box, Button, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
import PenaLogo from "./PenaLogo";
import SectionWrapper from "./SectionWrapper";
import { Link } from "react-router-dom";
import { Box, Button, Divider, Typography, useMediaQuery, useTheme } from "@mui/material"
import PenaLogo from "./PenaLogo"
import SectionWrapper from "./SectionWrapper"
import { Link } from "react-router-dom"
export default function Footer() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
return (
<SectionWrapper
@ -81,5 +81,5 @@ export default function Footer() {
</Typography>
</Box>
</SectionWrapper>
);
)
}

@ -7,9 +7,9 @@ import {
Theme,
useMediaQuery,
useTheme,
} from "@mui/material";
} from "@mui/material"
import "./text-input.css";
import "./text-input.css"
interface Props {
id: string;
@ -32,16 +32,16 @@ export default function InputTextfield({
color,
FormInputSx,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const labelFont = upMd
? bold
? theme.typography.p1
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
: theme.typography.body2
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" }
return (
<FormControl
@ -92,5 +92,5 @@ export default function InputTextfield({
onChange={onChange}
/>
</FormControl>
);
)
}

@ -1,11 +1,11 @@
import { Box, useTheme } from "@mui/material";
import { Box, useTheme } from "@mui/material"
type LoaderProps = {
size?: number;
};
export const Loader = ({ size = 40 }: LoaderProps) => {
const theme = useTheme();
const theme = useTheme()
return (
<Box
@ -26,5 +26,5 @@ export const Loader = ({ size = 40 }: LoaderProps) => {
},
}}
/>
);
};
)
}

@ -1,6 +1,6 @@
import { useState } from "react";
import { Box, Typography, useTheme } from "@mui/material";
import { Link, useLocation } from "react-router-dom";
import { useState } from "react"
import { Box, Typography, useTheme } from "@mui/material"
import { Link, useLocation } from "react-router-dom"
type MenuItem = {
name: string;
@ -9,11 +9,11 @@ type MenuItem = {
};
export default function Menu() {
const [activeSubMenu, setActiveSubMenu] = useState<MenuItem[]>([]);
const theme = useTheme();
const location = useLocation();
const [activeSubMenu, setActiveSubMenu] = useState<MenuItem[]>([])
const theme = useTheme()
const location = useLocation()
const color = location.pathname === "/" ? "white" : "black";
const color = location.pathname === "/" ? "white" : "black"
const arrayMenu: MenuItem[] = location.pathname === "/" ? [
{ name: "Наши продукты", url: "/faq" },
@ -32,7 +32,7 @@ export default function Menu() {
{ name: "Корзина", url: "/cart" },
{ name: "Поддержка", url: "/support" },
{ name: "История", url: "/history" },
];
]
return (
@ -127,5 +127,5 @@ export default function Menu() {
))}
</Box>
</Box>
);
)
}

@ -1,12 +1,12 @@
import { Avatar, Box, IconButton, SxProps, Theme, Typography, useTheme } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { Avatar, Box, IconButton, SxProps, Theme, Typography, useTheme } from "@mui/material"
import { useNavigate } from "react-router-dom"
interface Props {
sx?: SxProps<Theme>;
}
export default function CustomAvatar({ sx }: Props) {
const theme = useTheme();
const theme = useTheme()
const navigate = useNavigate()
return (
@ -38,5 +38,5 @@ export default function CustomAvatar({ sx }: Props) {
</Box>
</Avatar>
</IconButton>
);
)
}

@ -1,18 +1,18 @@
import { TransitionProps } from "@mui/material/transitions";
import logotip from "../../assets/Icons/logoPenaHab.svg";
import logotipBlack from "../../assets/Icons/black_logo_PenaHab.svg";
import CustomAvatar from "./Avatar";
import React from "react";
import { Box, Button, Dialog, List, ListItem, Slide, Typography, useMediaQuery, useTheme } from "@mui/material";
import { Link, useLocation } from "react-router-dom";
import { useUserStore } from "@root/stores/user";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { TransitionProps } from "@mui/material/transitions"
import logotip from "../../assets/Icons/logoPenaHab.svg"
import logotipBlack from "../../assets/Icons/black_logo_PenaHab.svg"
import CustomAvatar from "./Avatar"
import React from "react"
import { Box, Button, Dialog, List, ListItem, Slide, Typography, useMediaQuery, useTheme } from "@mui/material"
import { Link, useLocation } from "react-router-dom"
import { useUserStore } from "@root/stores/user"
import { currencyFormatter } from "@root/utils/currencyFormatter"
const arrayMenu = [
{ name: "Наши продукты", url: "/" },
{ name: "Наши услуги", url: "/" },
];
]
const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
@ -21,8 +21,8 @@ const Transition = React.forwardRef(function Transition(
ref: React.Ref<null>
) {
return <Slide direction={"left"} ref={ref} {...props} />;
});
return <Slide direction={"left"} ref={ref} {...props} />
})
interface DialogMenuProps {
open: boolean;
@ -30,13 +30,13 @@ interface DialogMenuProps {
}
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
const theme = useTheme();
const location = useLocation();
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
const theme = useTheme()
const location = useLocation()
const isTablet = useMediaQuery(theme.breakpoints.down(900))
const user = useUserStore((state) => state.user)
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0
const isMobileHeight = useMediaQuery("(max-height: 400px)");
const isMobileHeight = useMediaQuery("(max-height: 400px)")
return (
<Dialog
@ -154,5 +154,5 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
</>
)}
</Dialog>
);
)
}

@ -1,6 +1,6 @@
import { useMediaQuery, useTheme } from "@mui/material";
import NavbarCollapsed from "./NavbarCollapsed";
import NavbarFull from "./NavbarFull";
import { useMediaQuery, useTheme } from "@mui/material"
import NavbarCollapsed from "./NavbarCollapsed"
import NavbarFull from "./NavbarFull"
interface Props {
isCollapsed?: boolean;
@ -8,8 +8,8 @@ interface Props {
}
export default function Navbar({ isLoggedIn, isCollapsed = false }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
return upMd ? <NavbarFull isLoggedIn={isLoggedIn} /> : <NavbarCollapsed isLoggedIn={isLoggedIn} />;
return upMd ? <NavbarFull isLoggedIn={isLoggedIn} /> : <NavbarCollapsed isLoggedIn={isLoggedIn} />
}

@ -1,20 +1,20 @@
import { useState } from "react";
import { Button, useMediaQuery, useTheme } from "@mui/material";
import { Link, useLocation } from "react-router-dom";
import { useState } from "react"
import { Button, useMediaQuery, useTheme } from "@mui/material"
import { Link, useLocation } from "react-router-dom"
import SectionWrapper from "../SectionWrapper";
import SectionWrapper from "../SectionWrapper"
import PenaLogo from "../PenaLogo";
import DialogMenu from "./DialogMenu";
import { BurgerButton } from "@frontend/kitui";
import PenaLogo from "../PenaLogo"
import DialogMenu from "./DialogMenu"
import { BurgerButton } from "@frontend/kitui"
interface Props {
isLoggedIn: boolean;
}
export default function NavbarCollapsed({ isLoggedIn }: Props) {
const [open, setOpen] = useState(false);
const theme = useTheme();
const [open, setOpen] = useState(false)
const theme = useTheme()
return (
<SectionWrapper
@ -42,5 +42,5 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
<BurgerButton onClick={() => setOpen(!open)} sx={{ color: "white" }} />
<DialogMenu open={open} handleClose={() => setOpen(false)} />
</SectionWrapper>
);
)
}

@ -1,5 +1,5 @@
import { useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useState } from "react"
import { Link, useLocation, useNavigate } from "react-router-dom"
import {
Box,
Button,
@ -8,52 +8,52 @@ import {
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import SectionWrapper from "../SectionWrapper";
import LogoutIcon from "../icons/LogoutIcon";
import DialogMenu from "./DialogMenu";
import WalletIcon from "../icons/WalletIcon";
import CustomAvatar from "./Avatar";
import Drawers from "../Drawers";
import PenaLogo from "../PenaLogo";
import Menu from "../Menu";
import { logout } from "@root/api/auth";
import { enqueueSnackbar } from "notistack";
import { clearUserData, useUserStore } from "@root/stores/user";
} from "@mui/material"
import SectionWrapper from "../SectionWrapper"
import LogoutIcon from "../icons/LogoutIcon"
import DialogMenu from "./DialogMenu"
import WalletIcon from "../icons/WalletIcon"
import CustomAvatar from "./Avatar"
import Drawers from "../Drawers"
import PenaLogo from "../PenaLogo"
import Menu from "../Menu"
import { logout } from "@root/api/auth"
import { enqueueSnackbar } from "notistack"
import { clearUserData, useUserStore } from "@root/stores/user"
import {
BurgerButton,
clearAuthToken,
getMessageFromFetchError,
} from "@frontend/kitui";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { clearCustomTariffs } from "@root/stores/customTariffs";
import { clearTickets } from "@root/stores/tickets";
} from "@frontend/kitui"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { clearCustomTariffs } from "@root/stores/customTariffs"
import { clearTickets } from "@root/stores/tickets"
interface Props {
isLoggedIn: boolean;
}
export default function NavbarFull({ isLoggedIn }: Props) {
const theme = useTheme();
const location = useLocation();
const navigate = useNavigate();
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
const isTablet = useMediaQuery(theme.breakpoints.up(1000));
const [open, setOpen] = useState(false);
const theme = useTheme()
const location = useLocation()
const navigate = useNavigate()
const user = useUserStore((state) => state.user)
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0
const isTablet = useMediaQuery(theme.breakpoints.up(1000))
const [open, setOpen] = useState(false)
async function handleLogoutClick() {
const [_, logoutError] = await logout();
const [_, logoutError] = await logout()
if (logoutError) {
return enqueueSnackbar(logoutError);
return enqueueSnackbar(logoutError)
}
clearAuthToken();
clearUserData();
clearCustomTariffs();
clearTickets();
navigate("/");
clearAuthToken()
clearUserData()
clearCustomTariffs()
clearTickets()
navigate("/")
}
return isLoggedIn ? (
@ -160,5 +160,5 @@ export default function NavbarFull({ isLoggedIn }: Props) {
</SectionWrapper>
<DialogMenu open={open} handleClose={() => setOpen(false)} />
</>
);
)
}

@ -1,10 +1,10 @@
import { useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { Box, Button, List, ListItem, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useUserStore } from "@root/stores/user";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { cardShadow } from "@root/utils/theme";
import { AvatarButton } from "@frontend/kitui";
import { useState } from "react"
import { Link, useLocation } from "react-router-dom"
import { Box, Button, List, ListItem, Typography, useMediaQuery, useTheme } from "@mui/material"
import { useUserStore } from "@root/stores/user"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { cardShadow } from "@root/utils/theme"
import { AvatarButton } from "@frontend/kitui"
type MenuItem = {
@ -28,29 +28,29 @@ const arrayMenu: MenuItem[] = [
{ name: "Корзина", url: "/cart" },
{ name: "Поддержка", url: "/support" },
{ name: "История", url: "/history" },
];
]
interface DialogMenuProps {
handleClose: () => void;
}
export default function DialogMenu({ handleClose }: DialogMenuProps) {
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1);
const theme = useTheme();
const location = useLocation();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
const initials = useUserStore(state => state.initials);
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1)
const theme = useTheme()
const location = useLocation()
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const isMobile = useMediaQuery(theme.breakpoints.down(600))
const user = useUserStore((state) => state.user)
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0
const initials = useUserStore(state => state.initials)
const closeDialogMenu = () => {
setActiveSubMenuIndex(-1);
setActiveSubMenuIndex(-1)
handleClose();
};
handleClose()
}
const handleSubMenu = (index: number) => setActiveSubMenuIndex((activeIndex) => (activeIndex !== index ? index : -1));
const handleSubMenu = (index: number) => setActiveSubMenuIndex((activeIndex) => (activeIndex !== index ? index : -1))
return (
@ -195,5 +195,5 @@ export default function DialogMenu({ handleClose }: DialogMenuProps) {
)}
</List>
</Box>
);
)
}

@ -1,15 +1,15 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import NavbarCollapsed from "./NavbarCollapsed";
import NavbarFull from "./NavbarFull";
import { Box, useMediaQuery, useTheme } from "@mui/material"
import NavbarCollapsed from "./NavbarCollapsed"
import NavbarFull from "./NavbarFull"
import type { ReactNode } from "react";
import type { ReactNode } from "react"
interface Props {
children: ReactNode;
}
export default function Navbar({ children }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up(1000));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up(1000))
return (
<Box
@ -24,5 +24,5 @@ export default function Navbar({ children }: Props) {
<NavbarCollapsed>{children}</NavbarCollapsed>
)}
</Box>
);
)
}

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect } from "react"
import {
Box,
Badge,
@ -6,60 +6,60 @@ import {
IconButton,
useTheme,
useMediaQuery,
} from "@mui/material";
import { Link } from "react-router-dom";
} from "@mui/material"
import { Link } from "react-router-dom"
import SectionWrapper from "../SectionWrapper";
import { NotificationsModal } from "../NotificationsModal";
import { NavbarPanel } from "./NavbarPanel";
import SectionWrapper from "../SectionWrapper"
import { NotificationsModal } from "../NotificationsModal"
import { NavbarPanel } from "./NavbarPanel"
import { useUserStore } from "@root/stores/user";
import { useUserStore } from "@root/stores/user"
import {
useTicketStore
} from "@root/stores/tickets";
} from "@root/stores/tickets"
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg";
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg";
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg"
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg"
import DialogMenu from "./DialogMenu";
import PenaLogo from "../PenaLogo";
import CloseIcon from "../icons/CloseIcons";
import MenuIcon from "@mui/icons-material/Menu";
import DialogMenu from "./DialogMenu"
import PenaLogo from "../PenaLogo"
import CloseIcon from "../icons/CloseIcons"
import MenuIcon from "@mui/icons-material/Menu"
import type { ReactNode } from "react";
import type { ReactNode } from "react"
interface Props {
children: ReactNode;
}
export default function NavbarCollapsed({ children }: Props) {
const [open, setOpen] = useState(false);
const [open, setOpen] = useState(false)
const [openNotificationsModal, setOpenNotificationsModal] =
useState<boolean>(false);
const bellRef = useRef<HTMLButtonElement | null>(null);
const userAccount = useUserStore((state) => state.userAccount);
const tickets = useTicketStore((state) => state.tickets);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(550));
useState<boolean>(false)
const bellRef = useRef<HTMLButtonElement | null>(null)
const userAccount = useUserStore((state) => state.userAccount)
const tickets = useTicketStore((state) => state.tickets)
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down(550))
const handleClose = () => {
setOpen(false);
};
setOpen(false)
}
const notificationsCount = tickets.filter(
({ user, top_message }) =>
user !== top_message.user_id && top_message.shown.me !== 1
).length;
).length
useEffect(() => {
if (open) {
document.body.style.overflow = "hidden";
document.body.style.overflow = "hidden"
return;
return
}
document.body.style.overflow = "unset";
}, [open]);
document.body.style.overflow = "unset"
}, [open])
return (
<SectionWrapper
@ -244,5 +244,5 @@ export default function NavbarCollapsed({ children }: Props) {
</Box>
</Box>
</SectionWrapper>
);
)
}

@ -1,45 +1,45 @@
import { Link, useNavigate } from "react-router-dom";
import { Box, Container, Typography, useTheme } from "@mui/material";
import Drawers from "../Drawers";
import PenaLogo from "../PenaLogo";
import Menu from "../Menu";
import { logout } from "@root/api/auth";
import { enqueueSnackbar } from "notistack";
import { clearUserData, useUserStore } from "@root/stores/user";
import { Link, useNavigate } from "react-router-dom"
import { Box, Container, Typography, useTheme } from "@mui/material"
import Drawers from "../Drawers"
import PenaLogo from "../PenaLogo"
import Menu from "../Menu"
import { logout } from "@root/api/auth"
import { enqueueSnackbar } from "notistack"
import { clearUserData, useUserStore } from "@root/stores/user"
import {
AvatarButton,
LogoutButton,
WalletButton,
clearAuthToken,
} from "@frontend/kitui";
import { clearCustomTariffs } from "@root/stores/customTariffs";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { clearTickets } from "@root/stores/tickets";
} from "@frontend/kitui"
import { clearCustomTariffs } from "@root/stores/customTariffs"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { clearTickets } from "@root/stores/tickets"
import type { ReactNode } from "react";
import type { ReactNode } from "react"
interface Props {
children: ReactNode;
}
export default function NavbarFull({ children }: Props) {
const theme = useTheme();
const navigate = useNavigate();
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
const initials = useUserStore((state) => state.initials);
const theme = useTheme()
const navigate = useNavigate()
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0
const initials = useUserStore((state) => state.initials)
async function handleLogoutClick() {
const [_, logoutError] = await logout();
const [_, logoutError] = await logout()
if (logoutError) {
return enqueueSnackbar(logoutError);
return enqueueSnackbar(logoutError)
}
clearAuthToken();
clearUserData();
clearCustomTariffs();
clearTickets();
navigate("/");
clearAuthToken()
clearUserData()
clearCustomTariffs()
clearTickets()
navigate("/")
}
return (
@ -90,5 +90,5 @@ export default function NavbarFull({ children }: Props) {
</Container>
<Box>{children}</Box>
</Box>
);
)
}

@ -1,43 +1,43 @@
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom"
import {
Box,
IconButton,
Typography,
useTheme,
useMediaQuery,
} from "@mui/material";
import LogoutIcon from "../icons/LogoutIcon";
import Drawers from "../Drawers";
import { logout } from "@root/api/auth";
import { enqueueSnackbar } from "notistack";
import { clearUserData, useUserStore } from "@root/stores/user";
import { AvatarButton, clearAuthToken } from "@frontend/kitui";
import { clearCustomTariffs } from "@root/stores/customTariffs";
import { clearTickets } from "@root/stores/tickets";
} from "@mui/material"
import LogoutIcon from "../icons/LogoutIcon"
import Drawers from "../Drawers"
import { logout } from "@root/api/auth"
import { enqueueSnackbar } from "notistack"
import { clearUserData, useUserStore } from "@root/stores/user"
import { AvatarButton, clearAuthToken } from "@frontend/kitui"
import { clearCustomTariffs } from "@root/stores/customTariffs"
import { clearTickets } from "@root/stores/tickets"
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { currencyFormatter } from "@root/utils/currencyFormatter"
import walletIcon from "@root/assets/Icons/wallet_icon.svg";
import walletIcon from "@root/assets/Icons/wallet_icon.svg"
export const NavbarPanel = () => {
const navigate = useNavigate();
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
const initials = useUserStore((state) => state.initials);
const navigate = useNavigate()
const theme = useTheme()
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0
const initials = useUserStore((state) => state.initials)
async function handleLogoutClick() {
const [_, logoutError] = await logout();
const [_, logoutError] = await logout()
if (logoutError) {
return enqueueSnackbar(logoutError);
return enqueueSnackbar(logoutError)
}
clearAuthToken();
clearUserData();
clearCustomTariffs();
clearTickets();
navigate("/");
clearAuthToken()
clearUserData()
clearCustomTariffs()
clearTickets()
navigate("/")
}
return (
@ -87,5 +87,5 @@ export const NavbarPanel = () => {
<LogoutIcon />
</IconButton>
</Box>
);
};
)
}

@ -5,8 +5,8 @@ import {
Typography,
useTheme,
useMediaQuery,
} from "@mui/material";
import { Link } from "react-router-dom";
} from "@mui/material"
import { Link } from "react-router-dom"
type Notification = {
text: string;
@ -28,8 +28,8 @@ export const NotificationsModal = ({
anchorElement,
notifications,
}: NotificationsModalProps) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down(650))
return (
<Popover
@ -97,7 +97,7 @@ export const NotificationsModal = ({
paddingLeft: watched ? "0" : "35px",
fontWeight: watched ? "normal" : "bold",
"&::before": {
content: '""',
content: "\"\"",
display: watched ? "none" : "block",
position: "absolute",
left: "10px",
@ -141,5 +141,5 @@ export const NotificationsModal = ({
)}
</List>
</Popover>
);
};
)
}

@ -1,5 +1,5 @@
import { Box, SxProps, Theme } from "@mui/material";
import { ReactElement } from "react";
import { Box, SxProps, Theme } from "@mui/material"
import { ReactElement } from "react"
interface Props {
@ -10,20 +10,20 @@ interface Props {
}
export default function NumberIcon({ number, backgroundColor = "rgb(0 0 0 / 0)", color, sx }: Props) {
number = number % 100;
number = number % 100
const firstDigit = Math.floor(number / 10);
const secondDigit = number % 10;
const firstDigit = Math.floor(number / 10)
const secondDigit = number % 10
const firstDigitTranslateX = 6;
const firstDigitTranslateX = 6
const secondDigitTranslateX = number < 10
? 9
: number < 20
? 11
: 12;
: 12
const firstDigitElement = digitSvgs[firstDigit](firstDigitTranslateX);
const secondDigitElement = digitSvgs[secondDigit](secondDigitTranslateX);
const firstDigitElement = digitSvgs[firstDigit](firstDigitTranslateX)
const secondDigitElement = digitSvgs[secondDigit](secondDigitTranslateX)
return (
<Box sx={{
@ -44,10 +44,10 @@ export default function NumberIcon({ number, backgroundColor = "rgb(0 0 0 / 0)",
{secondDigitElement}
</svg>
</Box>
);
)
}
const circleSvg = <path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" />;
const circleSvg = <path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="currentColor" strokeWidth="1.5" strokeMiterlimit="10" />
const digitSvgs: Record<number, (translateX: number) => ReactElement> = {
0: (translateX: number) => (
@ -92,4 +92,4 @@ const digitSvgs: Record<number, (translateX: number) => ReactElement> = {
<path transform={`translate(${translateX} 7)`} d="M3.04688 5.6189C4.3154 5.6189 5.34375 4.59055 5.34375 3.32202C5.34375 2.05349 4.3154 1.02515 3.04688 1.02515C1.77835 1.02515 0.75 2.05349 0.75 3.32202C0.75 4.59055 1.77835 5.6189 3.04688 5.6189Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</>
),
};
}

@ -1,7 +1,7 @@
import { useState } from "react";
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material";
import { useState } from "react"
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material"
import type { ChangeEvent } from "react";
import type { ChangeEvent } from "react"
interface Props {
id: string;
@ -11,8 +11,8 @@ interface Props {
}
export default function NumberInputWithUnitAdornment({ id, value, adornmentText, onChange }: Props) {
const theme = useTheme();
const [changed, setChanged] = useState<boolean>(false);
const theme = useTheme()
const [changed, setChanged] = useState<boolean>(false)
return (
<TextField
@ -23,23 +23,23 @@ export default function NumberInputWithUnitAdornment({ id, value, adornmentText,
value={changed ? (value !== 0 ? value : "") : ""}
onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
if (!changed) {
setChanged(true);
setChanged(true)
}
if (Number(target.value) > 999999) {
target.value = "999999";
target.value = "999999"
}
const newNumber = parseInt(target.value);
const newNumber = parseInt(target.value)
if (!isFinite(newNumber) || newNumber < 0) {
onChange(0);
onChange(0)
return;
return
}
onChange(newNumber);
onChange(newNumber)
}}
sx={{
maxWidth: "200px",
@ -98,5 +98,5 @@ export default function NumberInputWithUnitAdornment({ id, value, adornmentText,
),
}}
/>
);
)
}

@ -1,4 +1,4 @@
import { useTheme } from "@mui/material";
import { useTheme } from "@mui/material"
interface Props {
@ -7,7 +7,7 @@ interface Props {
}
export default function PenaLogo({ width, color }: Props) {
const theme = useTheme();
const theme = useTheme()
return (
<svg style={{ minWidth: width }} width={width} viewBox="0 0 180 70" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -31,5 +31,5 @@ export default function PenaLogo({ width, color }: Props) {
</clipPath>
</defs>
</svg>
);
)
}

@ -1,5 +1,5 @@
import { Outlet } from "react-router-dom";
import Navbar from "./NavbarSite/Navbar";
import { Outlet } from "react-router-dom"
import Navbar from "./NavbarSite/Navbar"
import {
Ticket,
getMessageFromFetchError,
@ -8,78 +8,78 @@ import {
useSSESubscription,
useTicketsFetcher,
useToken,
} from "@frontend/kitui";
import { updateTickets, setTicketCount, useTicketStore, setTicketsFetchState } from "@root/stores/tickets";
import { enqueueSnackbar } from "notistack";
import { updateTariffs } from "@root/stores/tariffs";
import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs";
import { setCustomTariffs } from "@root/stores/customTariffs";
import { useDiscounts } from "@root/utils/hooks/useDiscounts";
import { setDiscounts } from "@root/stores/discounts";
import { setPrivileges } from "@root/stores/privileges";
} from "@frontend/kitui"
import { updateTickets, setTicketCount, useTicketStore, setTicketsFetchState } from "@root/stores/tickets"
import { enqueueSnackbar } from "notistack"
import { updateTariffs } from "@root/stores/tariffs"
import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs"
import { setCustomTariffs } from "@root/stores/customTariffs"
import { useDiscounts } from "@root/utils/hooks/useDiscounts"
import { setDiscounts } from "@root/stores/discounts"
import { setPrivileges } from "@root/stores/privileges"
export default function ProtectedLayout() {
const token = useToken();
const ticketApiPage = useTicketStore((state) => state.apiPage);
const ticketsPerPage = useTicketStore((state) => state.ticketsPerPage);
const token = useToken()
const ticketApiPage = useTicketStore((state) => state.apiPage)
const ticketsPerPage = useTicketStore((state) => state.ticketsPerPage)
useSSESubscription<Ticket>({
url: `https://hub.pena.digital/heruvym/subscribe?Authorization=${token}`,
onNewData: (data) => {
updateTickets(data.filter((d) => Boolean(d.id)));
setTicketCount(data.length);
updateTickets(data.filter((d) => Boolean(d.id)))
setTicketCount(data.length)
},
marker: "ticket",
});
})
useTicketsFetcher({
url: "https://hub.pena.digital/heruvym/getTickets",
ticketsPerPage,
ticketApiPage,
onSuccess: (result) => {
if (result.data) updateTickets(result.data);
setTicketCount(result.count);
if (result.data) updateTickets(result.data)
setTicketCount(result.count)
},
onError: (error: Error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
},
onFetchStateChange: setTicketsFetchState,
});
})
useAllTariffsFetcher({
onSuccess: updateTariffs,
onError: (error) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
const errorMessage = getMessageFromFetchError(error)
if (errorMessage) enqueueSnackbar(errorMessage)
},
});
})
useCustomTariffs({
onNewUser: setCustomTariffs,
onError: (error) => {
if (error) enqueueSnackbar(error);
if (error) enqueueSnackbar(error)
},
});
})
useDiscounts({
onNewDiscounts: setDiscounts,
onError: (error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
},
});
})
usePrivilegeFetcher({
onSuccess: setPrivileges,
onError: (error) => {
console.log("usePrivilegeFetcher error :>> ", error);
console.log("usePrivilegeFetcher error :>> ", error)
},
});
})
return (
<Navbar>
<Outlet />
</Navbar>
);
)
}

@ -1,5 +1,5 @@
import { Breakpoint, Container, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material";
import React, { ElementType } from "react";
import { Breakpoint, Container, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material"
import React, { ElementType } from "react"
interface Props {
component?: ElementType;
@ -11,10 +11,10 @@ interface Props {
}
export default function SectionWrapper({ component, outerContainerSx: sx, sx: innerSx, children, maxWidth }: Props) {
const theme = useTheme();
const matchMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const matchMd = useMediaQuery(theme.breakpoints.up("md"))
const isMobile = useMediaQuery(theme.breakpoints.down(380));
const isMobile = useMediaQuery(theme.breakpoints.down(380))
return (
<Container component={component || "div"} maxWidth={false} disableGutters sx={sx}>
@ -29,5 +29,5 @@ export default function SectionWrapper({ component, outerContainerSx: sx, sx: in
{children}
</Container>
</Container>
);
)
}

@ -1,12 +1,12 @@
import { useState, useRef } from "react";
import { Select as MuiSelect, MenuItem, Box, Typography, useTheme } from "@mui/material";
import classnames from "classnames";
import { useState, useRef } from "react"
import { Select as MuiSelect, MenuItem, Box, Typography, useTheme } from "@mui/material"
import classnames from "classnames"
import checkIcon from "@root/assets/Icons/check.svg";
import checkIcon from "@root/assets/Icons/check.svg"
import "./select.css";
import "./select.css"
import type { SelectChangeEvent } from "@mui/material";
import type { SelectChangeEvent } from "@mui/material"
type SelectProps = {
items: string[];
@ -15,15 +15,15 @@ type SelectProps = {
};
export const Select = ({ items, selectedItem, setSelectedItem }: SelectProps) => {
const [opened, setOpened] = useState<boolean>(false);
const [currentValue, setCurrentValue] = useState<string>(items[selectedItem]);
const ref = useRef<HTMLDivElement | null>(null);
const theme = useTheme();
const [opened, setOpened] = useState<boolean>(false)
const [currentValue, setCurrentValue] = useState<string>(items[selectedItem])
const ref = useRef<HTMLDivElement | null>(null)
const theme = useTheme()
const selectItem = (event: SelectChangeEvent<HTMLDivElement>) => {
setCurrentValue(items[Number(event.target.value)]);
setSelectedItem(Number(event.target.value));
};
setCurrentValue(items[Number(event.target.value)])
setSelectedItem(Number(event.target.value))
}
return (
<Box>
@ -84,5 +84,5 @@ export const Select = ({ items, selectedItem, setSelectedItem }: SelectProps) =>
))}
</MuiSelect>
</Box>
);
};
)
}

@ -1,5 +1,5 @@
import { Tabs as MuiTabs } from "@mui/material";
import { CustomTab } from "@root/components/CustomTab";
import { Tabs as MuiTabs } from "@mui/material"
import { CustomTab } from "@root/components/CustomTab"
type TabsProps = {
items: string[];
@ -19,4 +19,4 @@ export const Tabs = ({ items, selectedItem, setSelectedItem }: TabsProps) => (
<CustomTab key={item + index} value={index} label={item} />
))}
</MuiTabs>
);
)

@ -1,6 +1,6 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { enqueueSnackbar } from "notistack"
import {
Alert,
Box,
@ -8,13 +8,13 @@ import {
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
} from "@mui/material"
import { Loader } from "./Loader";
import { Loader } from "./Loader"
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { payCart } from "@root/api/cart";
import { setUserAccount } from "@root/stores/user";
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { payCart } from "@root/api/cart"
import { setUserAccount } from "@root/stores/user"
interface Props {
priceBeforeDiscounts: number;
@ -25,41 +25,41 @@ export default function TotalPrice({
priceAfterDiscounts,
priceBeforeDiscounts,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(550));
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false);
const navigate = useNavigate();
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const isMobile = useMediaQuery(theme.breakpoints.down(550))
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0)
const [loading, setLoading] = useState<boolean>(false)
const navigate = useNavigate()
async function handlePayClick() {
setLoading(true);
setLoading(true)
const [payCartResponse, payCartError] = await payCart();
const [payCartResponse, payCartError] = await payCart()
if (payCartError) {
if (payCartError.includes("insufficient funds: ")) {
const notEnoughMoneyAmount = parseInt(
payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, "")
);
)
setNotEnoughMoneyAmount(notEnoughMoneyAmount);
setNotEnoughMoneyAmount(notEnoughMoneyAmount)
}
setLoading(false);
setLoading(false)
return enqueueSnackbar(payCartError);
return enqueueSnackbar(payCartError)
}
if (payCartResponse) {
setUserAccount(payCartResponse);
setUserAccount(payCartResponse)
}
setLoading(false);
setLoading(false)
}
function handleReplenishWallet() {
navigate("/payment", { state: { notEnoughMoneyAmount } });
navigate("/payment", { state: { notEnoughMoneyAmount } })
}
return (
@ -146,5 +146,5 @@ export default function TotalPrice({
</Button>
</Box>
</Box>
);
)
}

@ -1,5 +1,5 @@
import { Button, ButtonProps, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material";
import { MouseEventHandler, ReactNode } from "react";
import { Button, ButtonProps, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material"
import { MouseEventHandler, ReactNode } from "react"
interface Props {
@ -11,8 +11,8 @@ interface Props {
}
export default function UnderlinedButtonWithIcon({ ButtonProps, icon, children, sx, onClick }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
return (
<Button
@ -41,5 +41,5 @@ export default function UnderlinedButtonWithIcon({ ButtonProps, icon, children,
>
{children}
</Button>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
color: string;
@ -64,5 +64,5 @@ export default function CalendarIcon({ color, bgcolor }: Props) {
/>
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
export default function CloseIcon() {
return (
@ -19,5 +19,5 @@ export default function CloseIcon() {
<path d="M1 1L25 25M1 25L25 1" stroke="black" />
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
export default function CloseSmallIcon() {
@ -17,5 +17,5 @@ export default function CloseSmallIcon() {
<path d="M6 6L18 18" stroke="#A9AAB1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
color: string;
@ -56,5 +56,5 @@ export default function CustomIcon({ color, bgcolor }: Props) {
/>
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { useTheme , Box} from "@mui/material";
import { useTheme , Box} from "@mui/material"
interface Props {
@ -6,7 +6,7 @@ interface Props {
}
export default function ExpandIcon({ isExpanded }: Props) {
const theme = useTheme();
const theme = useTheme()
return (
<Box sx={{

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
export default function EyeIcon() {
@ -17,5 +17,5 @@ export default function EyeIcon() {
<circle cx="10.9495" cy="7.50033" r="3.58333" stroke="#7E2AEA" strokeWidth="1.5" />
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
color?: string;
@ -26,5 +26,5 @@ export default function CustomIcon() {
<path d="M7.65039 17.4375H12.1004" strokeWidth="1.5" strokeLinecap="round"/>
</svg>
</Box>
);
)
}

@ -1,8 +1,8 @@
import { useTheme } from "@mui/material";
import { useTheme } from "@mui/material"
export default function LogoutIcon() {
const theme = useTheme();
const theme = useTheme()
return (
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -10,5 +10,5 @@ export default function LogoutIcon() {
<path d="M15.067 11.475L16.8532 9.71165C17.2498 9.32011 17.2498 8.6799 16.8532 8.28836L15.067 6.52501" stroke={theme.palette.gray.main} strokeWidth="1.5" strokeLinecap="round" />
<path d="M16.7384 9L6.70996 9" stroke={theme.palette.gray.main} strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
@ -20,5 +20,5 @@ export default function PaperClipIcon({ color = "#7E2AEA" }: Props) {
<path d="M15.7541 9.80636C13.714 11.8464 9.42986 16.1306 8.61382 16.9467C7.59378 17.9667 5.14568 19.0548 3.10559 17.0147C1.0655 14.9746 1.47352 12.5265 2.83358 11.1664C4.19364 9.80636 10.9939 3.00608 11.674 2.32605C12.694 1.30601 14.1901 1.44201 15.2101 2.46205C16.2301 3.4821 16.5686 4.91167 15.4141 6.06621C14.0541 7.42626 7.45777 14.1585 6.77775 14.8386C6.09772 15.5186 5.31107 15.276 4.90767 14.8726C4.50426 14.4692 4.26164 13.6825 4.94167 13.0025C5.48569 12.4585 9.79254 8.15163 11.946 5.9982" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
@ -28,5 +28,5 @@ export default function PieChartIcon({ color, bgcolor }: Props) {
<path d="M10.0025 1.00455C11.5795 1.00473 13.1288 1.41928 14.4952 2.2067C15.8616 2.99411 16.9971 4.12674 17.7879 5.49112C18.5788 6.85551 18.9973 8.40375 19.0014 9.98078C19.0056 11.5578 18.5953 13.1082 17.8117 14.4768C17.028 15.8453 15.8985 16.9839 14.5363 17.7785C13.1741 18.5732 11.627 18.9959 10.05 19.0044C8.47303 19.0129 6.92147 18.6069 5.55077 17.827C4.18007 17.0472 3.03836 15.9208 2.23999 14.5608" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { CSSProperties } from "react";
import { CSSProperties } from "react"
interface Props {
@ -13,5 +13,5 @@ export default function SendIcon({ style }: Props) {
<path d="M33.8489 22.1816L15.9232 12.1415C15.7722 12.0581 15.5994 12.0227 15.4277 12.0399C15.2561 12.0571 15.0938 12.1263 14.9624 12.2381C14.831 12.3498 14.7368 12.499 14.6923 12.6657C14.6478 12.8323 14.6551 13.0086 14.7133 13.171L18.0883 22.638C18.1627 22.8218 18.1627 23.0273 18.0883 23.2111L14.7133 32.6781C14.6551 32.8405 14.6478 33.0167 14.6923 33.1834C14.7368 33.3501 14.831 33.4992 14.9624 33.611C15.0938 33.7228 15.2561 33.7919 15.4277 33.8092C15.5994 33.8264 15.7722 33.791 15.9232 33.7076L33.8489 23.6675C33.9816 23.594 34.0922 23.4864 34.1693 23.3558C34.2463 23.2251 34.2869 23.0762 34.2869 22.9245C34.2869 22.7729 34.2463 22.624 34.1693 22.4933C34.0922 22.3627 33.9816 22.255 33.8489 22.1816V22.1816Z" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18.1943 22.9248H24.9868" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
export default function SendIcon() {
@ -18,5 +18,5 @@ export default function SendIcon() {
<path d="M20.25 14.4761V19.8094C20.25 20.0115 20.171 20.2053 20.0303 20.3482C19.8897 20.491 19.6989 20.5713 19.5 20.5713H4.5C4.30109 20.5713 4.11032 20.491 3.96967 20.3482C3.82902 20.2053 3.75 20.0115 3.75 19.8094V14.4761" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Box>
);
)
}

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
interface Props {
color: string;
@ -33,5 +33,5 @@ export default function SendIcon({ color, bgcolor }: Props) {
/>
</svg>
</Box>
);
)
}

@ -8,11 +8,11 @@ import {
Theme,
useMediaQuery,
useTheme,
} from "@mui/material";
import * as React from "react";
import InputAdornment from "@mui/material/InputAdornment";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
} from "@mui/material"
import * as React from "react"
import InputAdornment from "@mui/material/InputAdornment"
import Visibility from "@mui/icons-material/Visibility"
import VisibilityOff from "@mui/icons-material/VisibilityOff"
interface Props {
id: string;
@ -35,24 +35,24 @@ export default function PasswordInput({
color,
FormInputSx,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const labelFont = upMd
? bold
? theme.typography.p1
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
: theme.typography.body2
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" }
const [showPassword, setShowPassword] = React.useState(false);
const [showPassword, setShowPassword] = React.useState(false)
const handleClickShowPassword = () => setShowPassword((show) => !show);
const handleClickShowPassword = () => setShowPassword((show) => !show)
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
};
event.preventDefault()
}
return (
<FormControl
@ -124,5 +124,5 @@ export default function PasswordInput({
type={showPassword ? "text" : "password"}
/>
</FormControl>
);
)
}

@ -1,10 +1,10 @@
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
import card1Image from "@root/assets/landing/card1.png";
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material"
import card1Image from "@root/assets/landing/card1.png"
export default function TemplCardPhoneLight() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const isMobile = useMediaQuery(theme.breakpoints.down(600))
return (
<Box
sx={{
@ -52,5 +52,5 @@ export default function TemplCardPhoneLight() {
<Button variant="pena-contained-light">Подробнее</Button>
</Box>
</Box>
);
)
}

@ -1,10 +1,10 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import CardWithLink from "@components/CardWithLink";
import card1Image from "@root/assets/landing/card1.png";
import { Box, useMediaQuery, useTheme } from "@mui/material"
import CardWithLink from "@components/CardWithLink"
import card1Image from "@root/assets/landing/card1.png"
export default function () {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
return (
<Box
@ -15,7 +15,7 @@ export default function () {
justifyContent: "space-evenly",
columnGap: "40px",
rowGap: "50px",
backgroundColor: '"#E6E6EB',
backgroundColor: "\"#E6E6EB",
}}
>
<CardWithLink
@ -26,5 +26,5 @@ export default function () {
isHighlighted={!upMd}
/>
</Box>
);
)
}

@ -1,7 +1,7 @@
import { Box, Typography, SxProps, Theme, Button, useTheme, useMediaQuery } from "@mui/material";
import cardImageBig from "@root/assets/landing/card1big.png";
import { PenaLink } from "@frontend/kitui";
import { Link as RouterLink } from "react-router-dom";
import { Box, Typography, SxProps, Theme, Button, useTheme, useMediaQuery } from "@mui/material"
import cardImageBig from "@root/assets/landing/card1big.png"
import { PenaLink } from "@frontend/kitui"
import { Link as RouterLink } from "react-router-dom"
interface Props {
light?: boolean;
@ -11,8 +11,8 @@ interface Props {
}
export default function WideTemplCard({ light = true, sx, name="Шаблонизатор", desc="тект заполнитель это текст который имеет" }: Props) {
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const theme = useTheme()
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
return (
<Box
@ -52,5 +52,5 @@ export default function WideTemplCard({ light = true, sx, name="Шаблониз
}}
/>
</Box>
);
)
}

@ -1,4 +1,4 @@
import type { UserDocumentTypes } from "@root/model/user";
import type { UserDocumentTypes } from "@root/model/user"
export const DOCUMENT_TYPE_MAP: Record<
"inn" | "rule" | "egrule" | "certificate",
@ -8,4 +8,4 @@ export const DOCUMENT_TYPE_MAP: Record<
rule: "Устав",
certificate: "Свидетельство о регистрации НКО",
egrule: "ИНН",
};
}

@ -1,70 +1,70 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
import { CssBaseline, ThemeProvider } from "@mui/material";
import Faq from "./pages/Faq/Faq";
import Wallet from "./pages/Wallet";
import Payment from "./pages/Payment/Payment";
import Support from "./pages/Support/Support";
import AccountSettings from "./pages/AccountSettings/AccountSettings";
import Landing from "./pages/Landing/Landing";
import Tariffs from "./pages/Tariffs/Tariffs";
import SigninDialog from "./pages/auth/Signin";
import SignupDialog from "./pages/auth/Signup";
import History from "./pages/History";
import Cart from "./pages/Cart/Cart";
import TariffPage from "./pages/Tariffs/TariffsPage";
import SavedTariffs from "./pages/SavedTariffs";
import PrivateRoute from "@root/utils/routes/ProtectedRoute";
import reportWebVitals from "./reportWebVitals";
import { SnackbarProvider, enqueueSnackbar } from "notistack";
import "./index.css";
import ProtectedLayout from "./components/ProtectedLayout";
import { clearUserData, setUser, setUserAccount, useUserStore } from "./stores/user";
import TariffConstructor from "./pages/TariffConstructor/TariffConstructor";
import { clearAuthToken, getMessageFromFetchError, useUserAccountFetcher, useUserFetcher } from "@frontend/kitui";
import { pdfjs } from "react-pdf";
import { theme } from "./utils/theme";
import React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import { CssBaseline, ThemeProvider } from "@mui/material"
import Faq from "./pages/Faq/Faq"
import Wallet from "./pages/Wallet"
import Payment from "./pages/Payment/Payment"
import Support from "./pages/Support/Support"
import AccountSettings from "./pages/AccountSettings/AccountSettings"
import Landing from "./pages/Landing/Landing"
import Tariffs from "./pages/Tariffs/Tariffs"
import SigninDialog from "./pages/auth/Signin"
import SignupDialog from "./pages/auth/Signup"
import History from "./pages/History"
import Cart from "./pages/Cart/Cart"
import TariffPage from "./pages/Tariffs/TariffsPage"
import SavedTariffs from "./pages/SavedTariffs"
import PrivateRoute from "@root/utils/routes/ProtectedRoute"
import reportWebVitals from "./reportWebVitals"
import { SnackbarProvider, enqueueSnackbar } from "notistack"
import "./index.css"
import ProtectedLayout from "./components/ProtectedLayout"
import { clearUserData, setUser, setUserAccount, useUserStore } from "./stores/user"
import TariffConstructor from "./pages/TariffConstructor/TariffConstructor"
import { clearAuthToken, getMessageFromFetchError, useUserAccountFetcher, useUserFetcher } from "@frontend/kitui"
import { pdfjs } from "react-pdf"
import { theme } from "./utils/theme"
pdfjs.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.js", import.meta.url).toString();
pdfjs.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.js", import.meta.url).toString()
const App = () => {
console.log("render app")
const location = useLocation();
const userId = useUserStore((state) => state.userId);
const navigate = useNavigate();
const location = useLocation()
const userId = useUserStore((state) => state.userId)
const navigate = useNavigate()
useUserFetcher({
url: `https://hub.pena.digital/user/${userId}`,
userId,
onNewUser: setUser,
onError: (error) => {
const errorMessage = getMessageFromFetchError(error);
const errorMessage = getMessageFromFetchError(error)
if (errorMessage) {
enqueueSnackbar(errorMessage);
clearUserData();
clearAuthToken();
enqueueSnackbar(errorMessage)
clearUserData()
clearAuthToken()
}
},
});
})
useUserAccountFetcher({
url: "https://hub.pena.digital/customer/account",
userId,
onNewUserAccount: setUserAccount,
onError: (error) => {
const errorMessage = getMessageFromFetchError(error);
const errorMessage = getMessageFromFetchError(error)
if (errorMessage) {
enqueueSnackbar(errorMessage);
clearUserData();
clearAuthToken();
navigate("/signin");
enqueueSnackbar(errorMessage)
clearUserData()
clearAuthToken()
navigate("/signin")
}
},
});
})
if (location.state?.redirectTo)
return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />;
return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />
return (
<>
@ -97,10 +97,10 @@ const App = () => {
</Route>
</Routes>
</>
);
};
)
}
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
root.render(
// <React.StrictMode>
<ThemeProvider theme={theme}>
@ -111,9 +111,9 @@ root.render(
</BrowserRouter>
</ThemeProvider>
// </React.StrictMode>
);
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
reportWebVitals()

@ -1,4 +1,4 @@
import { UserName } from "@frontend/kitui";
import { UserName } from "@frontend/kitui"
export enum VerificationStatus {

@ -1,4 +1,4 @@
import type { Attachment } from "@root/model/attachment";
import type { Attachment } from "@root/model/attachment"
export type File = {
name: "inn" | "rule" | "egrule" | "certificate";

@ -1,5 +1,5 @@
import { PrivilegeWithAmount } from "@frontend/kitui";
import { PrivilegeWithoutPrice } from "./privilege";
import { PrivilegeWithAmount } from "@frontend/kitui"
import { PrivilegeWithoutPrice } from "./privilege"
type ServiceKey = string;

@ -1,4 +1,4 @@
import { Discount } from "@frontend/kitui";
import { Discount } from "@frontend/kitui"
export interface GetDiscountsResponse {

@ -1,4 +1,4 @@
import { Privilege, PrivilegeWithAmount } from "@frontend/kitui";
import { Privilege, PrivilegeWithAmount } from "@frontend/kitui"
export type ServiceKeyToPrivilegesMap = Record<string, Privilege[]>;

@ -1,4 +1,4 @@
import { Tariff } from "@frontend/kitui";
import { Tariff } from "@frontend/kitui"
export interface GetTariffsResponse {

@ -1,54 +1,54 @@
import { useEffect } from "react";
import { Box, Button, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material";
import InputTextfield from "@components/InputTextfield";
import PasswordInput from "@components/passwordInput";
import UserFields from "./UserFields";
import SectionWrapper from "@components/SectionWrapper";
import { openDocumentsDialog, sendUserData, setSettingsField, useUserStore } from "@root/stores/user";
import UnderlinedButtonWithIcon from "@root/components/UnderlinedButtonWithIcon";
import UploadIcon from "@root/components/icons/UploadIcon";
import DocumentsDialog from "./DocumentsDialog/DocumentsDialog";
import EyeIcon from "@root/components/icons/EyeIcon";
import { cardShadow } from "@root/utils/theme";
import { getMessageFromFetchError } from "@frontend/kitui";
import { enqueueSnackbar } from "notistack";
import { VerificationStatus } from "@root/model/account";
import { verify } from "./helper";
import { withErrorBoundary } from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError";
import { useEffect } from "react"
import { Box, Button, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material"
import InputTextfield from "@components/InputTextfield"
import PasswordInput from "@components/passwordInput"
import UserFields from "./UserFields"
import SectionWrapper from "@components/SectionWrapper"
import { openDocumentsDialog, sendUserData, setSettingsField, useUserStore } from "@root/stores/user"
import UnderlinedButtonWithIcon from "@root/components/UnderlinedButtonWithIcon"
import UploadIcon from "@root/components/icons/UploadIcon"
import DocumentsDialog from "./DocumentsDialog/DocumentsDialog"
import EyeIcon from "@root/components/icons/EyeIcon"
import { cardShadow } from "@root/utils/theme"
import { getMessageFromFetchError } from "@frontend/kitui"
import { enqueueSnackbar } from "notistack"
import { VerificationStatus } from "@root/model/account"
import { verify } from "./helper"
import { withErrorBoundary } from "react-error-boundary"
import { handleComponentError } from "@root/utils/handleComponentError"
function AccountSettings() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const isMobile = useMediaQuery(theme.breakpoints.down(600))
const fields = useUserStore((state) => state.settingsFields);
const verificationStatus = useUserStore((state) => state.verificationStatus);
const verificationType = useUserStore((state) => state.verificationType);
const comment = useUserStore((state) => state.comment);
const userId = useUserStore((state) => state.userId) ?? "";
const fields = useUserStore((state) => state.settingsFields)
const verificationStatus = useUserStore((state) => state.verificationStatus)
const verificationType = useUserStore((state) => state.verificationType)
const comment = useUserStore((state) => state.comment)
const userId = useUserStore((state) => state.userId) ?? ""
useEffect(() => {
verify(userId);
}, []);
verify(userId)
}, [])
const textFieldProps = {
gap: upMd ? "16px" : "10px",
color: "#F2F3F7",
bold: true,
};
}
function handleSendDataClick() {
sendUserData()
.then(() => {
enqueueSnackbar("Информация обновлена");
enqueueSnackbar("Информация обновлена")
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
})
}
return (
@ -139,7 +139,7 @@ function AccountSettings() {
</Button>
</Box>
</SectionWrapper>
);
)
}
export default withErrorBoundary(AccountSettings, {
@ -151,7 +151,7 @@ const verificationStatusData: Record<VerificationStatus, { text: string; color:
verificated: { text: "Верификация пройдена", color: "#0D9F00" },
waiting: { text: "В ожидании верификации", color: "#F18956" },
notVerificated: { text: "Не верифицирован", color: "#E02C2C" },
};
}
function VerificationIndicator({
verificationStatus,
@ -176,5 +176,5 @@ function VerificationIndicator({
>
<Typography lineHeight="100%">{verificationStatusData[verificationStatus].text}</Typography>
</Box>
);
)
}

@ -1,8 +1,8 @@
import axios from "axios";
import { Box, SxProps, Theme, Typography, useTheme } from "@mui/material";
import { Document, Page } from "react-pdf";
import { Buffer } from "buffer";
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice";
import axios from "axios"
import { Box, SxProps, Theme, Typography, useTheme } from "@mui/material"
import { Document, Page } from "react-pdf"
import { Buffer } from "buffer"
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice"
interface Props {
text: string;
@ -11,24 +11,24 @@ interface Props {
}
export default function DocumentItem({ text, documentUrl = "", sx }: Props) {
const theme = useTheme();
const theme = useTheme()
const downloadFile = async () => {
const { data } = await axios.get<ArrayBuffer>(documentUrl, {
responseType: "arraybuffer",
});
})
if (!data) {
return;
return
}
downloadFileToDevice(
`${documentUrl.split("/").pop()?.split(".")?.[0] || "document"}.pdf`,
Buffer.from(data)
);
)
return;
};
return
}
return (
<Box
@ -68,5 +68,5 @@ export default function DocumentItem({ text, documentUrl = "", sx }: Props) {
</>
)}
</Box>
);
)
}

@ -1,16 +1,16 @@
import { ChangeEvent, useRef } from "react";
import axios from "axios";
import { Document, Page, pdfjs } from "react-pdf";
import { Box, SxProps, Theme, Typography } from "@mui/material";
import { Buffer } from "buffer";
import { ChangeEvent, useRef } from "react"
import axios from "axios"
import { Document, Page, pdfjs } from "react-pdf"
import { Box, SxProps, Theme, Typography } from "@mui/material"
import { Buffer } from "buffer"
import UnderlinedButtonWithIcon from "@root/components/UnderlinedButtonWithIcon";
import PaperClipIcon from "@root/components/icons/PaperClipIcon";
import UnderlinedButtonWithIcon from "@root/components/UnderlinedButtonWithIcon"
import PaperClipIcon from "@root/components/icons/PaperClipIcon"
import { UserDocument } from "@root/model/user";
import { UserDocument } from "@root/model/user"
import { readFile } from "@root/utils/readFile";
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice";
import { readFile } from "@root/utils/readFile"
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice"
interface Props {
text: string;
@ -24,7 +24,7 @@ interface Props {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.js",
import.meta.url
).toString();
).toString()
export default function DocumentUploadItem({
text,
@ -34,33 +34,33 @@ export default function DocumentUploadItem({
sx,
accept = "image/*",
}: Props) {
const fileInputRef = useRef<HTMLInputElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null)
function handleChooseFileClick() {
fileInputRef.current?.click();
fileInputRef.current?.click()
}
const downloadFile = async () => {
if (!document.file && documentUrl) {
const { data } = await axios.get<ArrayBuffer>(documentUrl, {
responseType: "arraybuffer",
});
})
if (!data) {
return;
return
}
downloadFileToDevice("document.pdf", Buffer.from(data));
downloadFileToDevice("document.pdf", Buffer.from(data))
return;
return
}
if (document.file) {
const fileArrayBuffer = await readFile(document.file, "array");
const fileArrayBuffer = await readFile(document.file, "array")
downloadFileToDevice(document.file.name, Buffer.from(fileArrayBuffer));
downloadFileToDevice(document.file.name, Buffer.from(fileArrayBuffer))
}
}
};
return (
<Box
@ -106,5 +106,5 @@ export default function DocumentUploadItem({
/>
</Document>
</Box>
);
)
}

@ -1,11 +1,11 @@
import { useUserStore } from "@root/stores/user";
import NkoDocumentsDialog from "./NkoDocumentsDialog";
import JuridicalDocumentsDialog from "./JuridicalDocumentsDialog";
import { useUserStore } from "@root/stores/user"
import NkoDocumentsDialog from "./NkoDocumentsDialog"
import JuridicalDocumentsDialog from "./JuridicalDocumentsDialog"
export default function DocumentsDialog() {
switch(useUserStore(state => state.dialogType)) {
case 'juridical':
case "juridical":
return <JuridicalDocumentsDialog />
case "nko":

@ -1,20 +1,20 @@
import { Box, Button, Dialog, IconButton, Typography } from "@mui/material";
import { enqueueSnackbar } from "notistack";
import CloseSmallIcon from "@root/components/icons/CloseSmallIcon";
import { Box, Button, Dialog, IconButton, Typography } from "@mui/material"
import { enqueueSnackbar } from "notistack"
import CloseSmallIcon from "@root/components/icons/CloseSmallIcon"
import {
closeDocumentsDialog,
setDocument,
useUserStore,
} from "@root/stores/user";
import DocumentUploadItem from "./DocumentUploadItem";
import DocumentItem from "./DocumentItem";
import { VerificationStatus } from "@root/model/account";
import { sendDocuments, updateDocuments } from "@root/api/verification";
import { readFile } from "@root/utils/readFile";
import { deleteEmptyKeys } from "@root/utils/deleteEmptyKeys";
import { verify } from "../helper";
import { useState } from "react";
import { theme } from "@root/utils/theme";
} from "@root/stores/user"
import DocumentUploadItem from "./DocumentUploadItem"
import DocumentItem from "./DocumentItem"
import { VerificationStatus } from "@root/model/account"
import { sendDocuments, updateDocuments } from "@root/api/verification"
import { readFile } from "@root/utils/readFile"
import { deleteEmptyKeys } from "@root/utils/deleteEmptyKeys"
import { verify } from "../helper"
import { useState } from "react"
import { theme } from "@root/utils/theme"
const dialogContainerStyle = {
height: "100%",
@ -22,50 +22,50 @@ const dialogContainerStyle = {
"::-webkit-scrollbar": {
display: "none",
},
};
}
export default function JuridicalDocumentsDialog() {
const isOpen = useUserStore((state) => state.isDocumentsDialogOpen);
const verificationStatus = useUserStore((state) => state.verificationStatus);
const documents = useUserStore((state) => state.documents);//загруженные юзером файлы
const documentsUrl = useUserStore((state) => state.documentsUrl);//ссылки с бекенда
const userId = useUserStore((state) => state.userId) ?? "";
const isOpen = useUserStore((state) => state.isDocumentsDialogOpen)
const verificationStatus = useUserStore((state) => state.verificationStatus)
const documents = useUserStore((state) => state.documents)//загруженные юзером файлы
const documentsUrl = useUserStore((state) => state.documentsUrl)//ссылки с бекенда
const userId = useUserStore((state) => state.userId) ?? ""
const sendUploadedDocuments = async () => {
if (documents["ИНН"].file && documents["Устав"].file && !documentsUrl["ИНН"] && !documentsUrl["Устав"]) {
closeDocumentsDialog();
closeDocumentsDialog()
//Пользователь заполнил все поля и на беке пусто
const inn = await readFile(documents["ИНН"].file, "binary");
const rule = await readFile(documents["Устав"].file, "binary");
const inn = await readFile(documents["ИНН"].file, "binary")
const rule = await readFile(documents["Устав"].file, "binary")
const [_, sendDocumentsError] = await sendDocuments({
status: "org",
inn,
rule,
});
})
if (sendDocumentsError) {
enqueueSnackbar(sendDocumentsError);
return;
enqueueSnackbar(sendDocumentsError)
return
}
if (_ === "OK") {
enqueueSnackbar("Информация доставлена")
}
setDocument("ИНН", null);
setDocument("Устав", null);
setDocument("ИНН", null)
setDocument("Устав", null)
await verify(userId);
await verify(userId)
} else { //Пользователь заполнил не все, или на беке что-то есть
if ((documents["ИНН"].file || documents["Устав"].file) && (documentsUrl["ИНН"] || documentsUrl["Устав"])) { //минимум 1 поле заполнено на фронте и минимум 1 поле на беке записано
closeDocumentsDialog();
closeDocumentsDialog()
const inn = documents["ИНН"].file
? await readFile(documents["ИНН"].file, "binary")
: undefined;
: undefined
const rule = documents["Устав"].file
? await readFile(documents["Устав"].file, "binary")
: undefined;
: undefined
const [_, updateDocumentsError] = await updateDocuments(
deleteEmptyKeys({
@ -73,24 +73,24 @@ export default function JuridicalDocumentsDialog() {
inn,
rule,
})
);
)
if (updateDocumentsError) {
enqueueSnackbar(updateDocumentsError);
enqueueSnackbar(updateDocumentsError)
return;
return
}
if (_ === "OK") {
enqueueSnackbar("Информация доставлена")
}
setDocument("ИНН", null);
setDocument("Устав", null);
setDocument("ИНН", null)
setDocument("Устав", null)
await verify(userId);
await verify(userId)
}
}
}
};
const disbutt = () => {
if (documents["ИНН"].file && documents["Устав"].file && !documentsUrl["ИНН"] && !documentsUrl["Устав"]) { //post
@ -136,7 +136,7 @@ export default function JuridicalDocumentsDialog() {
}
/>
</>
);
)
return (
<Dialog
@ -223,5 +223,5 @@ export default function JuridicalDocumentsDialog() {
</Button>
)}
</Dialog>
);
)
}

@ -1,20 +1,20 @@
import { Box, Button, Dialog, IconButton, Typography } from "@mui/material";
import { enqueueSnackbar } from "notistack";
import CloseSmallIcon from "@root/components/icons/CloseSmallIcon";
import { Box, Button, Dialog, IconButton, Typography } from "@mui/material"
import { enqueueSnackbar } from "notistack"
import CloseSmallIcon from "@root/components/icons/CloseSmallIcon"
import {
closeDocumentsDialog,
setDocument,
useUserStore,
} from "@root/stores/user";
import DocumentUploadItem from "./DocumentUploadItem";
import DocumentItem from "./DocumentItem";
import { verify } from "../helper";
import { VerificationStatus } from "@root/model/account";
import { sendDocuments, updateDocuments } from "@root/api/verification";
import { readFile } from "@root/utils/readFile";
import { deleteEmptyKeys } from "@root/utils/deleteEmptyKeys";
import { useState } from "react";
import { theme } from "@root/utils/theme";
} from "@root/stores/user"
import DocumentUploadItem from "./DocumentUploadItem"
import DocumentItem from "./DocumentItem"
import { verify } from "../helper"
import { VerificationStatus } from "@root/model/account"
import { sendDocuments, updateDocuments } from "@root/api/verification"
import { readFile } from "@root/utils/readFile"
import { deleteEmptyKeys } from "@root/utils/deleteEmptyKeys"
import { useState } from "react"
import { theme } from "@root/utils/theme"
const dialogContainerStyle = {
height: "100%",
@ -22,14 +22,14 @@ const dialogContainerStyle = {
"::-webkit-scrollbar": {
display: "none",
},
};
}
export default function NkoDocumentsDialog() {
const isOpen = useUserStore((state) => state.isDocumentsDialogOpen);
const verificationStatus = useUserStore((state) => state.verificationStatus);
const documents = useUserStore((state) => state.documents);
const documentsUrl = useUserStore((state) => state.documentsUrl);
const userId = useUserStore((state) => state.userId) ?? "";
const isOpen = useUserStore((state) => state.isDocumentsDialogOpen)
const verificationStatus = useUserStore((state) => state.verificationStatus)
const documents = useUserStore((state) => state.documents)
const documentsUrl = useUserStore((state) => state.documentsUrl)
const userId = useUserStore((state) => state.userId) ?? ""
const sendUploadedDocuments = async () => {
if (
@ -38,51 +38,51 @@ export default function NkoDocumentsDialog() {
documents["Свидетельство о регистрации НКО"].file
&& !documentsUrl["ИНН"] && !documentsUrl["Устав"] && !documentsUrl["Свидетельство о регистрации НКО"]
) {
closeDocumentsDialog();
closeDocumentsDialog()
//Пользователь заполнил все поля и на беке пусто
const inn = await readFile(documents["ИНН"].file, "binary");
const rule = await readFile(documents["Устав"].file, "binary");
const inn = await readFile(documents["ИНН"].file, "binary")
const rule = await readFile(documents["Устав"].file, "binary")
const certificate = await readFile(
documents["Свидетельство о регистрации НКО"].file,
"binary"
);
)
const [_, sendDocumentsError] = await sendDocuments({
status: "nko",
inn,
rule,
certificate,
});
})
if (sendDocumentsError) {
enqueueSnackbar(sendDocumentsError);
enqueueSnackbar(sendDocumentsError)
return;
return
}
if (_ === "OK") {
enqueueSnackbar("Информация доставлена")
}
setDocument("ИНН", null);
setDocument("Устав", null);
setDocument("Свидетельство о регистрации НКО", null);
setDocument("ИНН", null)
setDocument("Устав", null)
setDocument("Свидетельство о регистрации НКО", null)
await verify(userId);
await verify(userId)
} else { //Пользователь заполнил не все, или на беке что-то есть
if ((documents["ИНН"].file || documents["Устав"].file || documents["Свидетельство о регистрации НКО"].file) && (documentsUrl["ИНН"] || documentsUrl["Устав"] || documentsUrl["Свидетельство о регистрации НКО"])) { //минимум 1 поле заполнено на фронте и минимум 1 поле на беке записано
closeDocumentsDialog();
closeDocumentsDialog()
const inn = documents["ИНН"].file
? await readFile(documents["ИНН"].file, "binary")
: undefined;
: undefined
const rule = documents["Устав"].file
? await readFile(documents["Устав"].file, "binary")
: undefined;
: undefined
const certificate = documents["Свидетельство о регистрации НКО"].file
? await readFile(
documents["Свидетельство о регистрации НКО"].file,
"binary"
)
: undefined;
: undefined
const [_, updateDocumentsError] = await updateDocuments(
deleteEmptyKeys({
@ -91,26 +91,26 @@ export default function NkoDocumentsDialog() {
rule,
certificate,
})
);
)
if (updateDocumentsError) {
enqueueSnackbar(updateDocumentsError);
enqueueSnackbar(updateDocumentsError)
return;
return
}
if (_ === "OK") {
enqueueSnackbar("Информация доставлена")
}
setDocument("ИНН", null);
setDocument("Устав", null);
setDocument("Свидетельство о регистрации НКО", null);
setDocument("ИНН", null)
setDocument("Устав", null)
setDocument("Свидетельство о регистрации НКО", null)
await verify(userId);
await verify(userId)
}
}
};
}
const disbutt = () => {
if (documents["ИНН"].file && documents["Устав"].file && documents["Свидетельство о регистрации НКО"].file && !documentsUrl["ИНН"] && !documentsUrl["Устав"] && !documentsUrl["Свидетельство о регистрации НКО"]) { //post
@ -172,7 +172,7 @@ export default function NkoDocumentsDialog() {
}
/>
</>
);
)
return (
<Dialog
@ -259,5 +259,5 @@ export default function NkoDocumentsDialog() {
)}
<></>
</Dialog>
);
)
}

@ -1,15 +1,15 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import InputTextfield from "@components/InputTextfield";
import PasswordInput from "@components/passwordInput";
import { setSettingsField, useUserStore } from "@root/stores/user";
import { Box, useMediaQuery, useTheme } from "@mui/material"
import InputTextfield from "@components/InputTextfield"
import PasswordInput from "@components/passwordInput"
import { setSettingsField, useUserStore } from "@root/stores/user"
export default function UserFields () {
const theme = useTheme();
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const fields = useUserStore((state) => state.settingsFields);
const fields = useUserStore((state) => state.settingsFields)
console.log("fields")
@ -17,7 +17,7 @@ export default function UserFields () {
gap: upMd ? "16px" : "10px",
color: "#F2F3F7",
bold: true,
};
}
return(
<Box

@ -1,68 +1,68 @@
import { devlog } from "@frontend/kitui";
import { devlog } from "@frontend/kitui"
import { verification } from "@root/api/verification";
import { verification } from "@root/api/verification"
import {
setVerificationStatus,
setVerificationType,
setComment,
setDocumentUrl,
} from "@root/stores/user";
import { VerificationStatus } from "@root/model/account";
} from "@root/stores/user"
import { VerificationStatus } from "@root/model/account"
import { DOCUMENT_TYPE_MAP } from "@root/constants/documentTypeMap";
import { DOCUMENT_TYPE_MAP } from "@root/constants/documentTypeMap"
import type { Verification } from "@root/model/auth";
import type { Verification } from "@root/model/auth"
const updateVerificationStatus = (verification: Verification) => {
if (!verification) {
setVerificationStatus(VerificationStatus.NOT_VERIFICATED);
setVerificationStatus(VerificationStatus.NOT_VERIFICATED)
return;
return
}
if (verification.accepted) {
setVerificationStatus(VerificationStatus.VERIFICATED);
setVerificationStatus(VerificationStatus.VERIFICATED)
return;
return
}
if (
(!verification.accepted && !verification.files?.length) ||
(!verification.accepted && !verification.comment)
) {
setVerificationStatus(VerificationStatus.NOT_VERIFICATED);
setVerificationStatus(VerificationStatus.NOT_VERIFICATED)
return;
return
}
if (verification.files?.length && !verification.comment) {
setVerificationStatus(VerificationStatus.WAITING);
setVerificationStatus(VerificationStatus.WAITING)
return;
return
}
setVerificationStatus(VerificationStatus.NOT_VERIFICATED);
};
setVerificationStatus(VerificationStatus.NOT_VERIFICATED)
}
export const verify = async (id: string) => {
const [verificationResult, verificationError] = await verification(id);
const [verificationResult, verificationError] = await verification(id)
if (verificationError) {
setVerificationStatus(VerificationStatus.NOT_VERIFICATED);
setVerificationStatus(VerificationStatus.NOT_VERIFICATED)
devlog("Error fetching user", verificationError);
devlog("Error fetching user", verificationError)
return;
return
}
if (verificationResult) {
updateVerificationStatus(verificationResult);
setVerificationType(verificationResult.status);
setComment(verificationResult.comment);
updateVerificationStatus(verificationResult)
setVerificationType(verificationResult.status)
setComment(verificationResult.comment)
verificationResult.files.forEach((file) =>
setDocumentUrl(DOCUMENT_TYPE_MAP[file.name], file.url)
);
)
devlog("User", verificationResult);
devlog("User", verificationResult)
}
}
};

@ -1,26 +1,26 @@
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
import SectionWrapper from "@components/SectionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import TotalPrice from "@components/TotalPrice";
import CustomWrapper from "./CustomWrapper";
import { useCart } from "@root/utils/hooks/useCart";
import { useLocation } from "react-router-dom";
import { usePrevLocation } from "@root/utils/hooks/handleCustomBackNavigation";
import { handleComponentError } from "@root/utils/handleComponentError";
import { withErrorBoundary } from "react-error-boundary";
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"
import SectionWrapper from "@components/SectionWrapper"
import ArrowBackIcon from "@mui/icons-material/ArrowBack"
import TotalPrice from "@components/TotalPrice"
import CustomWrapper from "./CustomWrapper"
import { useCart } from "@root/utils/hooks/useCart"
import { useLocation } from "react-router-dom"
import { usePrevLocation } from "@root/utils/hooks/handleCustomBackNavigation"
import { handleComponentError } from "@root/utils/handleComponentError"
import { withErrorBoundary } from "react-error-boundary"
function Cart() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(550));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const cart = useCart();
const location = useLocation();
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const isMobile = useMediaQuery(theme.breakpoints.down(550))
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const cart = useCart()
const location = useLocation()
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts;
const totalPriceAfterDiscounts = cart.priceAfterDiscounts;
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts
const totalPriceAfterDiscounts = cart.priceAfterDiscounts
const handleCustomBackNavigation = usePrevLocation(location);
const handleCustomBackNavigation = usePrevLocation(location)
return (
@ -71,7 +71,7 @@ function Cart() {
</Box>
<TotalPrice priceBeforeDiscounts={totalPriceBeforeDiscounts} priceAfterDiscounts={totalPriceAfterDiscounts} />
</SectionWrapper>
);
)
}
export default withErrorBoundary(Cart, {

@ -1,21 +1,21 @@
import { useState } from "react";
import { Box, SvgIcon, Typography, useMediaQuery, useTheme } from "@mui/material";
import ExpandIcon from "@components/icons/ExpandIcon";
import ClearIcon from "@mui/icons-material/Clear";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { removeTariffFromCart } from "@root/stores/user";
import { enqueueSnackbar } from "notistack";
import { CloseButton, ServiceCartData, getMessageFromFetchError } from "@frontend/kitui";
import { useState } from "react"
import { Box, SvgIcon, Typography, useMediaQuery, useTheme } from "@mui/material"
import ExpandIcon from "@components/icons/ExpandIcon"
import ClearIcon from "@mui/icons-material/Clear"
import { currencyFormatter } from "@root/utils/currencyFormatter"
import { removeTariffFromCart } from "@root/stores/user"
import { enqueueSnackbar } from "notistack"
import { CloseButton, ServiceCartData, getMessageFromFetchError } from "@frontend/kitui"
import type { MouseEvent } from "react";
import CustomTariffAccordion from "@root/components/CustomTariffAccordion";
import type { MouseEvent } from "react"
import CustomTariffAccordion from "@root/components/CustomTariffAccordion"
const name: Record<string, string> = {
templategen: "Шаблонизатор",
squiz: "Опросник",
reducer: "Сокращатель ссылок",
custom: "Кастомные тарифы",
};
}
interface Props {
serviceData: ServiceCartData;
@ -24,35 +24,35 @@ interface Props {
}
export default function CustomWrapper({ serviceData, last, first }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const [isExpanded, setIsExpanded] = useState<boolean>(false)
function handleItemDeleteClick(tariffId: string) {
removeTariffFromCart(tariffId)
.then(() => {
enqueueSnackbar("Тариф удален");
enqueueSnackbar("Тариф удален")
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
})
}
const deleteService = async (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
event.stopPropagation()
setIsExpanded(false);
setIsExpanded(false)
for (const { id } of serviceData.tariffs) {
try {
await removeTariffFromCart(id);
await removeTariffFromCart(id)
} catch {}
}
enqueueSnackbar("Тарифы удалены");
};
enqueueSnackbar("Тарифы удалены")
}
return (
<Box
@ -133,7 +133,7 @@ export default function CustomWrapper({ serviceData, last, first }: Props) {
</Box>
{isExpanded &&
serviceData.tariffs.map((tariff) => {
const privilege = tariff.privileges[0];
const privilege = tariff.privileges[0]
return tariff.privileges.length > 1 ? (
@ -192,9 +192,9 @@ export default function CustomWrapper({ serviceData, last, first }: Props) {
<SvgIcon component={ClearIcon} sx={{ fill: theme.palette.purple.main }} />
</Box>
</Box>
);
)
})}
</Box>
</Box>
);
)
}

@ -1,7 +1,7 @@
import { Box } from "@mui/material";
import { Box } from "@mui/material"
import CustomAccordion from "@components/CustomAccordion";
import { cardShadow } from "@root/utils/theme";
import CustomAccordion from "@components/CustomAccordion"
import { cardShadow } from "@root/utils/theme"
interface Props {
content: [string, string][];
@ -20,5 +20,5 @@ export default function AccordionWrapper({ content }: Props) {
<CustomAccordion key={index} header={accordionItem[0]} text={accordionItem[1]} />
))}
</Box>
);
)
}

@ -1,25 +1,25 @@
import { IconButton, useMediaQuery, useTheme } from "@mui/material";
import { Select } from "@root/components/Select";
import { Box } from "@mui/material";
import { Typography } from "@mui/material";
import { useState } from "react";
import SectionWrapper from "../../components/SectionWrapper";
import AccordionWrapper from "./AccordionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { Tabs } from "@root/components/Tabs";
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
import { IconButton, useMediaQuery, useTheme } from "@mui/material"
import { Select } from "@root/components/Select"
import { Box } from "@mui/material"
import { Typography } from "@mui/material"
import { useState } from "react"
import SectionWrapper from "../../components/SectionWrapper"
import AccordionWrapper from "./AccordionWrapper"
import ArrowBackIcon from "@mui/icons-material/ArrowBack"
import { Tabs } from "@root/components/Tabs"
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker"
const subPages = ["Pena hub", "Шаблоны", "Опросы", "Ссылки", "Финансовые", "Юридические", "Юридические лица"];
const subPages = ["Pena hub", "Шаблоны", "Опросы", "Ссылки", "Финансовые", "Юридические", "Юридические лица"]
export default function Faq() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(550));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const [tabIndex, setTabIndex] = useState<number>(0);
const [selectedItem, setSelectedItem] = useState<number>(0);
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const isMobile = useMediaQuery(theme.breakpoints.down(550))
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const [tabIndex, setTabIndex] = useState<number>(0)
const [selectedItem, setSelectedItem] = useState<number>(0)
const handleCustomBackNavigation = useHistoryTracker();
const handleCustomBackNavigation = useHistoryTracker()
return (
<SectionWrapper
@ -164,7 +164,7 @@ export default function Faq() {
/>
</TabPanel>
</SectionWrapper>
);
)
}
interface TabPanelProps {
@ -179,5 +179,5 @@ function TabPanel({ index, value, children, mt }: TabPanelProps) {
<Box hidden={index !== value} sx={{ mt }}>
{children}
</Box>
);
)
}

@ -1,10 +1,10 @@
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomAccordion from "@components/CustomAccordion";
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"
import CustomAccordion from "@components/CustomAccordion"
import File from "@components/icons/File"
import { getDeclension } from "@utils/declension"
import { enqueueSnackbar } from "notistack";
import { addTariffToCart } from "@root/stores/user";
import { getMessageFromFetchError } from "@frontend/kitui";
import { enqueueSnackbar } from "notistack"
import { addTariffToCart } from "@root/stores/user"
import { getMessageFromFetchError } from "@frontend/kitui"
export type History = {
title: string;
@ -25,32 +25,32 @@ interface AccordionWrapperProps {
}
export default function AccordionWrapper({ content, last, first, createdAt }: AccordionWrapperProps) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isMobile = useMediaQuery(theme.breakpoints.down(560));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const isTablet = useMediaQuery(theme.breakpoints.down(900))
const isMobile = useMediaQuery(theme.breakpoints.down(560))
const valuesByKey: any = {};
const valuesByKey: any = {}
content[0].Value[0].forEach((item) => {
valuesByKey[item.Key] = item.Value;
});
valuesByKey[item.Key] = item.Value
})
console.log(content)
console.log(content[0])
console.log(content[0].Value)
console.log(valuesByKey)
const extractDateFromString = (tariffName: string) => {
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/);
return dateMatch ? dateMatch[0] : null;
};
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/)
return dateMatch ? dateMatch[0] : null
}
async function handleTariffItemClick(tariffId: string) {
const { patchCartError } = await addTariffToCart(tariffId)
if (patchCartError) {
enqueueSnackbar(patchCartError);
enqueueSnackbar(patchCartError)
} else {
enqueueSnackbar("Тариф добавлен в корзину");
enqueueSnackbar("Тариф добавлен в корзину")
}
}
@ -118,10 +118,10 @@ export default function AccordionWrapper({ content, last, first, createdAt }: Ac
fontWeight: 500,
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
px: 0,
width: '200px',
maxWidth: '200px',
overflow: 'hidden',
textOverflow: 'ellipsis'
width: "200px",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
}}
>
{valuesByKey.iscustom ? "Кастомный тариф" : valuesByKey.name}
@ -150,10 +150,10 @@ export default function AccordionWrapper({ content, last, first, createdAt }: Ac
>
{valuesByKey.payMethod && <Typography
sx={{
maxWidth: '300px',
width: '300px',
overflow: 'hidden',
textOverflow: 'ellipsis'
maxWidth: "300px",
width: "300px",
overflow: "hidden",
textOverflow: "ellipsis"
}}
>Способ оплаты: {valuesByKey.payMethod}</Typography>}
</Typography>
@ -236,5 +236,5 @@ export default function AccordionWrapper({ content, last, first, createdAt }: Ac
}
/>
</Box>
);
)
}

@ -1,4 +1,4 @@
import type { History } from "./AccordionWrapper";
import type { History } from "./AccordionWrapper"
const PAYMENT_HISTORY: History[] = [
{
@ -43,7 +43,7 @@ const PAYMENT_HISTORY: History[] = [
info: "6 190 руб.",
description: "Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
];
]
const PURCHASED_TARIFFS_HISTORY: History[] = [
{
@ -82,7 +82,7 @@ const PURCHASED_TARIFFS_HISTORY: History[] = [
info: "9 месяцев 1 000 шаблонов",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
];
]
const FINISHED_TARIFFS_HISTORY: History[] = [
{
@ -124,6 +124,6 @@ const FINISHED_TARIFFS_HISTORY: History[] = [
expired: true,
description: "Тариф на время/ объем/ кастомный или другая информация",
},
];
]
export const HISTORY: History[][] = [PAYMENT_HISTORY, PURCHASED_TARIFFS_HISTORY, FINISHED_TARIFFS_HISTORY];
export const HISTORY: History[][] = [PAYMENT_HISTORY, PURCHASED_TARIFFS_HISTORY, FINISHED_TARIFFS_HISTORY]

@ -1,36 +1,36 @@
import { useEffect, useState } from "react";
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { useEffect, useState } from "react"
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"
import ArrowBackIcon from "@mui/icons-material/ArrowBack"
import SectionWrapper from "@root/components/SectionWrapper";
import { Select } from "@root/components/Select";
import { Tabs } from "@root/components/Tabs";
import SectionWrapper from "@root/components/SectionWrapper"
import { Select } from "@root/components/Select"
import { Tabs } from "@root/components/Tabs"
import AccordionWrapper from "./AccordionWrapper";
import { HISTORY } from "./historyMocks";
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
import { useHistoryData } from "@root/utils/hooks/useHistoryData";
import { isArray } from "cypress/types/lodash";
import { ErrorBoundary } from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError";
import AccordionWrapper from "./AccordionWrapper"
import { HISTORY } from "./historyMocks"
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker"
import { useHistoryData } from "@root/utils/hooks/useHistoryData"
import { isArray } from "cypress/types/lodash"
import { ErrorBoundary } from "react-error-boundary"
import { handleComponentError } from "@root/utils/handleComponentError"
const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"];
const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
export default function History() {
const [selectedItem, setSelectedItem] = useState<number>(0);
const [selectedItem, setSelectedItem] = useState<number>(0)
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const { historyData, error } = useHistoryData();
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const isMobile = useMediaQuery(theme.breakpoints.down(600))
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const { historyData, error } = useHistoryData()
const handleCustomBackNavigation = useHistoryTracker();
const handleCustomBackNavigation = useHistoryTracker()
const extractDateFromString = (tariffName: string) => {
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/);
return dateMatch ? dateMatch[0] : "";
};
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/)
return dateMatch ? dateMatch[0] : ""
}
return (
<SectionWrapper
@ -94,5 +94,5 @@ export default function History() {
)})}
</ErrorBoundary>
</SectionWrapper>
);
)
}

@ -1,4 +1,4 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"
interface Props {
bigText: string;
@ -7,8 +7,8 @@ interface Props {
}
export default function Infographics({ bigText, text, flex }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
return (
<Box
@ -30,5 +30,5 @@ export default function Infographics({ bigText, text, flex }: Props) {
{text}
</Typography>
</Box>
);
)
}

@ -1,17 +1,17 @@
import { Box, CssBaseline, ThemeProvider, useTheme, useMediaQuery } from "@mui/material";
import Section1 from "./Section1";
import Section2 from "./Section2";
import Section3 from "./Section3";
import Section4 from "./Section4";
import Section5 from "./Section5";
import FloatingSupportChat from "@root/components/FloatingSupportChat/FloatingSupportChat";
import Footer from "@root/components/Footer";
import Navbar from "@root/components/NavbarLanding/Navbar";
import { theme } from "@root/utils/theme";
import { Box, CssBaseline, ThemeProvider, useTheme, useMediaQuery } from "@mui/material"
import Section1 from "./Section1"
import Section2 from "./Section2"
import Section3 from "./Section3"
import Section4 from "./Section4"
import Section5 from "./Section5"
import FloatingSupportChat from "@root/components/FloatingSupportChat/FloatingSupportChat"
import Footer from "@root/components/Footer"
import Navbar from "@root/components/NavbarLanding/Navbar"
import { theme } from "@root/utils/theme"
export default function Landing() {
const muiTheme = useTheme();
const isTablet = useMediaQuery(muiTheme.breakpoints.down(900));
const muiTheme = useTheme()
const isTablet = useMediaQuery(muiTheme.breakpoints.down(900))
return (
<ThemeProvider theme={theme}>
@ -33,5 +33,5 @@ export default function Landing() {
<FloatingSupportChat />
</Box>
</ThemeProvider>
);
)
}

Some files were not shown because too many files have changed in this diff Show More