Compare commits

..

No commits in common. "madev" and "master" have entirely different histories.

76 changed files with 14 additions and 14664 deletions

@ -1,25 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs', "vite.config.ts"],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "warn",
"no-restricted-exports": ["error", {
"restrictDefaultExports": {
direct: true,
}
}]
},
};

@ -1,33 +0,0 @@
name: Deploy
run-name: ${{ gitea.actor }} build image and push to container registry
on:
push:
branches:
- 'main'
- 'staging'
- 'dev'
jobs:
Publish:
runs-on: ["skeris"]
conainer:
image: gitea.pena:3000/penadevops/container-images/node-compose:main
steps:
- name: Check out repository code
uses: http://gitea.pena:3000/PenaDevops/actions.git/checkout@v1
- name: Publish
run: |
npm config set registry=http://gitea.pena/api/packages/skeris/npm/
npm config set -- '//gitea.pena/api/packages/skeris/npm/:_authToken' "1856e802057f59193ca6fdb4068cbea44982bcc2"
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
npm version major
elif [ "${{ github.ref }}" == "refs/heads/staging" ]; then
npm version minor
else
npm version patch
fi
npm publish

24
.gitignore vendored

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
.npmrc Normal file

@ -0,0 +1,4 @@
@frontend:kitui=https://penahub.gitlab.yandexcloud.net/api/v4/packages/npm/
'//penahub.gitlab.yandexcloud.net/api/v4/packages/npm/:_authToken'="${GITLAB_AUTH_TOKEN}"
'//penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/:_authToken'="${GITLAB_AUTH_TOKEN}"

@ -1 +0,0 @@
"@frontend:registry" "http://gitea.pena/api/packages/skeris/npm/"

@ -1,4 +0,0 @@
- Бог в помощь
- Обновлены все пакеты
- Убран yarn
#v2.0.0

@ -1,19 +0,0 @@
## Перед использованием и публикацией
```bash
# заменить TOKEN на актуальный токен
npm config set //penahub.gitlab.yandexcloud.net/api/v4/packages/npm/:_authToken=TOKEN
npm config set //penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/:_authToken=TOKEN
```
## Публикация
```bash
yarn publish
```
## Использование
Добавить в корень проекта файл .yarnrc с содержимым
```
"@frontend:registry" "https://penahub.gitlab.yandexcloud.net/api/v4/packages/npm/"
```
Установка
```bash
yarn add @frontend/kitui
```

11
env.d.ts vendored

@ -1,11 +0,0 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_REACT_APP_DOMAIN: string;
readonly VITE_API_KEY: string;
// Добавьте другие переменные окружения, которые вы используете
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

@ -1,17 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

@ -1,27 +0,0 @@
import { UserAccount, UserName } from "../model/account";
import { makeRequest } from "./makeRequest";
const apiUrl = import.meta.env.VITE__APP_DOMAIN + "/customer";
export function patchUserAccount(user: UserName, version:string | undefined) {
return makeRequest<UserName, UserAccount>({
url: `${apiUrl + (version ? `/${version}` : "")}/account`,
contentType: true,
method: "PATCH",
useToken: true,
withCredentials: false,
body: user,
});
}
export function createUserAccount(signal: AbortSignal, url: string | undefined, version: string) {
return makeRequest<never, UserAccount>({
url: url || `${apiUrl + (version.length > 0 ? `/${version}` : "")}/account`,
contentType: true,
method: "POST",
useToken: true,
withCredentials: false,
signal,
});
}

@ -1,4 +0,0 @@
export * from "./account";
export * from "./makeRequest";
export * from "./tariff";
export * from "./tickets";

@ -1,76 +0,0 @@
import axios, { AxiosResponse, Method, ResponseType } from "axios";
import { getAuthToken, setAuthToken } from "../stores/auth";
export async function makeRequest<TRequest = unknown, TResponse = unknown>({
method = "post",
url,
body,
useToken = true,
contentType = false,
responseType = "json",
signal,
withCredentials,
}: {
method?: Method;
url: string;
body?: TRequest;
/** Send access token */
useToken?: boolean;
contentType?: boolean;
responseType?: ResponseType;
signal?: AbortSignal;
/** Send refresh token */
withCredentials?: boolean;
}): Promise<TResponse> {
const headers: Record<string, string> = {};
if (useToken) headers["Authorization"] = `Bearer ${getAuthToken()}`;
if (contentType) headers["Content-Type"] = "application/json";
try {
const response = await axios<TRequest, AxiosResponse<TResponse & { accessToken?: string; }>>({
url,
method,
headers,
data: body,
signal,
responseType,
withCredentials,
});
if (response.data?.accessToken) {
setAuthToken(response.data.accessToken);
}
return response.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401 && !withCredentials) {
const refreshResponse = await refresh(getAuthToken());
if (refreshResponse.data?.accessToken) setAuthToken(refreshResponse.data.accessToken);
headers["Authorization"] = refreshResponse.data.accessToken;
const response = await axios.request<TRequest, AxiosResponse<TResponse>>({
url,
method,
headers,
data: body,
signal,
});
return response.data;
}
throw error;
}
}
function refresh(token: string) {
return axios<never, AxiosResponse<{ accessToken: string; }>>(import.meta.env.VITE__APP_DOMAIN + "/auth/refresh", {
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "post"
});
}

@ -1,11 +0,0 @@
import { Tariff } from "../model/tariff";
import { makeRequest } from "./makeRequest";
export function getTariffById(tariffId:string){
return makeRequest<never, Tariff>({
url: import.meta.env.VITE__APP_DOMAIN + `/strator/tariff/${tariffId}`,
method: "get",
useToken: true,
});
}

@ -1,17 +0,0 @@
import { CreateTicketRequest, CreateTicketResponse } from "../model/ticket";
import { makeRequest } from "./makeRequest";
export function createTicket({ url, body, useToken = true }: {
url: string;
body: CreateTicketRequest;
useToken?: boolean;
}): Promise<CreateTicketResponse> {
return makeRequest({
url,
method: "POST",
useToken,
body,
withCredentials: true,
});
}

@ -1,59 +0,0 @@
import { Avatar, IconButton, IconButtonProps, Typography, useTheme } from "@mui/material";
import { deepmerge } from "@mui/utils";
import { ForwardRefExoticComponent, RefAttributes } from "react";
import { LinkProps } from "react-router-dom";
type Props = IconButtonProps & {
component?: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAnchorElement>>;
to?: string;
};
export function AvatarButton(props: Props) {
const theme = useTheme();
return (
<IconButton
{...deepmerge({
sx: {
height: 36,
width: 36,
p: 0,
"&:hover .MuiAvatar-root": {
border: `2px solid ${theme.palette.gray.main}`,
},
"&:active .MuiAvatar-root": {
backgroundColor: theme.palette.purple.main,
color: theme.palette.purple.main,
border: "1px solid black",
},
}
}, props)}
>
<Avatar sx={{
height: "100%",
width: "100%",
backgroundColor: theme.palette.orange.main,
color: theme.palette.orange.light,
transition: "all 100ms",
}}>
<Typography sx={{
fontWeight: 500,
fontSize: "14px",
lineHeight: "20px",
zIndex: 1,
textTransform: "uppercase",
position: "absolute",
color: "white",
}}>
{props.children ?? "AA"}
</Typography>
<svg width="100%" height="100%" viewBox="0 0 36 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M15.348 16.146c.1-5.981 6.823-10.034 12.62-11.479 6.055-1.508 13.264-.719 17.31 4.023 3.673 4.303.83 10.565-.085 16.16-.678 4.152-1.209 8.41-4.383 11.171-3.418 2.973-8.742 6.062-12.43 3.452-3.663-2.593 1.412-8.88-.78-12.8-2.764-4.95-12.347-4.85-12.252-10.527Z" fill="currentColor" />
<circle cx="28.052" cy="-3.333" r="5.519" transform="rotate(-32.339 28.052 -3.333)" fill="currentColor" />
<circle cx="24.363" cy="29.03" r="1.27" transform="rotate(-32.339 24.363 29.03)" fill="currentColor" />
</svg>
</Avatar>
</IconButton>
);
}

@ -1,31 +0,0 @@
import { IconButton, IconButtonProps, useTheme } from "@mui/material";
import { deepmerge } from "@mui/utils";
export function BurgerButton(props: IconButtonProps) {
const theme = useTheme();
return (
<IconButton
{...deepmerge({
sx: {
height: 30,
width: 30,
p: 0,
color: "black",
"&:hover": {
color: theme.palette.purple.main,
backgroundColor: "rgb(0 0 0 / 0)",
},
"&:active": {
color: theme.palette.purple.main,
},
},
}, props)}
>
<svg width="30" height="31" viewBox="0 0 30 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28 8.005H3M28 16.005H3M28 24.005H3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</IconButton>
);
}

@ -1,31 +0,0 @@
import { IconButton, IconButtonProps, useTheme } from "@mui/material";
import { deepmerge } from "@mui/utils";
export function CloseButton(props: IconButtonProps) {
const theme = useTheme();
return (
<IconButton
{...deepmerge({
sx: {
height: 30,
width: 30,
p: 0,
color: "black",
"&:hover": {
color: theme.palette.orange.main,
backgroundColor: "rgb(0 0 0 / 0)",
},
"&:active": {
color: theme.palette.orange.main,
},
},
}, props)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 30 31" fill="none">
<path d="m3 3.605 24 24m-24 0 24-24" stroke="currentColor" />
</svg>
</IconButton>
);
}

@ -1,31 +0,0 @@
import { IconButton, IconButtonProps, useTheme } from "@mui/material";
import { deepmerge } from "@mui/utils";
export function CloseButtonSmall(props: IconButtonProps) {
const theme = useTheme();
return (
<IconButton
{...deepmerge({
sx: {
height: 12,
width: 12,
p: 0,
color: theme.palette.purple.main,
"&:hover": {
color: theme.palette.orange.main,
backgroundColor: "rgb(0 0 0 / 0)",
},
"&:active": {
color: theme.palette.orange.main,
},
},
}, props)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 14 14" fill="none">
<path d="m13 1.176-12 12M13 13.176l-12-12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</IconButton>
);
}

@ -1,39 +0,0 @@
import { IconButton, IconButtonProps, useTheme } from "@mui/material";
import { deepmerge } from "@mui/utils";
export function LogoutButton(props: IconButtonProps) {
const theme = useTheme();
return (
<IconButton
{...deepmerge({
sx: {
height: 36,
width: 36,
p: 0,
borderRadius: "6px",
backgroundColor: theme.palette.background.default,
color: theme.palette.gray.main,
"&:hover": {
color: theme.palette.background.default,
backgroundColor: theme.palette.gray.main,
},
"&:active": {
backgroundColor: theme.palette.purple.main,
color: "white",
},
}
}, props)}
>
<svg width="100%" height="100%" viewBox="0 0 36 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M22.559 22.052v2.95a2 2 0 0 1-2 2h-6.865a2 2 0 0 1-2-2v-12.5a2 2 0 0 1 2-2h6.865a2 2 0 0 1 2 2v2.95M25.067 21.227l1.786-1.763a1 1 0 0 0 0-1.423l-1.786-1.764M26.737 18.752H16.71"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
</IconButton>
);
}

@ -1,34 +0,0 @@
import { Link, LinkProps, LinkTypeMap, Typography, useTheme } from "@mui/material";
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import { deepmerge } from "@mui/utils";
import { OverridableComponent } from "@mui/material/OverridableComponent";
export const PenaLink: OverridableComponent<LinkTypeMap<object, "a">> = (props: LinkProps) => {
const theme = useTheme();
return (
<Link
{...deepmerge({
sx: {
display: "flex",
gap: "3px",
textUnderlinePosition: "under",
color: theme.palette.purple.light,
textDecorationColor: theme.palette.purple.main,
textUnderlineOffset: "2px",
"&:hover": {
textDecorationColor: theme.palette.purple.light,
},
"&:active": {
color: "white",
textDecorationColor: "white",
},
}
}, props)}
>
<Typography variant="body2">{props.children}</Typography>
<ArrowForwardIcon sx={{ height: "20px", width: "20px" }} />
</Link>
);
};

@ -1,120 +0,0 @@
import { FormControl, InputLabel, SxProps, TextField, TextFieldProps, Theme, useMediaQuery, useTheme } from "@mui/material";
interface Props {
id?: string;
label?: string;
labelSx?: SxProps<Theme>;
bold?: boolean;
gap?: string;
backgroundColor?: string;
FormControlSx?: SxProps<Theme>;
TextFieldSx?: SxProps<Theme>;
placeholder?: TextFieldProps["placeholder"];
value?: TextFieldProps["value"];
helperText?: TextFieldProps["helperText"];
error?: TextFieldProps["error"];
type?: TextFieldProps["type"];
onBlur?: TextFieldProps["onBlur"];
onChange?: TextFieldProps["onChange"];
fullWidth?: boolean;
}
export function PenaTextField({
id = "pena-textfield",
label,
labelSx,
bold = false,
gap = "10px",
onChange,
error,
helperText,
onBlur,
placeholder,
type,
value,
backgroundColor,
FormControlSx,
TextFieldSx,
fullWidth = true,
}: Props) {
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;
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
return (
<FormControl
fullWidth={fullWidth}
variant="standard"
sx={{
gap,
...FormControlSx,
}}
>
{label && (
<InputLabel
shrink
htmlFor={id}
sx={{
position: "inherit",
color: "black",
transform: "none",
...labelFont,
...labelSx,
}}
>
{label}
</InputLabel>
)}
<TextField
fullWidth
id={id}
error={error}
helperText={helperText}
onBlur={onBlur}
placeholder={placeholder}
type={type}
value={value}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "8px",
"& fieldset": {
border: `1px solid ${theme.palette.gray.main}`,
},
"&:hover fieldset": {
border: `1px solid ${theme.palette.gray.dark}`,
},
"&.Mui-focused fieldset": {
border: `2px solid ${theme.palette.purple.main}`,
},
},
"& .MuiFormHelperText-root.MuiFormHelperText-contained.MuiFormHelperText-filled.Mui-error": {
position: "absolute",
top: "45px",
},
...TextFieldSx,
}}
inputProps={{
sx: {
boxSizing: "border-box",
backgroundColor: backgroundColor ?? theme.palette.background.default,
borderRadius: "8px",
height: "48px",
py: 0,
color: theme.palette.gray.dark,
...placeholderFont,
},
}}
onChange={onChange}
/>
</FormControl>
);
}

@ -1,41 +0,0 @@
import { IconButton, IconButtonProps, useTheme } from "@mui/material";
import { ForwardRefExoticComponent, RefAttributes } from "react";
import { LinkProps } from "react-router-dom";
import { deepmerge } from "@mui/utils";
type Props = IconButtonProps & {
component?: ForwardRefExoticComponent<LinkProps & RefAttributes<HTMLAnchorElement>>;
to?: string;
};
export function WalletButton(props: Props) {
const theme = useTheme();
return (
<IconButton
{...deepmerge({
sx: {
height: 36,
width: 36,
p: 0,
borderRadius: "6px",
backgroundColor: theme.palette.background.default,
color: theme.palette.gray.main,
"&:hover": {
color: theme.palette.background.default,
backgroundColor: theme.palette.gray.main,
},
"&:active": {
backgroundColor: theme.palette.purple.main,
color: "white",
},
},
}, props)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 36 37" fill="none">
<path d="M26.571 16.051v-5c0-.789-.64-1.428-1.428-1.428H9.429c-.79 0-1.429.64-1.429 1.428v14.286c0 .789.64 1.428 1.429 1.428h15.714c.789 0 1.428-.64 1.428-1.428v-5m1.33-5h-7.044a2.857 2.857 0 0 0 0 5.714h7.044a.099.099 0 0 0 .099-.099v-5.516a.1.1 0 0 0-.099-.1Z" stroke="currentColor" strokeWidth="1.5" />
</svg>
</IconButton>
);
}

@ -1,9 +0,0 @@
export * from "./AvatarButton";
export * from "./BurgerButton";
export * from "./CloseButton";
export * from "./CloseButtonSmall";
export * from "./LogoutButton";
export * from "./PenaLink";
export * from "./PenaTextField";
export * from "./theme";
export * from "./WalletButton";

@ -1,612 +0,0 @@
import { createTheme } from "@mui/material";
export const penaMuiTheme = createTheme({
breakpoints: {
values: {
xs: 300,
sm: 560,
md: 900,
lg: 1200,
xl: 1536,
},
},
palette: {
mode: "light",
primary: {
main: "#000000",
},
secondary: {
main: "#252734",
},
text: {
primary: "#000000",
secondary: "#7E2AEA",
},
background: {
default: "#F2F3F7",
},
purple: {
main: "#7E2AEA",
dark: "#581CA7",
light: "#944FEE",
},
bg: {
main: "#333647",
dark: "#252734",
},
gray: {
main: "#9A9AAF",
dark: "#4D4D4D",
},
orange: {
main: "#FB5607",
light: "#FC712F",
},
},
components: {
MuiButton: {
variants: [
{
props: { variant: "pena-contained-dark" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: "white",
backgroundColor: theme.palette.purple.main,
border: `1px solid ${theme.palette.purple.main}`,
"&:hover": {
color: "white",
backgroundColor: theme.palette.purple.light,
border: `1px solid ${theme.palette.purple.light}`,
},
"&:active": {
color: theme.palette.purple.main,
backgroundColor: "white",
border: `1px solid ${theme.palette.purple.main}`,
}
}),
},
{
props: { variant: "pena-outlined-dark" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: "white",
backgroundColor: "rgb(0 0 0 / 0)",
border: `1px solid white`,
"&:hover": {
color: "white",
backgroundColor: theme.palette.bg.dark,
border: `1px solid white`,
},
"&:active": {
color: "white",
backgroundColor: theme.palette.purple.main,
border: `1px solid ${theme.palette.purple.main}`,
}
}),
},
{
props: { variant: "pena-contained-light" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: theme.palette.bg.dark,
backgroundColor: "white",
border: `1px solid white`,
"&:hover": {
color: theme.palette.bg.dark,
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.background.default}`,
},
"&:active": {
color: "white",
backgroundColor: "black",
border: `1px solid black`,
}
}),
},
{
props: { variant: "pena-outlined-light" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: "white",
backgroundColor: "rgb(0 0 0 / 0)",
border: `1px solid white`,
"&:hover": {
color: "white",
backgroundColor: "#581CA7",
border: `1px solid white`,
},
"&:active": {
color: "white",
backgroundColor: "black",
border: `1px solid black`,
}
}),
},
{
props: { variant: "pena-outlined-purple" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: theme.palette.purple.main,
backgroundColor: "rgb(0 0 0 / 0)",
border: `1px solid ${theme.palette.purple.main}`,
"&:hover": {
color: theme.palette.purple.main,
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.purple.main}`,
},
"&:active": {
color: "white",
backgroundColor: theme.palette.purple.dark,
border: `1px solid ${theme.palette.purple.dark}`,
}
}),
},
{
props: { variant: "pena-navitem-dark" },
style: ({ theme }) => theme.unstable_sx({
p: 0,
boxShadow: "none",
fontSize: "16px",
lineHeight: "20px",
fontWeight: 500,
textTransform: "none",
whiteSpace: "nowrap",
color: "white",
backgroundColor: "rgb(0 0 0 / 0)",
"&:hover": {
color: theme.palette.purple.light,
backgroundColor: "rgb(0 0 0 / 0)",
},
"&:active": {
color: theme.palette.purple.light,
backgroundColor: "rgb(0 0 0 / 0)",
}
}),
},
{
props: { variant: "pena-navitem-light" },
style: ({ theme }) => theme.unstable_sx({
p: 0,
boxShadow: "none",
fontSize: "16px",
lineHeight: "20px",
fontWeight: 500,
textTransform: "none",
whiteSpace: "nowrap",
color: "black",
backgroundColor: "rgb(0 0 0 / 0)",
"&:hover": {
color: theme.palette.purple.main,
backgroundColor: "rgb(0 0 0 / 0)",
},
"&:active": {
color: theme.palette.purple.main,
backgroundColor: "rgb(0 0 0 / 0)",
}
}),
},
{
props: { variant: "pena-contained-white1" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: theme.palette.purple.main,
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.gray.main}`,
"&:hover": {
color: "white",
backgroundColor: theme.palette.purple.main,
border: `1px solid ${theme.palette.purple.main}`,
},
"&:active": {
color: "white",
backgroundColor: "black",
border: `1px solid black`,
}
}),
},
{
props: { variant: "pena-contained-white2" },
style: ({ theme }) => theme.unstable_sx({
minWidth: "180px",
py: "9px",
px: "43px",
borderRadius: "8px",
boxShadow: "none",
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
color: "black",
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.background.default}`,
"&:hover": {
color: "white",
backgroundColor: theme.palette.purple.light,
border: `1px solid white`,
},
"&:active": {
color: theme.palette.purple.main,
backgroundColor: "white",
border: `1px solid ${theme.palette.purple.light}`,
}
}),
},
{
props: {
variant: "pena-text",
},
style: ({ theme }) => ({
color: theme.palette.purple.main,
padding: 0,
textTransform: "none",
textDecoration: "underline",
textUnderlineOffset: "7px",
fontSize: "16px",
fontWeight: 500,
lineHeight: "20px",
}),
},
],
defaultProps: {
disableTouchRipple: true,
},
},
MuiIconButton: {
defaultProps: {
disableTouchRipple: true,
},
},
MuiTypography: {
defaultProps: {
variantMapping: {
p1: "p",
t1: "p",
"pena-h1": "h1",
"pena-card-header1": "h5",
}
},
},
MuiAlert: {
styleOverrides: {
filledError: {
backgroundColor: "#FB5607",
},
root: {
borderRadius: "8px",
}
}
},
MuiPagination: {
variants: [
{
props: { variant: "pena-pagination" },
style: {
marginRight: "-15px",
marginLeft: "-15px",
"& .MuiPaginationItem-root": {
height: "30px",
width: "30px",
minWidth: "30px",
marginLeft: "5px",
marginRight: "5px",
backgroundColor: "white",
color: "black",
fontSize: "16px",
lineHeight: "20px",
fontWeight: 400,
borderRadius: "5px",
"&.Mui-selected": {
backgroundColor: "white",
color: "#7E2AEA",
fontWeight: 500,
},
"&:hover": {
backgroundColor: "#ffffff55",
},
"&:active": {
backgroundColor: "#7F2CEA",
color: "white",
},
boxShadow: `
0px 77.2727px 238.773px rgba(210, 208, 225, 0.24),
0px 32.2827px 99.7535px rgba(210, 208, 225, 0.172525),
0px 17.2599px 53.333px rgba(210, 208, 225, 0.143066),
0px 9.67574px 29.8981px rgba(210, 208, 225, 0.12),
0px 5.13872px 15.8786px rgba(210, 208, 225, 0.0969343),
0px 2.13833px 6.60745px rgba(210, 208, 225, 0.0674749)
`,
},
"& .MuiPaginationItem-previousNext": {
backgroundColor: "#7E2AEA",
color: "white",
marginLeft: "15px",
marginRight: "15px",
"&:hover": {
backgroundColor: "#995DED",
},
},
}
}
],
},
MuiSwitch: {
styleOverrides: {
root: {
color: "#7E2AEA",
height: "50px",
width: "69px",
"& .MuiSwitch-switchBase.Mui-checked+.MuiSwitch-track": {
backgroundColor: "#7E2AEA",
opacity: 1,
},
},
track: {
height: "12px",
alignSelf: "center",
backgroundColor: "#00000000",
opacity: 1,
border: "1px solid #9A9AAF",
},
thumb: {
height: "32px",
width: "32px",
border: `6px solid #7E2AEA`,
backgroundColor: "white",
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD
`,
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD
`,
},
},
},
},
},
typography: palette => ({
h5: {
fontSize: "24px",
lineHeight: "28.44px",
fontWeight: 500,
},
button: {
fontSize: "18px",
lineHeight: "24px",
fontWeight: 400,
textTransform: "none",
},
body1: {
fontSize: "18px",
lineHeight: "21.33px",
fontWeight: 400,
},
body2: {
fontSize: "16px",
lineHeight: "20px",
fontWeight: 500,
},
p1: {
fontSize: "20px",
lineHeight: "24px",
fontWeight: 500,
},
price: {
fontWeight: 500,
fontSize: "20px",
lineHeight: "24px",
color: palette.gray.dark,
},
oldPrice: {
fontWeight: 400,
fontSize: "18px",
lineHeight: "21px",
textDecorationLine: "line-through",
color: palette.orange.main,
},
t1: {
display: "block",
fontWeight: 400,
fontSize: "18px",
lineHeight: "21.33px",
},
fontFamily: [
"Rubik",
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
'"Helvetica Neue"',
"Arial",
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(","),
}),
});
penaMuiTheme.typography["pena-h1"] = {
fontSize: "70px",
fontWeight: 500,
lineHeight: "100%",
[penaMuiTheme.breakpoints.down("md")]: {
fontSize: "36px",
lineHeight: "100%",
},
};
penaMuiTheme.typography["pena-h3"] = {
color: "#000000",
fontWeight: 500,
fontSize: "36px",
lineHeight: "100%",
[penaMuiTheme.breakpoints.down("md")]: {
fontSize: "30px",
lineHeight: "100%",
},
};
penaMuiTheme.typography["pena-card-header1"] = {
fontWeight: 500,
fontSize: "24px",
lineHeight: "100%",
[penaMuiTheme.breakpoints.down("md")]: {
fontSize: "21px",
lineHeight: "100%",
},
};
penaMuiTheme.typography.h2 = {
fontSize: "70px",
lineHeight: "70px",
fontWeight: 500,
[penaMuiTheme.breakpoints.down("md")]: {
fontSize: "42px",
lineHeight: "50px",
}
};
penaMuiTheme.typography.h4 = {
fontSize: "36px",
lineHeight: "42.66px",
fontWeight: 500,
[penaMuiTheme.breakpoints.down("md")]: {
fontSize: "24px",
lineHeight: "28.44px",
}
};
penaMuiTheme.typography.infographic = {
fontSize: "80px",
lineHeight: "94.8px",
fontWeight: 400,
[penaMuiTheme.breakpoints.down("md")]: {
fontSize: "50px",
lineHeight: "59px",
fontWeight: 400,
}
};
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
"pena-contained-light": true;
"pena-outlined-light": true;
"pena-contained-dark": true;
"pena-outlined-dark": true;
"pena-outlined-purple": true;
"pena-navitem-light": true;
"pena-navitem-dark": true;
"pena-contained-white1": true;
"pena-contained-white2": true;
"pena-text": true;
}
}
declare module '@mui/material/Pagination' {
interface PaginationPropsVariantOverrides {
"pena-pagination": true;
}
}
declare module "@mui/material/styles" {
interface Palette {
purple: Palette["primary"],
bg: Palette["primary"],
gray: Palette["primary"],
orange: Palette["primary"],
}
interface PaletteOptions {
purple?: PaletteOptions["primary"],
bg?: PaletteOptions["primary"],
gray?: PaletteOptions["primary"],
orange?: PaletteOptions["primary"],
}
interface TypographyVariants {
infographic: React.CSSProperties;
p1: React.CSSProperties;
price: React.CSSProperties;
oldPrice: React.CSSProperties;
t1: React.CSSProperties;
"pena-card-header1": React.CSSProperties;
"pena-h1": React.CSSProperties;
"pena-h3": React.CSSProperties;
}
interface TypographyVariantsOptions {
infographic?: React.CSSProperties;
p1?: React.CSSProperties;
price?: React.CSSProperties;
oldPrice?: React.CSSProperties;
t1?: React.CSSProperties;
"pena-card-header1"?: React.CSSProperties;
"pena-h1"?: React.CSSProperties;
"pena-h3"?: React.CSSProperties;
}
}
declare module "@mui/material/Typography" {
interface TypographyPropsVariantOverrides {
infographic: true;
p1: true;
price: true;
oldPrice: true;
t1: true;
"pena-card-header1": true;
"pena-h1": true;
"pena-h3": true;
}
}
declare module "@mui/material/Switch" {
interface SwitchPropsVariantOverrides {
"pena-switch": true;
}
}

@ -1 +0,0 @@
export * from "./throttle";

@ -1,29 +0,0 @@
export type ThrottledFunction<T extends (...args: any) => any> = (...args: Parameters<T>) => void;
export function throttle<T extends (...args: any) => any>(func: T, ms: number): ThrottledFunction<T> {
let isThrottled = false;
let savedArgs: Parameters<T> | null;
let savedThis: any;
function wrapper(this: any, ...args: Parameters<T>) {
if (isThrottled) {
savedArgs = args;
savedThis = this;
return;
}
func.apply(this, args);
isThrottled = true;
setTimeout(function () {
isThrottled = false;
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}

7
lib/env.d.ts vendored

@ -1,7 +0,0 @@
export declare global {
namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: 'development' | 'production' | 'test';
}
}
}

@ -1,12 +0,0 @@
export * from "./useAllTariffsFetcher";
export * from "./useDebounce";
export * from "./useEventListener";
export * from "./usePrivilegeFetcher";
export * from "./useSSESubscription";
export * from "./usePaginatedTariffsFetcher";
export * from "./useThrottle";
export * from "./useTicketMessages";
export * from "./useTickets";
export * from "./useToken";
export * from "./useUserAccountFetcher";
export * from "./useUserFetcher";

@ -1,60 +0,0 @@
import { useRef, useLayoutEffect, useEffect } from "react";
import { GetTariffsResponse, Tariff } from "../model/tariff";
import { makeRequest } from "../api/makeRequest";
export function useAllTariffsFetcher({
enabled = true,
baseUrl = import.meta.env.VITE__APP_DOMAIN + "/strator/tariff",
onSuccess,
onError,
}: {
enabled?: boolean;
baseUrl?: string;
onSuccess: (response: Tariff[]) => void;
onError?: (error: Error) => void;
}) {
const onNewTariffsRef = useRef(onSuccess);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onNewTariffsRef.current = onSuccess;
onErrorRef.current = onError;
}, [onError, onSuccess]);
useEffect(() => {
if (!enabled) return;
const controller = new AbortController();
async function getPaginatedTariffs() {
let apiPage = 1;
const tariffsPerPage = 100;
let isDone = false;
while (!isDone) {
try {
const result = await makeRequest<never, GetTariffsResponse>({
url: baseUrl + `?page=${apiPage}&limit=${tariffsPerPage}`,
method: "get",
useToken: true,
signal: controller.signal,
});
if (result) onNewTariffsRef.current(result.tariffs);
if (result.totalPages < apiPage) {
apiPage++;
} else {
isDone = true;
}
} catch (error) {
onErrorRef.current?.(error as Error);
isDone = true;
}
}
}
getPaginatedTariffs();
return () => controller.abort();
}, [baseUrl, enabled]);
}

@ -1,15 +0,0 @@
import { useState, useEffect } from "react";
export function useDebounce<T>(value: T, delay: number) {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}

@ -1,80 +0,0 @@
import { useEffect, useRef, RefObject, useLayoutEffect } from "react";
// https://usehooks-ts.com/react-hook/use-event-listener
// MediaQueryList Event based useEventListener interface
export function useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K,
handler: (event: MediaQueryListEventMap[K]) => void,
element: RefObject<MediaQueryList>,
options?: boolean | AddEventListenerOptions,
): void;
// Window Event based useEventListener interface
export function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (event: WindowEventMap[K]) => void,
element?: undefined,
options?: boolean | AddEventListenerOptions,
): void;
// Element Event based useEventListener interface
export function useEventListener<
K extends keyof HTMLElementEventMap,
T extends HTMLElement = HTMLDivElement,
>(
eventName: K,
handler: (event: HTMLElementEventMap[K]) => void,
element: RefObject<T>,
options?: boolean | AddEventListenerOptions,
): void;
// Document Event based useEventListener interface
export function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (event: DocumentEventMap[K]) => void,
element: RefObject<Document>,
options?: boolean | AddEventListenerOptions,
): void;
export function useEventListener<
KW extends keyof WindowEventMap,
KH extends keyof HTMLElementEventMap,
KM extends keyof MediaQueryListEventMap,
T extends HTMLElement | MediaQueryList | void = void,
>(
eventName: KW | KH | KM,
handler: (
event:
| WindowEventMap[KW]
| HTMLElementEventMap[KH]
| MediaQueryListEventMap[KM]
| Event,
) => void,
element?: RefObject<T>,
options?: boolean | AddEventListenerOptions,
) {
// Create a ref that stores handler
const savedHandler = useRef(handler);
useLayoutEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
// Define the listening target
const targetElement: T | Window = element?.current ?? window;
if (!(targetElement && targetElement.addEventListener)) return;
// Create event listener that calls handler export function stored in ref
const listener: typeof handler = event => savedHandler.current(event);
targetElement.addEventListener(eventName, listener, options);
// Remove event listener on cleanup
return () => {
targetElement.removeEventListener(eventName, listener, options);
};
}, [eventName, element, options]);
}

@ -1,51 +0,0 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import { makeRequest } from "../api";
import { Tariff, GetTariffsResponse } from "../model/tariff";
import { devlog } from "../utils";
import { FetchState } from "../model/fetchState";
export function usePaginatedTariffsFetcher({ enabled = true, url, tariffsPerPage, apiPage, onSuccess, onError, onFetchStateChange }: {
enabled?: boolean;
url: string;
tariffsPerPage: number;
apiPage: number;
onSuccess: (response: Tariff[]) => void;
onError?: (error: Error) => void;
onFetchStateChange?: (state: FetchState) => void;
}) {
const onNewTariffsRef = useRef(onSuccess);
const onErrorRef = useRef(onError);
const onFetchStateChangeRef = useRef(onFetchStateChange);
useLayoutEffect(() => {
onNewTariffsRef.current = onSuccess;
onErrorRef.current = onError;
onFetchStateChangeRef.current = onFetchStateChange;
}, [onSuccess, onError, onFetchStateChange]);
useEffect(function fetchTickets() {
if (!enabled) return;
const controller = new AbortController();
onFetchStateChangeRef.current?.("fetching");
makeRequest<never, GetTariffsResponse>({
url,
method: "get",
useToken: true,
signal: controller.signal,
}).then((result) => {
devlog("GetTariffsResponse", result);
if (result.tariffs.length > 0) {
onNewTariffsRef.current(result.tariffs);
onFetchStateChangeRef.current?.("idle");
} else onFetchStateChangeRef.current?.("all fetched");
}).catch(error => {
devlog("Error fetching tariffs", error);
onErrorRef.current?.(error);
});
return () => controller.abort();
}, [apiPage, enabled, tariffsPerPage, url]);
}

@ -1,39 +0,0 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import { makeRequest } from "../api";
import { Privilege } from "../model";
export function usePrivilegeFetcher({
onSuccess,
url = import.meta.env.VITE__APP_DOMAIN + "/strator/privilege",
onError,
}: {
onSuccess: (response: Privilege[]) => void;
url?: string;
onError?: (error: Error) => void;
}) {
const onSuccessRef = useRef(onSuccess);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onSuccessRef.current = onSuccess;
onErrorRef.current = onError;
}, [onSuccess, onError]);
useEffect(function fetchTickets() {
const controller = new AbortController();
makeRequest<never, Privilege[]>({
url,
method: "get",
useToken: true,
signal: controller.signal,
}).then((result) => {
onSuccessRef.current(result);
}).catch(error => {
onErrorRef.current?.(error);
});
return () => controller.abort();
}, [url]);
}

@ -1,49 +0,0 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import ReconnectingEventSource from "reconnecting-eventsource";
import { devlog } from "../utils";
export function useSSESubscription<T>({ enabled = true, url, onNewData, onDisconnect, marker = "", consolelog = false }: {
enabled?: boolean;
url: string;
onNewData: (data: T[]) => void;
onDisconnect?: () => void;
marker?: string;
consolelog?: boolean;
}) {
const onNewDataRef = useRef(onNewData);
const onDisconnectRef = useRef(onDisconnect);
useLayoutEffect(() => {
onNewDataRef.current = onNewData;
onDisconnectRef.current = onDisconnect;
}, [onNewData, onDisconnect]);
useEffect(() => {
if (!enabled) return;
const eventSource = new ReconnectingEventSource(url);
eventSource.addEventListener("open", () => devlog(`EventSource connected with ${url}`));
eventSource.addEventListener("close", () => devlog(`EventSource closed with ${url}`));
eventSource.addEventListener("message", event => {
try {
if (consolelog) console.log(event);
const newData = JSON.parse(event.data) as T;
if (newData) console.log(event.data);
devlog(`new SSE: ${marker}`, newData);
onNewDataRef.current([newData]);
} catch (error) {
devlog(`SSE parsing error: ${marker}`, event.data, error);
}
});
eventSource.addEventListener("error", event => {
devlog("SSE Error:", event);
});
return () => {
eventSource.close();
onDisconnectRef.current?.();
};
}, [enabled, marker, url]);
}

@ -1,22 +0,0 @@
import { useState, useEffect, useRef } from "react";
export function useThrottle<T>(value: T, delay: number) {
const [throttledValue, setThrottledValue] = useState<T>(value);
const time = useRef<number>(0);
useEffect(() => {
const now = Date.now();
if (now > time.current + delay) {
time.current = now;
setThrottledValue(value);
} else {
const handler = setTimeout(() => {
setThrottledValue(value);
}, delay);
return () => clearTimeout(handler);
}
}, [value, delay]);
return throttledValue;
}

@ -1,58 +0,0 @@
import { useEffect, useRef, useLayoutEffect } from "react";
import { TicketMessage, GetMessagesRequest, GetMessagesResponse } from "../model";
import { devlog } from "../utils";
import { makeRequest } from "../api";
import { FetchState } from "../model/fetchState";
export function useTicketMessages({ url, messageApiPage, messagesPerPage, ticketId, isUnauth = false, onSuccess, onError, onFetchStateChange }: {
url: string;
ticketId: string | undefined;
messagesPerPage: number;
messageApiPage: number;
isUnauth?: boolean;
onSuccess: (messages: TicketMessage[]) => void;
onError?: (error: Error) => void;
onFetchStateChange?: (state: FetchState) => void;
}) {
const onNewMessagesRef = useRef(onSuccess);
const onErrorRef = useRef(onError);
const onFetchStateChangeRef = useRef(onFetchStateChange);
useLayoutEffect(() => {
onNewMessagesRef.current = onSuccess;
onErrorRef.current = onError;
onFetchStateChangeRef.current = onFetchStateChange;
}, [onSuccess, onError, onFetchStateChange]);
useEffect(function fetchTicketMessages() {
if (!ticketId) return;
const controller = new AbortController();
onFetchStateChangeRef.current?.("fetching");
makeRequest<GetMessagesRequest, GetMessagesResponse>({
url,
method: "POST",
useToken: !isUnauth,
body: {
amt: messagesPerPage,
page: messageApiPage,
ticket: ticketId,
},
signal: controller.signal,
withCredentials: isUnauth,
}).then(result => {
devlog("GetMessagesResponse", result);
if (result?.length > 0) {
onNewMessagesRef.current(result);
onFetchStateChangeRef.current?.("idle");
} else onFetchStateChangeRef.current?.("all fetched");
}).catch(error => {
devlog("Error fetching messages", error);
onErrorRef.current?.(error);
});
return () => controller.abort();
}, [isUnauth, messageApiPage, messagesPerPage, ticketId, url]);
}

@ -1,56 +0,0 @@
import { useEffect, useRef, useLayoutEffect } from "react";
import { GetTicketsResponse, GetTicketsRequest } from "../model";
import { devlog } from "../utils";
import { makeRequest } from "../api";
import { FetchState } from "../model/fetchState";
export function useTicketsFetcher({ enabled = true, url, ticketsPerPage, ticketApiPage, onSuccess, onError, onFetchStateChange }: {
enabled?: boolean;
url: string;
ticketsPerPage: number;
ticketApiPage: number;
onSuccess: (response: GetTicketsResponse) => void;
onError?: (error: Error) => void;
onFetchStateChange?: (state: FetchState) => void;
}) {
const onNewTicketsRef = useRef(onSuccess);
const onErrorRef = useRef(onError);
const onFetchStateChangeRef = useRef(onFetchStateChange);
useLayoutEffect(() => {
onNewTicketsRef.current = onSuccess;
onErrorRef.current = onError;
onFetchStateChangeRef.current = onFetchStateChange;
}, [onSuccess, onError, onFetchStateChange]);
useEffect(function fetchTickets() {
if (!enabled) return;
const controller = new AbortController();
onFetchStateChangeRef.current?.("fetching");
makeRequest<GetTicketsRequest, GetTicketsResponse>({
url,
method: "POST",
useToken: true,
body: {
amt: ticketsPerPage,
page: ticketApiPage,
status: "open",
},
signal: controller.signal,
}).then((result) => {
devlog("GetTicketsResponse", result);
if (result.data) {
onNewTicketsRef.current(result);
onFetchStateChangeRef.current?.("idle");
} else onFetchStateChangeRef.current?.("all fetched");
}).catch(error => {
devlog("Error fetching tickets", error);
onErrorRef.current?.(error);
});
return () => controller.abort();
}, [enabled, ticketApiPage, ticketsPerPage, url]);
}

@ -1,6 +0,0 @@
import { useAuthStore } from "../stores/auth";
export function useToken() {
return useAuthStore(state => state.token);
}

@ -1,55 +0,0 @@
import { isAxiosError } from "axios";
import { useEffect, useLayoutEffect, useRef } from "react";
import { UserAccount } from "../model/account";
import { makeRequest } from "../api/makeRequest";
import { devlog } from "../utils/devlog";
import { createUserAccount } from "../api/account";
export function useUserAccountFetcher<T = UserAccount>({ onError, onNewUserAccount, versionOfCustomer="v1.0.0", url, userId }: {
url: string;
userId: string | null;
versionOfCustomer?: string;
onNewUserAccount: (response: T) => void;
onError?: (error: any) => void;
}) {
const onNewUserAccountRef = useRef(onNewUserAccount);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onNewUserAccountRef.current = onNewUserAccount;
onErrorRef.current = onError;
}, [onError, onNewUserAccount]);
useEffect(() => {
if (!userId) return;
const controller = new AbortController();
makeRequest<never, T>({
url,
contentType: true,
method: "GET",
useToken: true,
withCredentials: false,
signal: controller.signal,
}).then(result => {
devlog("User account", result);
onNewUserAccountRef.current(result);
}).catch(error => {
devlog("Error fetching user account", error);
if (isAxiosError(error) && error.response?.status === 404) {
createUserAccount(controller.signal, url, versionOfCustomer).then(result => {
devlog("Created user account", result);
onNewUserAccountRef.current(result as T);
}).catch(error => {
devlog("Error creating user account", error);
onErrorRef.current?.(error);
});
} else {
onErrorRef.current?.(error);
}
});
return () => controller.abort();
}, [url, userId]);
}

@ -1,43 +0,0 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import { User } from "../model/user";
import { devlog } from "../utils/devlog";
import { makeRequest } from "../api/makeRequest";
export function useUserFetcher({ onError, onNewUser, url, userId }: {
url: string;
userId: string | null;
onNewUser: (response: User) => void;
onError?: (error: any) => void;
}) {
const onNewUserRef = useRef(onNewUser);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onNewUserRef.current = onNewUser;
onErrorRef.current = onError;
}, [onError, onNewUser]);
useEffect(() => {
if (!userId) return;
const controller = new AbortController();
makeRequest<never, User>({
url,
contentType: true,
method: "GET",
useToken: true,
withCredentials: false,
signal: controller.signal,
}).then(result => {
devlog("User", result);
onNewUserRef.current(result);
}).catch(error => {
devlog("Error fetching user", error);
onErrorRef.current?.(error);
});
return () => controller.abort();
}, [url, userId]);
}

@ -1,8 +0,0 @@
export * from "./api";
export * from "./components";
export * from "./decorators";
export * from "./hooks";
export * from "./model";
export * from "./stores";
export * from "./utils";
export type * from "./model";

@ -1,25 +0,0 @@
export interface UserAccount {
_id: string;
userId: string;
name: UserName;
cart: string[];
wallet: {
currency: string;
cash: number;
purchasesAmount: number;
spent: number;
money: number;
};
status: "no" | "nko" | "org";
isDeleted: false;
createdAt: string;
updatedAt: string;
deletedAt: string;
}
export interface UserName {
firstname?: string;
secondname?: string;
middlename?: string;
orgname?: string;
}

@ -1,21 +0,0 @@
export interface RegisterRequest {
login: string;
password: string;
phoneNumber: string;
}
export interface RegisterResponse {
accessToken: string;
login: string;
email: string;
phoneNumber: string;
refreshToken: string;
_id: string;
}
export interface LoginRequest {
login: string;
password: string;
}
export type LoginResponse = RegisterResponse;

@ -1,32 +0,0 @@
import { Discount } from "./discount";
export type PrivilegeCartData = {
serviceKey: string;
privilegeId: string;
description: string;
price: number;
amount: number;
appliedDiscounts: Set<Discount>;
};
export type TariffCartData = {
name: string;
id: string;
price: number;
isCustom: boolean;
privileges: PrivilegeCartData[];
};
export type ServiceCartData = {
serviceKey: string;
tariffs: TariffCartData[];
price: number;
};
export type CartData = {
services: ServiceCartData[];
priceBeforeDiscounts: number;
priceAfterDiscounts: number;
allAppliedDiscounts: Set<Discount>;
};

@ -1,12 +0,0 @@
import { CustomPrivilegeWithAmount } from "./privilege";
type ServiceKey = string;
export type PrivilegeWithoutPrice = Omit<CustomPrivilegeWithAmount, "price">;
export type CustomTariffUserValues = Record<string, number>;
export type CustomTariffUserValuesMap = Record<ServiceKey, CustomTariffUserValues>;
export type ServiceKeyToPriceMap = Record<ServiceKey, number>;

@ -1,40 +0,0 @@
export interface Discount {
ID: string;
Name: string;
Layer: number;
Description: string;
Condition: {
Period?: {
From: string;
To: string;
};
User: string;
UserType?: string;
Coupon: string;
PurchasesAmount?: string;
CartPurchasesAmount?: string;
Product?: string;
Term?: string;
Usage?: string;
PriceFrom?: string;
Group?: string;
};
Target: {
Products: {
ID: string;
Factor: number;
Overhelm: boolean;
}[];
Factor: number;
TargetScope: string;
TargetGroup: string;
Overhelm: boolean;
};
Audit: {
UpdatedAt: string;
CreatedAt: string;
DeletedAt?: string;
Deleted: boolean;
};
Deprecated: boolean;
}

@ -1 +0,0 @@
export type FetchState = "fetching" | "idle" | "all fetched";

@ -1,10 +0,0 @@
export type * from "./account";
export type * from "./auth";
export type * from "./cart";
export type * from "./customTariffs";
export type * from "./discount";
export type * from "./fetchState";
export type * from "./privilege";
export type * from "./tariff";
export type * from "./ticket";
export type * from "./user";

@ -1,30 +0,0 @@
export interface Privilege {
name: string;
privilegeId: string;
serviceKey: string;
description: string;
type: "day" | "count";
value: PrivilegeValueType;
price: number;
amount: number;
}
export interface CustomPrivilege {
_id: string;
name: string;
privilegeId: string;
serviceKey: string;
description: string;
type: "day" | "count";
value: PrivilegeValueType;
price: number;
updatedAt?: string;
isDeleted?: boolean;
createdAt?: string;
}
export type PrivilegeMap = Record<string, CustomPrivilege[]>;
export type PrivilegeValueType = "шаблон" | "день" | "МБ" | "заявка";
export type CustomPrivilegeWithAmount = CustomPrivilege & { amount: number; };

@ -1,21 +0,0 @@
import { Privilege } from "./privilege";
export interface GetTariffsResponse {
totalPages: number;
tariffs: Tariff[];
}
export interface Tariff {
_id: string;
name: string;
description?: string;
order?: number;
price?: number;
isCustom: boolean;
privileges: Privilege[];
isDeleted: boolean;
createdAt?: string;
updatedAt?: string;
deletedAt?: string;
}

@ -1,69 +0,0 @@
export interface CreateTicketRequest {
Title: string;
Message: string;
System?: boolean;
}
export interface CreateTicketResponse {
Ticket: string;
sess: string;
}
export interface SendTicketMessageRequest {
message: string;
ticket: string;
lang: string;
files: string[];
}
export type TicketStatus = "open";
export interface GetTicketsRequest {
amt: number;
/** Пагинация начинается с индекса 0 */
page: number;
srch?: string;
status?: TicketStatus;
}
export interface GetTicketsResponse {
count: number;
data: Ticket[] | null;
}
export interface Ticket {
id: string;
user: string;
sess: string;
ans: string;
state: string;
top_message: TicketMessage;
title: string;
created_at: string;
updated_at: string;
rate: number;
origin: string;
}
export interface TicketMessage {
id: string;
ticket_id: string;
user_id: string,
session_id: string;
message: string;
files: string[],
shown: { [key: string]: number; },
request_screenshot: string,
created_at: string;
}
export interface GetMessagesRequest {
amt: number;
page: number;
srch?: string;
ticket: string;
}
export type GetMessagesResponse = TicketMessage[];

@ -1,10 +0,0 @@
export interface User {
_id: string;
login: string;
email: string;
phoneNumber: string;
isDeleted: boolean;
createdAt: string;
updatedAt: string;
deletedAt?: string;
}

@ -1,24 +0,0 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface AuthStore {
token: string;
}
export const useAuthStore = create<AuthStore>()(
persist(
(set, get) => ({
token: "",
}),
{
name: "token",
}
)
);
export const getAuthToken = () => useAuthStore.getState().token;
export const setAuthToken = (token: string) => useAuthStore.setState({ token });
export const clearAuthToken = () => useAuthStore.setState({ token: "" });

@ -1 +0,0 @@
export * from "./auth";

@ -1,30 +0,0 @@
import { isAxiosError } from "axios";
const translateMessage: Record<string, string> = {
"user not found": "Пользователь не найден",
"invalid password": "Неправильный пароль",
"field <password> is empty": "Поле \"Пароль\" не заполнено",
"field <login> is empty": "Поле \"Логин\" не заполнено",
"field <email> is empty": "Поле \"E-mail\" не заполнено",
"field <phoneNumber> is empty": "Поле \"Номер телефона\" не заполнено",
"user with this email or login is exist": "Пользователь уже существует",
};
export function getMessageFromFetchError(error: any, defaultMessage = "Что-то пошло не так. Повторите попытку позже"): string | null {
const rawMessage = error.response?.data?.message;
const translatedMessage = translateMessage[rawMessage];
if (translatedMessage) return translatedMessage;
if (isAxiosError(error)) {
switch (error.code) {
case "ERR_NETWORK": return "Ошибка сети";
case "ERR_CANCELED": return null;
}
}
if (process.env.NODE_ENV === "development") return rawMessage ?? error.message ?? defaultMessage;
return defaultMessage;
}

@ -1,21 +0,0 @@
import { expect, test, describe } from "vitest";
import { calcCart } from "./calcCart";
import { testDiscounts } from "./mockData/discounts";
import { cartTestResults } from "./mockData/results";
import { testTariffs } from "./mockData/tariffs";
describe("Cart calculation", () => {
for (let i = 0; i < cartTestResults.length; i++) {
test(`Cart calculation №${i}`, () => {
const usedTariffsMask = cartTestResults[i][1];
const isNkoApplied = Boolean(usedTariffsMask.pop());
const tariffs = testTariffs.filter((_, index) => (usedTariffsMask[index] === 1));
const cart = calcCart(tariffs, testDiscounts, 0, "someuserid", isNkoApplied);
expect(cart.priceAfterDiscounts).toBeCloseTo(cartTestResults[i][0]);
});
}
});

@ -1,192 +0,0 @@
import { CartData, PrivilegeCartData, TariffCartData } from "../../model/cart";
import { Discount } from "../../model/discount";
import { Tariff } from "../../model/tariff";
import { findCartDiscount, findDiscountFactor, findLoyaltyDiscount, findNkoDiscount, findPrivilegeDiscount, findServiceDiscount } from "./utils";
export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmount: number, userId: string, isUserNko?: boolean): CartData {
const cartData: CartData = {
services: [],
priceBeforeDiscounts: 0,
priceAfterDiscounts: 0,
allAppliedDiscounts: new Set(),
};
const privilegeAmountById = new Map<string, number>();
const servicePriceByKey = new Map<string, number>();
tariffs.forEach(tariff => {
if (tariff.privileges === undefined) return;
if (
(tariff.price || 0) > 0
&& tariff.privileges.length !== 1
) throw new Error("Price is defined for tariff with several privileges");
let serviceData = cartData.services.find(service => (service.serviceKey === "custom" && tariff.isCustom));
if (!serviceData && !tariff.isCustom) serviceData = cartData.services.find(service => service.serviceKey === tariff.privileges[0]?.serviceKey);
if (!serviceData) {
serviceData = {
serviceKey: tariff.isCustom ? "custom" : tariff.privileges[0]?.serviceKey,
tariffs: [],
price: 0,
};
cartData.services.push(serviceData);
}
const tariffCartData: TariffCartData = {
price: tariff.price ?? 0,
isCustom: tariff.isCustom,
privileges: [],
id: tariff._id,
name: tariff.name,
};
serviceData.tariffs.push(tariffCartData);
tariff.privileges.forEach(privilege => {
let privilegePrice = privilege.amount * privilege.price;
if (!tariff.price) tariffCartData.price += privilegePrice;
else privilegePrice = tariff.price;
const privilegeCartData: PrivilegeCartData = {
serviceKey: privilege.serviceKey,
privilegeId: privilege.privilegeId,
description: privilege.description,
price: privilegePrice,
amount: privilege.amount,
appliedDiscounts: new Set(),
};
privilegeAmountById.set(
privilege.privilegeId,
privilege.amount + (privilegeAmountById.get(privilege.privilegeId) ?? 0)
);
servicePriceByKey.set(
privilege.serviceKey,
privilegePrice + (servicePriceByKey.get(privilege.serviceKey) ?? 0)
);
tariffCartData.privileges.push(privilegeCartData);
});
serviceData.price += tariffCartData.price;
});
cartData.priceBeforeDiscounts = Array.from(servicePriceByKey.values()).reduce((a, b) => a + b, 0);
cartData.priceAfterDiscounts = cartData.priceBeforeDiscounts;
const nkoDiscount = findNkoDiscount(discounts);
if (isUserNko && nkoDiscount) {
cartData.allAppliedDiscounts.add(nkoDiscount);
cartData.services.forEach(service => {
service.tariffs.forEach(tariff => {
tariff.privileges.forEach(privilege => {
privilege.appliedDiscounts.add(nkoDiscount);
const discountAmount = privilege.price * (1 - findDiscountFactor(nkoDiscount));
privilege.price -= discountAmount;
tariff.price -= discountAmount;
service.price -= discountAmount;
cartData.priceAfterDiscounts -= discountAmount;
});
});
});
return cartData;
}
cartData.services.forEach(service => {
service.tariffs.forEach(tariff => {
tariff.privileges.forEach(privilege => {
const privilegeTotalAmount = privilegeAmountById.get(privilege.privilegeId) ?? 0;
const discount = findPrivilegeDiscount(privilege.privilegeId, privilegeTotalAmount, discounts, userId);
if (!discount) return;
cartData.allAppliedDiscounts.add(discount);
privilege.appliedDiscounts.add(discount);
const discountAmount = privilege.price * (1 - findDiscountFactor(discount));
privilege.price -= discountAmount;
tariff.price -= discountAmount;
service.price -= discountAmount;
cartData.priceAfterDiscounts -= discountAmount;
const serviceTotalPrice = servicePriceByKey.get(privilege.serviceKey);
if (!serviceTotalPrice) throw new Error(`Service key ${privilege.serviceKey} not found in servicePriceByKey`);
servicePriceByKey.set(privilege.serviceKey, serviceTotalPrice - discountAmount);
});
});
});
cartData.services.forEach(service => {
service.tariffs.map(tariff => {
tariff.privileges.forEach(privilege => {
const serviceTotalPrice = servicePriceByKey.get(privilege.serviceKey);
if (!serviceTotalPrice) throw new Error(`Service key ${privilege.serviceKey} not found in servicePriceByKey`);
const discount = findServiceDiscount(privilege.serviceKey, serviceTotalPrice, discounts, userId);
if (!discount) return;
cartData.allAppliedDiscounts.add(discount);
privilege.appliedDiscounts.add(discount);
const discountAmount = privilege.price * (1 - findDiscountFactor(discount));
privilege.price -= discountAmount;
tariff.price -= discountAmount;
service.price -= discountAmount;
cartData.priceAfterDiscounts -= discountAmount;
});
});
});
const userDiscount = discounts.find(discount => discount.Condition.User === userId);
const cartDiscount = findCartDiscount(cartData.priceAfterDiscounts, discounts);
if (cartDiscount) {
cartData.services.forEach(service => {
if (service.serviceKey === userDiscount?.Condition.Group) return;
service.tariffs.forEach(tariff => {
tariff.privileges.forEach(privilege => {
cartData.allAppliedDiscounts.add(cartDiscount);
privilege.appliedDiscounts.add(cartDiscount);
const discountAmount = privilege.price * (1 - findDiscountFactor(cartDiscount));
privilege.price -= discountAmount;
tariff.price -= discountAmount;
service.price -= discountAmount;
cartData.priceAfterDiscounts -= discountAmount;
});
});
});
}
const loyalDiscount = findLoyaltyDiscount(purchasesAmount, discounts);
if (loyalDiscount) {
cartData.services.forEach(service => {
if (service.serviceKey === userDiscount?.Condition.Group) return;
service.tariffs.forEach(tariff => {
tariff.privileges.forEach(privilege => {
cartData.allAppliedDiscounts.add(loyalDiscount);
privilege.appliedDiscounts.add(loyalDiscount);
const discountAmount = privilege.price * (1 - findDiscountFactor(loyalDiscount));
privilege.price -= discountAmount;
tariff.price -= discountAmount;
service.price -= discountAmount;
cartData.priceAfterDiscounts -= discountAmount;
});
});
});
}
return cartData;
}

@ -1,52 +0,0 @@
import { CustomTariffUserValues } from "../../model/customTariffs";
import { Discount } from "../../model/discount";
import { CustomPrivilegeWithAmount } from "../../model/privilege";
import { Tariff } from "../../model/tariff";
import { calcCart } from "./calcCart";
export function calcCustomTariffPrice(
customTariffUserValues: CustomTariffUserValues,
servicePrivileges: CustomPrivilegeWithAmount[],
cartTariffs: Tariff[],
discounts: Discount[],
purchasesAmount: number,
isUserNko: boolean,
userId: string,
) {
const privileges = new Array<CustomPrivilegeWithAmount>();
const priceBeforeDiscounts = servicePrivileges.reduce((price, privilege) => {
const amount = customTariffUserValues?.[privilege._id] ?? 0;
return price + privilege.price * amount;
}, 0);
Object.keys(customTariffUserValues).forEach(privilegeId => {
const pwa = servicePrivileges.find(p => p._id === privilegeId);
if (!pwa) return;
if (customTariffUserValues[privilegeId] > 0) privileges.push({
...pwa,
amount: customTariffUserValues[privilegeId]
});
});
const customTariff: Tariff = {
_id: crypto.randomUUID(),
name: "",
price: 0,
description: "",
isCustom: true,
isDeleted: false,
privileges: privileges,
};
const cart = calcCart([...cartTariffs, customTariff], discounts, purchasesAmount, userId, isUserNko);
const customService = cart.services.flatMap(service => service.tariffs).find(tariff => tariff.id === customTariff._id);
if (!customService) throw new Error("Custom service not found in cart");
return {
priceBeforeDiscounts,
priceAfterDiscounts: customService.price,
};
}

@ -1,31 +0,0 @@
import { Discount } from "../../model/discount";
import { Tariff } from "../../model/tariff";
import { calcCart } from "./calcCart";
export function calcTariffPrice(
targetTariff: Tariff,
discounts: Discount[],
purchasesAmount: number,
currentTariffs: Tariff[],
isUserNko: boolean,
userId: string,
): {
priceBeforeDiscounts: number;
priceAfterDiscounts: number;
} {
const priceBeforeDiscounts = targetTariff.price || targetTariff.privileges.reduce(
(sum, privilege) => sum + privilege.amount * privilege.price,
0
);
const cart = calcCart([...currentTariffs, targetTariff], discounts, purchasesAmount, userId, isUserNko);
const tariffCartData = cart.services.flatMap(service => service.tariffs).find(tariff => tariff.id === targetTariff._id);
if (!tariffCartData) throw new Error(`Target tariff ${targetTariff._id} not found in cart`);
return {
priceBeforeDiscounts,
priceAfterDiscounts: tariffCartData.price,
};
}

@ -1,4 +0,0 @@
export * from "./calcCart";
export * from "./calcCustomTariffPrice";
export * from "./calcTariffPrice";
export * from "./utils";

@ -1,901 +0,0 @@
import { Discount } from "../../../model/discount";
export const testDiscounts: Discount[] = [
{
"ID": "6521d98b166f36879928ebbf",
"Name": "NKO",
"Layer": 4,
"Description": "скидка ветеранам НКО",
"Condition": {
"Period": {
"From": "2023-10-07T21:00:45.829Z",
"To": "2023-11-06T21:00:45.829Z"
},
"User": "",
"UserType": "nko",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [],
"Factor": 0.4,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": true
},
"Audit": {
"UpdatedAt": "2023-11-10T23:20:32.619Z",
"CreatedAt": "2023-09-16T20:10:26.048Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657b9b73153787e41052c25b",
"Name": "1000 шаблонов",
"Layer": 1,
"Description": "Тариф на 1000 шаблонов",
"Condition": {
"Period": {
"From": "2023-12-15T00:18:57.999Z",
"To": "2024-01-14T00:18:58Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "templateCnt",
"Term": "1000",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "templateCnt",
"Factor": 0.7,
"Overhelm": false
}
],
"Factor": 0.7,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-15T00:18:59.138Z",
"CreatedAt": "2023-12-15T00:18:59.138Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657b9cb0153787e41052c25c",
"Name": "десять тысяч шаблонов",
"Layer": 1,
"Description": "Тариф 10 000",
"Condition": {
"Period": {
"From": "2023-12-15T00:24:15.555Z",
"To": "2024-01-14T00:24:15.555Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "templateCnt",
"Term": "10000",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "templateCnt",
"Factor": 0.5,
"Overhelm": false
}
],
"Factor": 0.5,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-15T00:24:16.562Z",
"CreatedAt": "2023-12-15T00:24:16.562Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657b9e67153787e41052c25d",
"Name": "3 месяца",
"Layer": 1,
"Description": "Тариф 3 месяца",
"Condition": {
"Period": {
"From": "2023-12-15T00:31:34.807Z",
"To": "2024-01-14T00:31:34.807Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "templateUnlimTime",
"Term": "90",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "templateUnlimTime",
"Factor": 0.8,
"Overhelm": false
}
],
"Factor": 0.8,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-15T00:31:35.601Z",
"CreatedAt": "2023-12-15T00:31:35.601Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657b9e8a153787e41052c25e",
"Name": "год",
"Layer": 1,
"Description": "Тариф год",
"Condition": {
"Period": {
"From": "2023-12-15T00:32:09.329Z",
"To": "2024-01-14T00:32:09.329Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "templateUnlimTime",
"Term": "365",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "templateUnlimTime",
"Factor": 0.65,
"Overhelm": false
}
],
"Factor": 0.65,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-15T00:32:10.123Z",
"CreatedAt": "2023-12-15T00:32:10.123Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657b9eb4153787e41052c25f",
"Name": "3 года",
"Layer": 1,
"Description": "Тариф 3 года",
"Condition": {
"Period": {
"From": "2023-12-15T00:32:51.379Z",
"To": "2024-01-14T00:32:51.379Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "templateUnlimTime",
"Term": "1095",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "templateUnlimTime",
"Factor": 0.5,
"Overhelm": false
}
],
"Factor": 0.5,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-15T00:32:52.174Z",
"CreatedAt": "2023-12-15T00:32:52.174Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f5028153787e41052c266",
"Name": "10т.р",
"Layer": 4,
"Description": "купил больше чем на 10 тыров",
"Condition": {
"Period": {
"From": "2023-12-17T19:48:47.466Z",
"To": "2024-01-16T19:48:47.466Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "10000",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.98,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:48:46.072Z",
"CreatedAt": "2023-12-17T19:46:48.854Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f50c3153787e41052c268",
"Name": "1т.р",
"Layer": 4,
"Description": "купил больше чем на 1 тыр",
"Condition": {
"Period": {
"From": "2023-12-17T19:49:24.782Z",
"To": "2024-01-16T19:49:24.782Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "100000",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.99,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:49:23.384Z",
"CreatedAt": "2023-12-17T19:49:23.384Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f50e4153787e41052c269",
"Name": "100т.р",
"Layer": 4,
"Description": "купил больше чем на 100 тыров",
"Condition": {
"Period": {
"From": "2023-12-17T19:49:57.462Z",
"To": "2024-01-16T19:49:57.462Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "10000000",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.95,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:49:56.066Z",
"CreatedAt": "2023-12-17T19:49:56.066Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f511b153787e41052c26a",
"Name": "1 т.р",
"Layer": 3,
"Description": "Больще 1т.р",
"Condition": {
"Period": {
"From": "2023-12-17T19:50:52.764Z",
"To": "2024-01-16T19:50:52.764Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "100000",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.95,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:50:51.408Z",
"CreatedAt": "2023-12-17T19:50:51.408Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f512d153787e41052c26b",
"Name": "5 т.р",
"Layer": 3,
"Description": "Больще 5т.р",
"Condition": {
"Period": {
"From": "2023-12-17T19:51:11.104Z",
"To": "2024-01-16T19:51:11.104Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "500000",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.93,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:51:09.707Z",
"CreatedAt": "2023-12-17T19:51:09.707Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f5144153787e41052c26c",
"Name": "10 т.р",
"Layer": 3,
"Description": "Больше 10т.р",
"Condition": {
"Period": {
"From": "2023-12-17T19:51:33.502Z",
"To": "2024-01-16T19:51:33.502Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "1000000",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.91,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:51:32.105Z",
"CreatedAt": "2023-12-17T19:51:32.105Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "657f515a153787e41052c26d",
"Name": "50 т.р",
"Layer": 3,
"Description": "Больше 50т.р",
"Condition": {
"Period": {
"From": "2023-12-17T19:51:56.316Z",
"To": "2024-01-16T19:51:56.316Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "5000000",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.89,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-17T19:51:54.919Z",
"CreatedAt": "2023-12-17T19:51:54.919Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "65872fb4153787e41052c26e",
"Name": "Лямчик",
"Layer": 4,
"Description": "Купил больше чем на миллиона",
"Condition": {
"Period": {
"From": "2023-12-23T19:06:27.521Z",
"To": "2024-01-22T19:06:27.521Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "100000000",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.9,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-23T19:06:28.253Z",
"CreatedAt": "2023-12-23T19:06:28.253Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "6588c6e9153787e41052c26f",
"Name": "больше 5т.р",
"Layer": 2,
"Description": "Шаблонизатор:Больше 5т.р",
"Condition": {
"Period": {
"From": "2023-12-25T00:03:53.024Z",
"To": "2024-01-24T00:03:53.024Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "500000",
"Group": "templategen"
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.98,
"TargetScope": "Sum",
"TargetGroup": "templategen",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-25T00:03:53.269Z",
"CreatedAt": "2023-12-25T00:03:53.269Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "6588c70c153787e41052c270",
"Name": "больше 10 т.р",
"Layer": 2,
"Description": "Шаблонизатор:Больше 10 т.р",
"Condition": {
"Period": {
"From": "2023-12-25T00:04:28.279Z",
"To": "2024-01-24T00:04:28.279Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "1000000",
"Group": "templategen"
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.97,
"TargetScope": "Sum",
"TargetGroup": "templategen",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-25T00:04:28.442Z",
"CreatedAt": "2023-12-25T00:04:28.442Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "6588c724153787e41052c271",
"Name": "больше 100 т.р",
"Layer": 2,
"Description": "Шаблонизатор:Больше 100 т.р",
"Condition": {
"Period": {
"From": "2023-12-25T00:04:52.461Z",
"To": "2024-01-24T00:04:52.461Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "",
"Term": "0",
"Usage": "0",
"PriceFrom": "10000000",
"Group": "templategen"
},
"Target": {
"Products": [
{
"ID": "",
"Factor": 0,
"Overhelm": false
}
],
"Factor": 0.95,
"TargetScope": "Sum",
"TargetGroup": "templategen",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2023-12-25T00:04:52.625Z",
"CreatedAt": "2023-12-25T00:04:52.625Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "65a49c215389294d1c348511",
"Name": "1000 заявок",
"Layer": 1,
"Description": "Полное прохождение 1000 опросов респондентом",
"Condition": {
"Period": {
"From": "2024-01-15T02:44:49.156Z",
"To": "2024-02-14T02:44:49.156Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "quizCnt",
"Term": "1000",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "quizCnt",
"Factor": 0.7,
"Overhelm": false
}
],
"Factor": 0.7,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2024-01-15T02:44:48.995Z",
"CreatedAt": "2024-01-15T02:44:48.995Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "65a49c4f5389294d1c348512",
"Name": "10000 заявок",
"Layer": 1,
"Description": "Полное прохождение 10000 опросов респондентом",
"Condition": {
"Period": {
"From": "2024-01-15T02:45:37.236Z",
"To": "2024-02-14T02:45:37.236Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "quizCnt",
"Term": "10000",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "quizCnt",
"Factor": 0.5,
"Overhelm": false
}
],
"Factor": 0.5,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2024-01-15T02:45:35.984Z",
"CreatedAt": "2024-01-15T02:45:35.984Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "65a49d1a5389294d1c348513",
"Name": "3 месяца",
"Layer": 1,
"Description": "3 Месяца безлимита",
"Condition": {
"Period": {
"From": "2024-01-15T02:48:59.617Z",
"To": "2024-02-14T02:48:59.617Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "quizUnlimTime",
"Term": "90",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "quizUnlimTime",
"Factor": 0.8,
"Overhelm": false
}
],
"Factor": 0.8,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2024-01-15T02:48:58.560Z",
"CreatedAt": "2024-01-15T02:48:58.560Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "65a49d335389294d1c348514",
"Name": "Год",
"Layer": 1,
"Description": "Год безлимита",
"Condition": {
"Period": {
"From": "2024-01-15T02:49:24.266Z",
"To": "2024-02-14T02:49:24.266Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "quizUnlimTime",
"Term": "365",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "quizUnlimTime",
"Factor": 0.65,
"Overhelm": false
}
],
"Factor": 0.65,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2024-01-15T02:49:23.024Z",
"CreatedAt": "2024-01-15T02:49:23.024Z",
"Deleted": false
},
"Deprecated": false
},
{
"ID": "65a49d4f5389294d1c348515",
"Name": "3 Года",
"Layer": 1,
"Description": "3 Года безлимита",
"Condition": {
"Period": {
"From": "2024-01-15T02:49:52.264Z",
"To": "2024-02-14T02:49:52.264Z"
},
"User": "",
"UserType": "",
"Coupon": "",
"PurchasesAmount": "0",
"CartPurchasesAmount": "0",
"Product": "quizUnlimTime",
"Term": "1095",
"Usage": "0",
"PriceFrom": "0",
"Group": ""
},
"Target": {
"Products": [
{
"ID": "quizUnlimTime",
"Factor": 0.5,
"Overhelm": false
}
],
"Factor": 0.5,
"TargetScope": "Sum",
"TargetGroup": "",
"Overhelm": false
},
"Audit": {
"UpdatedAt": "2024-01-15T02:49:51.024Z",
"CreatedAt": "2024-01-15T02:49:51.024Z",
"Deleted": false
},
"Deprecated": false
}
];

@ -1,84 +0,0 @@
type CartTestResult = [
/** Cart price */
number,
/** Used tariff mask, last number shows if nko applied */
number[],
];
export const cartTestResults: CartTestResult[] = [
[750184.5, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]],
[680, [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]],
[232560, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910227.5, [0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0]],
[1274000, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]],
[95000, [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[465000, [0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0]],
[1700, [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[465093, [0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]],
[910273, [0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0]],
[910000, [0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]],
[848285.55, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[911207.5700000001, [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]],
[383158.75, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1101077.25, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]],
[602756.25, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[465069.75, [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[910887.25, [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]],
[910250.25, [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]],
[116280, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[864016.5, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]],
[348840, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910591.5, [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0]],
[465186, [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0]],
[465000, [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0]],
[912912, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]],
[910000, [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]],
[465569.82499999995, [0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[683432.355, [0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[465068.35500000004, [0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]],
[910039.5850000001, [0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0]],
[910089.635, [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]],
[637980, [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[865644, [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910078.26, [1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910076.4400000001, [0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]],
[973904.9775, [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1196672.9775, [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[749535.36, [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[956184.3200000001, [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910247.52, [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]],
[903498.255, [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[911006.915, [1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1180018.385, [0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]],
[797926.05, [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910668.85, [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0]],
[1035015.8, [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]],
[536665.8, [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910054.6, [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0]],
[910338.0650000001, [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[877021.155, [0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[1037969.66, [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[1147125.98, [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[760745.5800000001, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[967153.4600000001, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910182, [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]],
[1092804.6675, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1315572.6675, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[1504998.2675, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[872300.9400000001, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1076309.78, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910538.7200000001, [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]],
[1265735.3800000001, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[902551.05, [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1105909.35, [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910080.0800000001, [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1295334.95, [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[1354679.69, [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[910176.54, [1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]],
[902190.2100000001, [0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1105556.27, [0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[910063.7000000001, [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[902280.42, [1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
[1105644.54, [1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
[1295070.1400000001, [1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]],
];

@ -1,812 +0,0 @@
import { Tariff } from "../../../model/tariff";
export const testTariffs: Tariff[] = [
{
"_id": "64f06be63fae7d590bf6426c",
"name": "Безлимит, Количество Шаблонов, 2023-08-31T10:31:02.472Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 130
},
{
"name": "Количество Шаблонов",
"privilegeId": "64e88d30c4c82e949d5c443e",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 5,
"amount": 2100
}
],
"isDeleted": false,
"createdAt": "2023-08-31T10:31:02.570Z",
"updatedAt": "2023-08-31T10:31:02.570Z"
},
{
"_id": "64f06be93fae7d590bf64271",
"name": "Безлимит, Количество Шаблонов, 2023-08-31T10:31:05.703Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 130
},
{
"name": "Количество Шаблонов",
"privilegeId": "64e88d30c4c82e949d5c443e",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 5,
"amount": 2100
}
],
"isDeleted": false,
"createdAt": "2023-08-31T10:31:05.732Z",
"updatedAt": "2023-08-31T10:31:05.732Z"
},
{
"_id": "64f06be93fae7d590bf64276",
"name": "Безлимит, Количество Шаблонов, 2023-08-31T10:31:05.874Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 130
},
{
"name": "Количество Шаблонов",
"privilegeId": "64e88d30c4c82e949d5c443e",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 5,
"amount": 2100
}
],
"isDeleted": false,
"createdAt": "2023-08-31T10:31:05.884Z",
"updatedAt": "2023-08-31T10:31:05.884Z"
},
{
"_id": "64f06c103fae7d590bf6427b",
"name": "Безлимит, Количество Шаблонов, 2023-08-31T10:31:44.015Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 130
},
{
"name": "Количество Шаблонов",
"privilegeId": "64e88d30c4c82e949d5c443e",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 5,
"amount": 2100
}
],
"isDeleted": false,
"createdAt": "2023-08-31T10:31:44.198Z",
"updatedAt": "2023-08-31T10:31:44.198Z"
},
{
"_id": "64f06c123fae7d590bf64280",
"name": "Безлимит, Количество Шаблонов, 2023-08-31T10:31:16.087Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 130
},
{
"name": "Количество Шаблонов",
"privilegeId": "64e88d30c4c82e949d5c443e",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 5,
"amount": 2100
}
],
"isDeleted": false,
"createdAt": "2023-08-31T10:31:46.954Z",
"updatedAt": "2023-08-31T10:31:46.954Z"
},
{
"_id": "64f61a713fae7d590bf6494c",
"name": "Размер Диска, Безлимит, 2023-09-04T17:57:05.862Z",
"isCustom": true,
"privileges": [
{
"name": "Размер Диска",
"privilegeId": "64e88d30c4c82e949d5c443c",
"serviceKey": "templategen",
"description": "Обьём ПенаДиска для хранения шаблонов и результатов шаблонизации",
"type": "count",
"value": "МБ",
"price": 555,
"amount": 1500
},
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 220
}
],
"isDeleted": false,
"createdAt": "2023-09-04T17:57:05.987Z",
"updatedAt": "2023-09-04T17:57:05.987Z"
},
{
"_id": "64ff6eb75913fc89c5667d85",
"name": "Безлимит, 2023-09-11T19:47:03.383Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "64e88d30c4c82e949d5c443d",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 10,
"amount": 170
}
],
"isDeleted": false,
"createdAt": "2023-09-11T19:47:03.546Z",
"updatedAt": "2023-09-11T19:47:03.546Z"
},
{
"_id": "657b9b11215b615d2e35741f",
"name": "100 шаблонов",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Количество Шаблонов",
"privilegeId": "templateCnt",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 1000,
"amount": 100
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:17:21.104Z",
"updatedAt": "2023-12-15T00:17:21.104Z"
},
{
"_id": "657b9b1a215b615d2e357424",
"name": "1000 шаблонов",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Количество Шаблонов",
"privilegeId": "templateCnt",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 1000,
"amount": 1000
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:17:30.813Z",
"updatedAt": "2023-12-15T00:17:30.813Z"
},
{
"_id": "657b9b98215b615d2e35743a",
"name": "10000 шаблонов",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Количество Шаблонов",
"privilegeId": "templateCnt",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 1000,
"amount": 10000
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:19:36.848Z",
"updatedAt": "2023-12-15T00:19:36.848Z"
},
{
"_id": "657b9bd0215b615d2e357445",
"name": "1 день",
"price": 10000,
"isCustom": false,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "templateUnlimTime",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 1700,
"amount": 1
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:20:32.586Z",
"updatedAt": "2023-12-15T00:20:32.586Z"
},
{
"_id": "657b9c15215b615d2e35745f",
"name": "Месяц",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "templateUnlimTime",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 1700,
"amount": 30
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:21:41.878Z",
"updatedAt": "2023-12-15T00:21:41.878Z"
},
{
"_id": "657b9c25215b615d2e357464",
"name": "3 месяца",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "templateUnlimTime",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 1700,
"amount": 90
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:21:57.114Z",
"updatedAt": "2023-12-15T00:21:57.114Z"
},
{
"_id": "657b9c4d215b615d2e357469",
"name": "Год",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "templateUnlimTime",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 1700,
"amount": 365
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:22:37.456Z",
"updatedAt": "2023-12-15T00:22:37.456Z"
},
{
"_id": "657b9c59215b615d2e35746e",
"name": "3 года",
"price": 0,
"isCustom": false,
"privileges": [
{
"name": "Безлимит",
"privilegeId": "templateUnlimTime",
"serviceKey": "templategen",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 1700,
"amount": 1095
}
],
"isDeleted": false,
"createdAt": "2023-12-15T00:22:49.492Z",
"updatedAt": "2023-12-15T00:22:49.492Z"
},
{
"_id": "65af14358507c326f5a2db91",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-23T01:19:49.676Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 30
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 100
}
],
"isDeleted": false,
"createdAt": "2024-01-23T01:19:49.897Z",
"updatedAt": "2024-01-23T01:19:49.897Z"
},
{
"_id": "65af1b978507c326f5a2dbaa",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-23T01:51:19.622Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 3
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 100
}
],
"isDeleted": false,
"createdAt": "2024-01-23T01:51:19.814Z",
"updatedAt": "2024-01-23T01:51:19.814Z"
},
{
"_id": "65afd0518507c326f5a2e59d",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-23T14:42:25.093Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 70
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 850
}
],
"isDeleted": false,
"createdAt": "2024-01-23T14:42:25.154Z",
"updatedAt": "2024-01-23T14:42:25.154Z"
},
{
"_id": "65afd05e8507c326f5a2e5a2",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-23T14:42:38.254Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 70
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 850
}
],
"isDeleted": false,
"createdAt": "2024-01-23T14:42:38.338Z",
"updatedAt": "2024-01-23T14:42:38.339Z"
},
{
"_id": "65afd0738507c326f5a2e5a7",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-23T14:42:58.966Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 70
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 850
}
],
"isDeleted": false,
"createdAt": "2024-01-23T14:42:59.050Z",
"updatedAt": "2024-01-23T14:42:59.050Z"
},
{
"_id": "65afd08a8507c326f5a2e5ac",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-23T14:43:22.214Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 30
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 100
}
],
"isDeleted": false,
"createdAt": "2024-01-23T14:43:22.284Z",
"updatedAt": "2024-01-23T14:43:22.284Z"
},
{
"_id": "65b2d740c644401f2ff3ad26",
"name": "Безлимит Опросов, Количество Заявок, 2024-01-25T21:48:47.933Z",
"isCustom": true,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 100
},
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 3480
}
],
"isDeleted": false,
"createdAt": "2024-01-25T21:48:48.015Z",
"updatedAt": "2024-01-25T21:48:48.015Z"
},
{
"_id": "657b9b06215b615d2e35741a",
"name": "10 шаблонов",
"price": 20000,
"order": 1,
"isCustom": false,
"privileges": [
{
"name": "Количество Шаблонов",
"privilegeId": "templateCnt",
"serviceKey": "templategen",
"description": "Количество шаблонов, которые может сделать пользователь сервиса",
"type": "count",
"value": "шаблон",
"price": 1000,
"amount": 0
}
],
"isDeleted": false,
"createdAt": "2024-01-14T19:22:07.206Z",
"updatedAt": "2024-01-14T19:22:07.206Z"
},
{
"_id": "65a493550089bcd87ba53d4b",
"name": "10 заявок",
"description": "Полное прохождение 10 опросов респондентом",
"price": 0,
"order": 1,
"isCustom": false,
"privileges": [
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 10
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:07:17.403Z",
"updatedAt": "2024-01-15T02:07:17.403Z"
},
{
"_id": "65a493b60089bcd87ba53d5f",
"name": "1 день",
"description": "день безлимитного пользования сервисом",
"price": 10000,
"order": 1,
"isCustom": false,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 1
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:08:54.275Z",
"updatedAt": "2024-01-15T02:08:54.275Z"
},
{
"_id": "65a493640089bcd87ba53d50",
"name": "100 заявок",
"description": "Полное прохождение 100 опросов респондентом",
"price": 0,
"order": 2,
"isCustom": false,
"privileges": [
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 100
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:07:32.444Z",
"updatedAt": "2024-01-15T02:07:32.444Z"
},
{
"_id": "65a497890089bcd87ba53d64",
"name": "Месяц",
"description": "Месяц безлимитного пользования сервисом",
"price": 0,
"order": 2,
"isCustom": false,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 30
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:25:13.366Z",
"updatedAt": "2024-01-15T02:25:13.366Z"
},
{
"_id": "65a493740089bcd87ba53d55",
"name": "1000 заявок",
"description": "Полное прохождение 1000 опросов респондентом",
"price": 0,
"order": 3,
"isCustom": false,
"privileges": [
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 1000
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:07:48.095Z",
"updatedAt": "2024-01-15T02:07:48.095Z"
},
{
"_id": "65a4987e0089bcd87ba53d75",
"name": "3 Месяца",
"description": "3 Месяца безлимитного пользования сервисом",
"price": 0,
"order": 3,
"isCustom": false,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 90
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:29:18.577Z",
"updatedAt": "2024-01-15T02:29:18.577Z"
},
{
"_id": "65a498cc0089bcd87ba53d92",
"name": "Год",
"description": "Год безлимитного пользования сервисом",
"price": 0,
"order": 3,
"isCustom": false,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 365
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:30:36.131Z",
"updatedAt": "2024-01-15T02:30:36.131Z"
},
{
"_id": "65a493830089bcd87ba53d5a",
"name": "10000 заявок",
"description": "Полное прохождение 10000 опросов респондентом",
"price": 0,
"order": 4,
"isCustom": false,
"privileges": [
{
"name": "Количество Заявок",
"privilegeId": "quizCnt",
"serviceKey": "squiz",
"description": "Количество полных прохождений опросов",
"type": "count",
"value": "заявка",
"price": 2000,
"amount": 10000
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:08:03.341Z",
"updatedAt": "2024-01-15T02:08:03.341Z"
},
{
"_id": "65a498f80089bcd87ba53d97",
"name": "3 Года",
"description": "3 Года безлимитного пользования сервисом",
"price": 0,
"order": 4,
"isCustom": false,
"privileges": [
{
"name": "Безлимит Опросов",
"privilegeId": "quizUnlimTime",
"serviceKey": "squiz",
"description": "Количество дней, в течении которых пользование сервисом безлимитно",
"type": "day",
"value": "день",
"price": 3400,
"amount": 1095
}
],
"isDeleted": false,
"createdAt": "2024-01-15T02:31:20.448Z",
"updatedAt": "2024-01-15T02:31:20.448Z"
}
];

@ -1,127 +0,0 @@
import { Discount } from "../../model/discount";
export function findDiscountFactor(discount: Discount | null | undefined): number {
if (!discount) return 1;
if (discount.Layer === 1) return discount.Target.Products[0].Factor;
return discount.Target.Factor;
}
export function findNkoDiscount(discounts: Discount[]): Discount | null {
const applicableDiscounts = discounts.filter(discount => discount.Condition.UserType === "nko");
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
return Number(current.Condition.CartPurchasesAmount) > Number(prev.Condition.CartPurchasesAmount) ? current : prev;
});
return maxValueDiscount;
}
export function findPrivilegeDiscount(
privilegeId: string,
privilegeAmount: number,
discounts: Discount[],
userId: string,
): Discount | null {
const applicableDiscounts = discounts.filter(discount => {
return (
discount.Layer === 1
&& privilegeId === discount.Condition.Product
&& privilegeAmount >= Number(discount.Condition.Term)
&& (discount.Condition.User === "" || discount.Condition.User === userId)
);
});
if (!applicableDiscounts.length) return null;
let maxValueDiscount: Discount = applicableDiscounts[0];
for (const discount of applicableDiscounts) {
if (discount.Condition.User !== "" && discount.Condition.User === userId) {
maxValueDiscount = discount;
break;
}
if (Number(discount.Condition.Term) > Number(maxValueDiscount.Condition.Term)) {
maxValueDiscount = discount;
}
}
return maxValueDiscount;
}
export function findServiceDiscount(
serviceKey: string,
currentPrice: number,
discounts: Discount[],
userId: string,
): Discount | null {
const applicableDiscounts = discounts.filter(discount => {
return (
discount.Layer === 2
&& serviceKey === discount.Condition.Group
&& currentPrice >= Number(discount.Condition.PriceFrom)
&& (discount.Condition.User === "" || discount.Condition.User === userId)
);
});
if (!applicableDiscounts.length) return null;
let maxValueDiscount: Discount = applicableDiscounts[0];
for (const discount of applicableDiscounts) {
if (discount.Condition.User !== "" && discount.Condition.User === userId) {
maxValueDiscount = discount;
break;
}
if (Number(discount.Condition.PriceFrom) > Number(maxValueDiscount.Condition.PriceFrom)) {
maxValueDiscount = discount;
}
}
return maxValueDiscount;
}
export function findCartDiscount(
cartPurchasesAmount: number,
discounts: Discount[],
): Discount | null {
const applicableDiscounts = discounts.filter(discount => {
return (
discount.Layer === 3
&& cartPurchasesAmount >= Number(discount.Condition.CartPurchasesAmount)
);
});
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
return Number(current.Condition.CartPurchasesAmount) > Number(prev.Condition.CartPurchasesAmount) ? current : prev;
});
return maxValueDiscount;
}
export function findLoyaltyDiscount(
purchasesAmount: number,
discounts: Discount[],
): Discount | null {
const applicableDiscounts = discounts.filter(discount => {
return (
discount.Layer === 4
&& discount.Condition.UserType !== "nko"
&& purchasesAmount >= Number(discount.Condition.PurchasesAmount)
);
});
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
return Number(current.Condition.PurchasesAmount) > Number(prev.Condition.PurchasesAmount) ? current : prev;
});
return maxValueDiscount;
}

@ -1,3 +0,0 @@
export const devlog: typeof console.log = (...args) => {
if (process.env.NODE_ENV === "development") console.log(...args);
};

@ -1,5 +0,0 @@
export function getInitials(firstname: string, secondname: string) {
return firstname[0] && secondname[0]
? firstname[0] + secondname[0]
: "АА";
}

@ -1,4 +0,0 @@
export * from "./backendMessageHandler";
export * from "./cart";
export * from "./devlog";
export * from "./getInitials";

9799
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,75 +1,12 @@
{
"name": "@frontend/kitui",
"version": "2.2.0",
"description": "test",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"repository": {
"type": "git",
"url": "git@gitea.pena:PenaSide/UIKit.git"
},
"author": "skeris <kotilion.95@gmail.com>",
"license": "MIT",
"type": "module",
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.js"
}
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test:cart": "vitest ./lib/utils/cart",
"prepublishOnly": "npm run build"
},
"publishConfig": {
"registry": "http://gitea.pena/api/packages/skeris/npm/"
},
"dependencies": {
"immer": "^10.1.1",
"reconnecting-eventsource": "^1.6.4"
},
"devDependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.7",
"@mui/material": "^6.4.6",
"@types/node": "^22.13.10",
"@types/react": "^19.0.4",
"@types/react-dom": "^19.0.4",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^8.26.0",
"@typescript-eslint/parser": "^8.26.0",
"@vitejs/plugin-react": "^4.3.4",
"axios": "^1.8.2",
"eslint": "^9.22.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"npm": "^11.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.3.0",
"react-syntax-highlighter": "^15.6.1",
"typescript": "^5.8.2",
"vite": "^6.2.1",
"vite-plugin-dts": "^4.5.3",
"vitest": "^3.0.8",
"zustand": "^5.0.3"
},
"peerDependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.7",
"@mui/material": "^6.4.6",
"axios": "^1.8.2",
"react": "^19.0.0",
"react-router-dom": "^7.3.0",
"zustand": "^5.0.3"
}
"name": "@frontend/kitui",
"version": "1.0.0",
"description": "test",
"main": "index.js",
"repository": "git@penahub.gitlab.yandexcloud.net:frontend/kitui.git",
"author": "skeris <kotilion.95@gmail.com>",
"license": "MIT",
"publishConfig": {
"registry": "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/"
}
}

@ -1,274 +0,0 @@
import { Box, Button, Container, Pagination, SxProps, Theme, Typography, useTheme } from "@mui/material";
import { PenaLink } from "../lib/components/PenaLink";
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import jsx from "react-syntax-highlighter/dist/esm/languages/prism/tsx";
import { ReactNode } from "react";
import { BurgerButton, CloseButton, CloseButtonSmall, PenaTextField, WalletButton } from "../lib";
import { AvatarButton } from "../lib/components/AvatarButton";
import { LogoutButton } from "../lib/components/LogoutButton";
SyntaxHighlighter.registerLanguage("jsx", jsx);
export function App() {
const theme = useTheme();
return (
<Box sx={{
backgroundColor: theme.palette.bg.main,
minHeight: "100dvh",
width: "100%",
}}>
<Container sx={{
py: 4,
display: "flex",
flexDirection: "column",
gap: 3,
alignItems: "start",
color: "white",
}}>
<Typography variant="h4">Components</Typography>
<ComponentWithCode
code={`<Button variant="pena-contained-dark">Подробнее</Button>`}
element={<Button variant="pena-contained-dark">Подробнее</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-contained-light">Подробнее</Button>`}
element={<Button variant="pena-contained-light">Подробнее</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-outlined-dark">Подробнее</Button>`}
element={<Button variant="pena-outlined-dark">Подробнее</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-outlined-light">Подробнее</Button>`}
element={<Button variant="pena-outlined-light">Подробнее</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-outlined-purple">Перейти</Button>`}
element={<Button variant="pena-outlined-purple">Перейти</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-contained-white1">Купить</Button>`}
element={<Button variant="pena-contained-white1">Купить</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-contained-white2">Купить</Button>`}
element={<Button variant="pena-contained-white2">Выбрать</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-navitem-dark">Подробнее</Button>`}
element={<Button variant="pena-navitem-dark">Подробнее</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-navitem-light">Подробнее</Button>`}
element={<Button variant="pena-navitem-light">Подробнее</Button>}
/>
<ComponentWithCode
code={`<Button variant="pena-text">Подробнее</Button>`}
element={<Button variant="pena-text">Подробнее</Button>}
/>
<ComponentWithCode
code={`<PenaLink>Подробнее</PenaLink>`}
element={<PenaLink href="/">Подробнее</PenaLink>}
/>
<ComponentWithCode
code={`<PenaTextField />`}
element={<PenaTextField />}
/>
<ComponentWithCode
code={`<AvatarButton>AB</AvatarButton>`}
element={<AvatarButton>AB</AvatarButton>}
/>
<ComponentWithCode
code={`<LogoutButton />`}
element={<LogoutButton />}
/>
<ComponentWithCode
code={`<WalletButton />`}
element={<WalletButton />}
/>
<ComponentWithCode
code={`<CloseButton />`}
element={<CloseButton />}
/>
<ComponentWithCode
code={`<CloseButtonSmall />`}
element={<CloseButtonSmall />}
/>
<ComponentWithCode
code={`<BurgerButton />`}
element={<BurgerButton />}
/>
<ComponentWithCode
sx={{
p: 1,
backgroundColor: theme.palette.background.default,
}}
code={`<Pagination variant="pena-pagination" />`}
element={<Pagination
variant="pena-pagination"
count={10}
page={1}
/>}
/>
<ComponentWithCode
code={`<Typography variant="infographic">Some text</Typography>`}
element={<Typography variant="infographic">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="p1">Some text</Typography>`}
element={<Typography variant="p1">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="price">Some text</Typography>`}
element={<Typography variant="price">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="oldPrice">Some text</Typography>`}
element={<Typography variant="oldPrice">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="t1">Some text</Typography>`}
element={<Typography variant="t1">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="pena-card-header1">Some text</Typography>`}
element={<Typography variant="pena-card-header1">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="pena-h1">Some text</Typography>`}
element={<Typography variant="pena-h1">Lorem ipsum dolor sit amet</Typography>}
/>
<ComponentWithCode
code={`<Typography variant="pena-h3">Some text</Typography>`}
element={<Typography variant="pena-h3">Lorem ipsum dolor sit amet</Typography>}
/>
<Typography variant="h4">Colors</Typography>
<Typography variant="body2">Click text to copy</Typography>
<Box sx={{
width: "100%",
display: "flex",
flexWrap: "wrap",
gap: 1,
}}>
<ColorShowcase
color={theme.palette.purple.light}
text1="theme.palette.purple.light"
text2="#944FEE"
/>
<ColorShowcase
color={theme.palette.purple.main}
text1="theme.palette.purple.main"
text2="#7E2AEA"
/>
<ColorShowcase
color={theme.palette.purple.dark}
text1="theme.palette.purple.dark"
text2="#581CA7"
/>
<ColorShowcase
color={theme.palette.background.default}
text1="theme.palette.background.default"
text2="#F2F3F7"
/>
<ColorShowcase
color={theme.palette.bg.main}
text1="theme.palette.bg.main"
text2="#333647"
/>
<ColorShowcase
color={theme.palette.bg.dark}
text1="theme.palette.bg.dark"
text2="#252734"
/>
<ColorShowcase
color={theme.palette.gray.main}
text1="theme.palette.gray.main"
text2="#9A9AAF"
/>
<ColorShowcase
color={theme.palette.gray.dark}
text1="theme.palette.gray.dark"
text2="#4D4D4D"
/>
<ColorShowcase
color={theme.palette.orange.main}
text1="theme.palette.orange.main"
text2="#FB5607"
/>
<ColorShowcase
color={theme.palette.orange.light}
text1="theme.palette.orange.light"
text2="#FC712F"
/>
</Box>
</Container>
</Box>
);
}
function ComponentWithCode({ sx, code, element }: {
sx?: SxProps<Theme>;
code: string;
element: ReactNode;
}) {
return (
<Box sx={{
display: "flex",
flexDirection: "column",
gap: 1,
alignItems: "start",
...sx,
}}>
<SyntaxHighlighter
language="jsx"
style={oneDark}
customStyle={{
padding: "4px",
fontSize: "16px",
margin: 0,
}}
>
{code}
</SyntaxHighlighter>
{element}
</Box>
);
}
function ColorShowcase({ color, text1, text2 }: {
text1: string;
text2: string;
color: string;
}) {
return (
<Box sx={{
backgroundColor: color,
width: "100%",
display: "flex",
flexDirection: "column",
maxWidth: "350px",
p: 2,
gap: 1,
border: "1px solid white",
overflow: "hidden",
}}>
<Typography
onClick={() => navigator.clipboard.writeText(text1)}
sx={{
textShadow: "0 0 6px black",
}}
>{text1}</Typography>
<Typography
onClick={() => navigator.clipboard.writeText(text2)}
sx={{
textShadow: "0 0 6px black",
}}
>{text2}</Typography>
</Box>
);
}

@ -1,15 +0,0 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App.tsx";
import { CssBaseline, ThemeProvider } from "@mui/material";
import { penaMuiTheme } from "../lib/index.ts";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ThemeProvider theme={penaMuiTheme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>,
);

1
src/vite-env.d.ts vendored

@ -1 +0,0 @@
/// <reference types="vite/client" />

0
themes/theme.tsx Normal file

@ -1,34 +0,0 @@
{
"compilerOptions": {
"types": ["vite/client"],
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src",
"lib",
"env.d.ts"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

@ -1,29 +0,0 @@
import react from '@vitejs/plugin-react';
import { resolve } from "path";
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), dts({ include: ["lib"] })],
build: {
lib: {
entry: resolve(__dirname, "lib/index.ts"),
formats: ["es"],
fileName: "index"
},
copyPublicDir: false,
rollupOptions: {
external: [
"@emotion/react",
"@emotion/styled",
"@mui/icons-material",
"@mui/material",
"axios",
"react-router-dom",
"react",
"zustand",
],
},
}
});