Merge branch 'staging'
This commit is contained in:
commit
ac297876d3
@ -2,9 +2,8 @@ name: Deploy
|
|||||||
run-name: ${{ gitea.actor }} build image and push to container registry
|
run-name: ${{ gitea.actor }} build image and push to container registry
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
registry_package:
|
||||||
branches:
|
types: [published]
|
||||||
- 'main'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CreateImage:
|
CreateImage:
|
||||||
|
@ -2,25 +2,30 @@ name: Deploy
|
|||||||
run-name: ${{ gitea.actor }} build image and push to container registry
|
run-name: ${{ gitea.actor }} build image and push to container registry
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
registry_package:
|
||||||
branches:
|
types: [published]
|
||||||
- 'staging'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CreateImage:
|
# CreateImage:
|
||||||
runs-on: [hubstaging]
|
# runs-on: [skeris]
|
||||||
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
|
# uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
|
||||||
with:
|
# with:
|
||||||
runner: hubstaging
|
# runner: skeris
|
||||||
secrets:
|
# secrets:
|
||||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
# REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
# REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
DeployService:
|
DeployService:
|
||||||
|
if: contains(github.event.package.name, 'staging')
|
||||||
runs-on: [frontstaging]
|
runs-on: [frontstaging]
|
||||||
needs: CreateImage
|
container:
|
||||||
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7
|
image: gitea.pena:3000/penadevops/container-images/node-compose:main
|
||||||
with:
|
env:
|
||||||
runner: frontstaging
|
GITHUB_RUN_NUMBER: "${{ inputs.actionid }}"
|
||||||
actionid: ${{ gitea.run_id }}
|
volumes:
|
||||||
|
- /run/user/1000/docker/docker.sock:/run/user/1000/docker/docker.sock
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: http://gitea.pena:3000/PenaDevops/actions.git/checkout@v1
|
||||||
|
- run: compose -f deployments/staging/docker-compose.yaml up -d
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,3 +2,4 @@
|
|||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
yarn eslint . --fix
|
yarn eslint . --fix
|
||||||
|
yarn type-check
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
FROM gitea.pena/penadevops/container-images/node:main as build
|
FROM docker.io/library/node:lts-alpine as build
|
||||||
|
|
||||||
RUN apk update && rm -rf /var/cache/apk/*
|
RUN apk update && rm -rf /var/cache/apk/*
|
||||||
WORKDIR /usr/app
|
WORKDIR /usr/app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN npm install --force && yarn cache clean
|
RUN npm install --force && yarn cache clean
|
||||||
RUN psstat.sh "npm run build"
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
FROM gitea.pena/penadevops/container-images/nginx:main as result
|
FROM gitea.pena/penadevops/container-images/nginx:main as result
|
@ -3,6 +3,7 @@ services:
|
|||||||
container_name: hub
|
container_name: hub
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
hostname: hub
|
hostname: hub
|
||||||
image: gitea.pena/penaside/front-hub/main:1118
|
image: gitea.pena/penaside/front-hub/main:$latest
|
||||||
tty: true
|
tty: true
|
||||||
|
pull_policy: always
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ services:
|
|||||||
hub:
|
hub:
|
||||||
container_name: hub
|
container_name: hub
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: gitea.pena/penaside/front-hub/staging:$GITHUB_RUN_NUMBER
|
image: gitea.pena/penaside/front-hub/staging:latest
|
||||||
hostname: hub
|
hostname: hub
|
||||||
tty: true
|
tty: true
|
||||||
|
pull_policy: always
|
||||||
|
|
||||||
|
10
package.json
10
package.json
@ -5,8 +5,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_OPTIONS=\"--max-old-space-size=1024\" craco start",
|
"start": "NODE_OPTIONS=\"--max-old-space-size=1024\" craco start",
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
|
"build:check": "tsc --noEmit && craco build",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"type-check:strict": "tsc --noEmit --strict",
|
||||||
|
"pre-commit": "npm run type-check",
|
||||||
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
|
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
|
||||||
"test:cart": "vitest ./src/utils/calcCart",
|
"test:cart": "vitest ./src/utils/calcCart",
|
||||||
|
"deploy": "docker login gitea.pena && docker build -t gitea.pena/penaside/front-hub/$(git branch --show-current):latest . && docker push gitea.pena/penaside/front-hub/$(git branch --show-current):latest",
|
||||||
"eject": "craco eject",
|
"eject": "craco eject",
|
||||||
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
|
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
|
||||||
"cypress": "cypress open",
|
"cypress": "cypress open",
|
||||||
@ -16,9 +21,10 @@
|
|||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.10.5",
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.10.5",
|
||||||
"@frontend/kitui": "^1.0.108",
|
"@frontend/kitui": "^1.0.110",
|
||||||
"@mui/icons-material": "^5.10.14",
|
"@mui/icons-material": "^5.10.14",
|
||||||
"@mui/material": "^5.10.14",
|
"@mui/material": "^5.10.14",
|
||||||
|
"@mui/x-date-pickers": "^7.13.0",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
@ -29,6 +35,7 @@
|
|||||||
"immer": "^10.0.2",
|
"immer": "^10.0.2",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"js-big-decimal": "^2.0.7",
|
"js-big-decimal": "^2.0.7",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"pdfjs-dist": "3.6.172",
|
"pdfjs-dist": "3.6.172",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -39,6 +46,7 @@
|
|||||||
"react-slick": "^0.30.3",
|
"react-slick": "^0.30.3",
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
|
"transliteration": "^2.3.5",
|
||||||
"use-debounce": "^10.0.0",
|
"use-debounce": "^10.0.0",
|
||||||
"web-vitals": "^2.1.0",
|
"web-vitals": "^2.1.0",
|
||||||
"yup": "^1.1.1",
|
"yup": "^1.1.1",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import type {
|
|||||||
type RecoverResponse = {
|
type RecoverResponse = {
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
//tsignore
|
||||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/auth`;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/auth`;
|
||||||
|
|
||||||
export const register = async (
|
export const register = async (
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { UserAccount } from "@frontend/kitui";
|
import { UserAccount } from "@frontend/kitui";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
import Cart from "@root/pages/Cart/Cart";
|
||||||
|
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`;
|
||||||
|
|
||||||
export const patchCart = async (
|
export const patchCart = async (
|
||||||
tariffId: string
|
tariffId: string
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Tariff } from "@frontend/kitui";
|
import { Tariff } from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
export interface GetHistoryResponse {
|
export interface GetHistoryResponse {
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
@ -44,7 +44,7 @@ export type RawDetails = {
|
|||||||
Value: string | number | KeyValue[][];
|
Value: string | number | KeyValue[][];
|
||||||
};
|
};
|
||||||
|
|
||||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`;
|
||||||
|
|
||||||
export const getHistory = async (): Promise<
|
export const getHistory = async (): Promise<
|
||||||
[GetHistoryResponse | GetHistoryResponse2 | null, string?]
|
[GetHistoryResponse | GetHistoryResponse2 | null, string?]
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import * as KIT from "@frontend/kitui";
|
|
||||||
import { Method, ResponseType, AxiosError } from "axios";
|
|
||||||
import { clearAuthToken } from "@frontend/kitui";
|
|
||||||
import { clearUserData } from "@root/stores/user";
|
|
||||||
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
|
||||||
import { clearTickets } from "@root/stores/tickets";
|
|
||||||
import { redirect } from "react-router-dom";
|
|
||||||
import { setNotEnoughMoneyAmount } from "@stores/cart";
|
|
||||||
|
|
||||||
interface MakeRequest {
|
|
||||||
method?: Method | undefined;
|
|
||||||
url: string;
|
|
||||||
body?: unknown;
|
|
||||||
useToken?: boolean | undefined;
|
|
||||||
contentType?: boolean | undefined;
|
|
||||||
responseType?: ResponseType | undefined;
|
|
||||||
signal?: AbortSignal | undefined;
|
|
||||||
withCredentials?: boolean | undefined;
|
|
||||||
}
|
|
||||||
interface ErrorResponseData {
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeRequest<TRequest = unknown, TResponse = unknown>(
|
|
||||||
data: MakeRequest
|
|
||||||
): Promise<TResponse> {
|
|
||||||
try {
|
|
||||||
const response = await KIT.makeRequest<unknown>(data);
|
|
||||||
|
|
||||||
return response as TResponse;
|
|
||||||
} catch (e) {
|
|
||||||
const error = e as AxiosError;
|
|
||||||
if (
|
|
||||||
error.response?.status === 400 &&
|
|
||||||
(error.response?.data as ErrorResponseData)?.message ===
|
|
||||||
"refreshToken is empty"
|
|
||||||
) {
|
|
||||||
clearAuthToken();
|
|
||||||
clearUserData();
|
|
||||||
clearCustomTariffs();
|
|
||||||
clearTickets();
|
|
||||||
setNotEnoughMoneyAmount(0);
|
|
||||||
redirect("/");
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default makeRequest;
|
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import type { GetDiscountsResponse } from "@root/model/discount";
|
import type { GetDiscountsResponse } from "@root/model/discount";
|
||||||
import { useUserStore } from "@root/stores/user";
|
import { useUserStore } from "@root/stores/user";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
import { parseAxiosError } from "@utils/parse-error";
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`;
|
||||||
|
|
||||||
type GetRecentlyPurchasedTariffsResponse = {
|
type GetRecentlyPurchasedTariffsResponse = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { Tariff } from "@frontend/kitui";
|
import { Tariff } from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
} from "@root/model/privilege";
|
} from "@root/model/privilege";
|
||||||
import type { GetTariffsResponse } from "@root/model/tariff";
|
import type { GetTariffsResponse } from "@root/model/tariff";
|
||||||
import { removeTariffFromCart } from "@root/stores/user";
|
import { removeTariffFromCart } from "@root/stores/user";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
interface CreateTariffBody {
|
interface CreateTariffBody {
|
||||||
name: string;
|
name: string;
|
||||||
@ -134,3 +135,24 @@ export const getTariffArray = async (tariffIds: string[] | undefined) => {
|
|||||||
|
|
||||||
return tariffs;
|
return tariffs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/requestquiz";
|
||||||
|
export async function sendContactFormRequest(body: {
|
||||||
|
contact: string;
|
||||||
|
whoami: string;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const a = await axios(apiUrl + "/callme", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
return [a];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Ошибка при отправке запроса. ${error}`];
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
import { createTicket as createTicketRequest } from "@frontend/kitui";
|
import { createTicket as createTicketRequest } from "@frontend/kitui";
|
||||||
import type { CreateTicketResponse } from "@frontend/kitui";
|
import type { CreateTicketResponse } from "@frontend/kitui";
|
||||||
@ -78,19 +78,23 @@ export const sendFile = async (
|
|||||||
export const createTicket = async (
|
export const createTicket = async (
|
||||||
ticketNameField: string,
|
ticketNameField: string,
|
||||||
ticketBodyField: string,
|
ticketBodyField: string,
|
||||||
isToken?: boolean
|
isToken: boolean = true
|
||||||
): Promise<[CreateTicketResponse | null, string?]> => {
|
): Promise<[CreateTicketResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const createTicketResponse = await createTicketRequest({
|
const createTicketResponse = await makeRequest<
|
||||||
|
{ Title: string; Message: string; System: boolean },
|
||||||
|
CreateTicketResponse
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
url: `${API_URL}/create`,
|
url: `${API_URL}/create`,
|
||||||
body: { Title: ticketNameField, Message: ticketBodyField, System: false },
|
body: { Title: ticketNameField, Message: ticketBodyField, System: false },
|
||||||
useToken: isToken
|
useToken: isToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [createTicketResponse];
|
return [createTicketResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null, `Не удалось отправить файл. ${error}`];
|
return [null, `Не удалось создать тикет. ${error}`];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { User } from "@frontend/kitui";
|
import { User } from "@frontend/kitui";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { PatchUserRequest } from "@root/model/user";
|
import { PatchUserRequest } from "@root/model/user";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
import { jsonToFormdata } from "@root/utils/jsonToFormdata";
|
import { jsonToFormdata } from "@root/utils/jsonToFormdata";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
@ -55,7 +55,7 @@ export const sendDocuments = async (
|
|||||||
const sendDocumentsResponse = await makeRequest<FormData, Verification>({
|
const sendDocumentsResponse = await makeRequest<FormData, Verification>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: API_URL,
|
url: API_URL,
|
||||||
body: jsonToFormdata({ ...documents, egrule: documents.inn }),
|
body: jsonToFormdata({ ...documents}),
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
@ -76,7 +76,7 @@ export const updateDocuments = async (
|
|||||||
method: "PUT",
|
method: "PUT",
|
||||||
url: `${API_URL}`,
|
url: `${API_URL}`,
|
||||||
body: jsonToFormdata(
|
body: jsonToFormdata(
|
||||||
documents.inn ? { ...documents, egrule: documents.inn } : documents
|
documents.inn ? { ...documents} : documents
|
||||||
),
|
),
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
|
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
@ -6,8 +6,12 @@ const isStaging = (() => {
|
|||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
return host.includes("s") ? "s" : "";
|
return host.includes("s") ? "s" : "";
|
||||||
})();
|
})();
|
||||||
|
const isLocalhost = (() => {
|
||||||
|
const host = window.location.hostname;
|
||||||
|
return host.includes("localhost");
|
||||||
|
})();
|
||||||
|
|
||||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`;
|
||||||
|
|
||||||
interface PaymentBody {
|
interface PaymentBody {
|
||||||
type: string;
|
type: string;
|
||||||
@ -16,28 +20,31 @@ interface PaymentBody {
|
|||||||
|
|
||||||
export const sendPayment = async ({
|
export const sendPayment = async ({
|
||||||
userId,
|
userId,
|
||||||
wayback,
|
|
||||||
body,
|
body,
|
||||||
fromSquiz,
|
action,
|
||||||
paymentPurpose,
|
fromDomain,
|
||||||
|
backWay,
|
||||||
|
additionalinformation,
|
||||||
}: {
|
}: {
|
||||||
userId: string;
|
userId: string;
|
||||||
wayback: string;
|
|
||||||
body: PaymentBody;
|
body: PaymentBody;
|
||||||
fromSquiz: boolean;
|
action?: string;
|
||||||
paymentPurpose: "paycart" | "replenishwallet";
|
fromDomain?: string;
|
||||||
|
backWay?: string;
|
||||||
|
additionalinformation?: string;
|
||||||
}): Promise<[SendPaymentResponse | null, string?]> => {
|
}): Promise<[SendPaymentResponse | null, string?]> => {
|
||||||
|
|
||||||
console.log(" я sendPayment и вот мой wayback = " + wayback)
|
|
||||||
|
|
||||||
let returnUrl= `https://${isStaging}hub.pena.digital/afterpay?from=${
|
// Правильно формируем returnUrl
|
||||||
fromSquiz ? "quiz" : "hub"
|
const domain = fromDomain || `${isStaging ? "s" : ""}hub.pena.digital`;
|
||||||
}&purpose=${paymentPurpose}&userid=${userId}`
|
const path = backWay ? `/${backWay}` : '';
|
||||||
|
let returnUrl = `https://${domain}${path}?userid=${userId}&action=${action || 'buy'}`;
|
||||||
|
|
||||||
if (wayback) returnUrl += `&wayback=${wayback}`
|
// Добавляем additionalinformation если он есть
|
||||||
|
if (additionalinformation) {
|
||||||
|
returnUrl += `&additionalinformation=${encodeURIComponent(additionalinformation)}`;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("returnUrl")
|
|
||||||
console.log(returnUrl)
|
|
||||||
|
|
||||||
const reqeustBody = {
|
const reqeustBody = {
|
||||||
currency: "RUB",
|
currency: "RUB",
|
||||||
|
38
src/assets/Icons/CloseIcon.tsx
Normal file
38
src/assets/Icons/CloseIcon.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
export default function CloseIcon({ sx }: Props) {
|
||||||
|
const location = useLocation();
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "30px",
|
||||||
|
height: "30px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexShrink: 0,
|
||||||
|
"&:hover path": {
|
||||||
|
stroke: "#7E2AEA",
|
||||||
|
},
|
||||||
|
...sx
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="26"
|
||||||
|
height="26"
|
||||||
|
viewBox="0 0 26 26"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1 1L25 25M1 25L25 1"
|
||||||
|
stroke={location.pathname === "/" ? "white" : "black"}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
22
src/assets/Icons/NotebookWithPencil copy.tsx
Normal file
22
src/assets/Icons/NotebookWithPencil copy.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
export default function NotebookWithPencil({ sx }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "24px",
|
||||||
|
...sx
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.88766 6.5124H15.3537M5.88766 10.4075H12.0172M5.88766 14.398H9.18792M18.988 4.41492V3.69678C18.988 3.14449 18.5403 2.69678 17.988 2.69678H3.19922C2.64693 2.69678 2.19922 3.14449 2.19922 3.69678V19.7038C2.19922 20.5407 3.16577 21.0074 3.8211 20.4869L6.25047 18.5577C6.42733 18.4173 6.64652 18.3408 6.87236 18.3408H17.988C18.5403 18.3408 18.988 17.8931 18.988 17.3408V15.2147" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" />
|
||||||
|
<path d="M20.383 7.29034L20.9133 6.76001L20.383 6.22968L19.8527 6.76001L20.383 7.29034ZM15.9919 11.6815L15.4615 11.1511L14.9312 11.6815L15.4615 12.2118L15.9919 11.6815ZM16.7185 12.4081L16.1882 12.9384L16.7185 13.4688L17.2489 12.9384L16.7185 12.4081ZM21.1096 8.01698L21.64 8.54731L22.1703 8.01698L21.64 7.48665L21.1096 8.01698ZM14.4001 13.2732L14.9304 12.7429L14.4001 12.2126L13.8698 12.7429L14.4001 13.2732ZM14.0817 13.5917L13.5514 13.0613C13.5011 13.1115 13.4583 13.1686 13.4242 13.2309L14.0817 13.5917ZM13.1982 15.2018L12.5407 14.841C12.3803 15.1333 12.4321 15.4964 12.6678 15.7321C12.9036 15.9679 13.2667 16.0197 13.559 15.8593L13.1982 15.2018ZM14.8083 14.3183L15.1691 14.9758C15.2314 14.9417 15.2885 14.8988 15.3387 14.8486L14.8083 14.3183ZM15.1267 13.9999L15.6571 14.5302L16.1874 13.9999L15.6571 13.4696L15.1267 13.9999ZM21.9075 5.76585L21.3772 5.23552L20.8468 5.76585L21.3772 6.29618L21.9075 5.76585ZM22.2812 5.39209L22.8116 4.86176C22.5187 4.56887 22.0438 4.56887 21.7509 4.86176L22.2812 5.39209ZM23.0079 6.11874L23.5382 6.64907C23.8311 6.35617 23.8311 5.8813 23.5382 5.58841L23.0079 6.11874ZM22.6341 6.4925L22.1038 7.02283L22.6341 7.55316L23.1645 7.02283L22.6341 6.4925ZM19.8527 6.76001L15.4615 11.1511L16.5222 12.2118L20.9133 7.82067L19.8527 6.76001ZM15.4615 12.2118L16.1882 12.9384L17.2489 11.8778L16.5222 11.1511L15.4615 12.2118ZM17.2489 12.9384L21.64 8.54731L20.5793 7.48665L16.1882 11.8778L17.2489 12.9384ZM21.64 7.48665L20.9133 6.76001L19.8527 7.82067L20.5793 8.54731L21.64 7.48665ZM13.8698 12.7429L13.5514 13.0613L14.612 14.122L14.9304 13.8036L13.8698 12.7429ZM13.4242 13.2309L12.5407 14.841L13.8557 15.5626L14.7392 13.9524L13.4242 13.2309ZM13.559 15.8593L15.1691 14.9758L14.4475 13.6608L12.8374 14.5443L13.559 15.8593ZM15.3387 14.8486L15.6571 14.5302L14.5964 13.4696L14.278 13.788L15.3387 14.8486ZM15.6571 13.4696L14.9304 12.7429L13.8698 13.8036L14.5964 14.5302L15.6571 13.4696ZM22.4378 6.29618L22.8116 5.92242L21.7509 4.86176L21.3772 5.23552L22.4378 6.29618ZM21.7509 5.92242L22.4776 6.64907L23.5382 5.58841L22.8116 4.86176L21.7509 5.92242ZM22.4776 5.58841L22.1038 5.96217L23.1645 7.02283L23.5382 6.64907L22.4776 5.58841ZM23.1645 5.96217L22.4378 5.23552L21.3772 6.29618L22.1038 7.02283L23.1645 5.96217Z" fill="#7E2AEA" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
20
src/assets/Icons/NotebookWithPencil.tsx
Normal file
20
src/assets/Icons/NotebookWithPencil.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
export default function CloseIcon({ sx }: Props) {
|
||||||
|
const location = useLocation();
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={sx}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.88766 6.5124H15.3537M5.88766 10.4075H12.0172M5.88766 14.398H9.18792M18.988 4.41492V3.69678C18.988 3.14449 18.5403 2.69678 17.988 2.69678H3.19922C2.64693 2.69678 2.19922 3.14449 2.19922 3.69678V19.7038C2.19922 20.5407 3.16577 21.0074 3.8211 20.4869L6.25047 18.5577C6.42733 18.4173 6.64652 18.3408 6.87236 18.3408H17.988C18.5403 18.3408 18.988 17.8931 18.988 17.3408V15.2147" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" />
|
||||||
|
<path d="M20.383 7.29034L20.9133 6.76001L20.383 6.22968L19.8527 6.76001L20.383 7.29034ZM15.9919 11.6815L15.4615 11.1511L14.9312 11.6815L15.4615 12.2118L15.9919 11.6815ZM16.7185 12.4081L16.1882 12.9384L16.7185 13.4688L17.2489 12.9384L16.7185 12.4081ZM21.1096 8.01698L21.64 8.54731L22.1703 8.01698L21.64 7.48665L21.1096 8.01698ZM14.4001 13.2732L14.9304 12.7429L14.4001 12.2126L13.8698 12.7429L14.4001 13.2732ZM14.0817 13.5917L13.5514 13.0613C13.5011 13.1115 13.4583 13.1686 13.4242 13.2309L14.0817 13.5917ZM13.1982 15.2018L12.5407 14.841C12.3803 15.1333 12.4321 15.4964 12.6678 15.7321C12.9036 15.9679 13.2667 16.0197 13.559 15.8593L13.1982 15.2018ZM14.8083 14.3183L15.1691 14.9758C15.2314 14.9417 15.2885 14.8988 15.3387 14.8486L14.8083 14.3183ZM15.1267 13.9999L15.6571 14.5302L16.1874 13.9999L15.6571 13.4696L15.1267 13.9999ZM21.9075 5.76585L21.3772 5.23552L20.8468 5.76585L21.3772 6.29618L21.9075 5.76585ZM22.2812 5.39209L22.8116 4.86176C22.5187 4.56887 22.0438 4.56887 21.7509 4.86176L22.2812 5.39209ZM23.0079 6.11874L23.5382 6.64907C23.8311 6.35617 23.8311 5.8813 23.5382 5.58841L23.0079 6.11874ZM22.6341 6.4925L22.1038 7.02283L22.6341 7.55316L23.1645 7.02283L22.6341 6.4925ZM19.8527 6.76001L15.4615 11.1511L16.5222 12.2118L20.9133 7.82067L19.8527 6.76001ZM15.4615 12.2118L16.1882 12.9384L17.2489 11.8778L16.5222 11.1511L15.4615 12.2118ZM17.2489 12.9384L21.64 8.54731L20.5793 7.48665L16.1882 11.8778L17.2489 12.9384ZM21.64 7.48665L20.9133 6.76001L19.8527 7.82067L20.5793 8.54731L21.64 7.48665ZM13.8698 12.7429L13.5514 13.0613L14.612 14.122L14.9304 13.8036L13.8698 12.7429ZM13.4242 13.2309L12.5407 14.841L13.8557 15.5626L14.7392 13.9524L13.4242 13.2309ZM13.559 15.8593L15.1691 14.9758L14.4475 13.6608L12.8374 14.5443L13.559 15.8593ZM15.3387 14.8486L15.6571 14.5302L14.5964 13.4696L14.278 13.788L15.3387 14.8486ZM15.6571 13.4696L14.9304 12.7429L13.8698 13.8036L14.5964 14.5302L15.6571 13.4696ZM22.4378 6.29618L22.8116 5.92242L21.7509 4.86176L21.3772 5.23552L22.4378 6.29618ZM21.7509 5.92242L22.4776 6.64907L23.5382 5.58841L22.8116 4.86176L21.7509 5.92242ZM22.4776 5.58841L22.1038 5.96217L23.1645 7.02283L23.5382 6.64907L22.4776 5.58841ZM23.1645 5.96217L22.4378 5.23552L21.3772 6.29618L22.1038 7.02283L23.1645 5.96217Z" fill="#7E2AEA" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
152
src/components/CustomTextField.tsx
Executable file
152
src/components/CustomTextField.tsx
Executable file
@ -0,0 +1,152 @@
|
|||||||
|
import type { ChangeEvent, FocusEvent, KeyboardEvent } from "react";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import type { InputProps, SxProps, Theme } from "@mui/material";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
InputLabel,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
interface CustomTextFieldProps {
|
||||||
|
placeholder: string;
|
||||||
|
id?: string;
|
||||||
|
value?: string;
|
||||||
|
error?: string;
|
||||||
|
emptyError?: boolean;
|
||||||
|
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||||
|
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
||||||
|
text?: string;
|
||||||
|
maxLength?: number;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
sxForm?: SxProps<Theme>;
|
||||||
|
InputProps?: Partial<InputProps>;
|
||||||
|
type?: string;
|
||||||
|
rows?: number;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomTextField({
|
||||||
|
placeholder,
|
||||||
|
id,
|
||||||
|
value = "",
|
||||||
|
onChange,
|
||||||
|
onKeyDown,
|
||||||
|
onBlur,
|
||||||
|
text,
|
||||||
|
sx,
|
||||||
|
error,
|
||||||
|
emptyError,
|
||||||
|
InputProps,
|
||||||
|
maxLength = 200,
|
||||||
|
type = "",
|
||||||
|
rows = 0,
|
||||||
|
sxForm,
|
||||||
|
className,
|
||||||
|
disabled,
|
||||||
|
}: CustomTextFieldProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [isInputActive, setIsInputActive] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInputValue(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.value.length <= maxLength) {
|
||||||
|
const inputValue = event.target.value;
|
||||||
|
|
||||||
|
if (type === "number") {
|
||||||
|
setInputValue(inputValue.replace(/\D/g, ""));
|
||||||
|
} else {
|
||||||
|
setInputValue(inputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
onChange(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputFocus = () => {
|
||||||
|
setIsInputActive(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||||
|
setIsInputActive(false);
|
||||||
|
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
sx={{ p: 0, ...sxForm }}
|
||||||
|
className={className || ""}
|
||||||
|
>
|
||||||
|
{error && (
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: "13.5px",
|
||||||
|
marginTop: "3px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</InputLabel>
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
defaultValue={text}
|
||||||
|
fullWidth
|
||||||
|
value={inputValue}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
error={!!error || emptyError}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
multiline={rows > 0}
|
||||||
|
rows={rows}
|
||||||
|
disabled={disabled}
|
||||||
|
disableUnderline
|
||||||
|
sx={{
|
||||||
|
maxLength: maxLength,
|
||||||
|
borderRadius: "10px",
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
p: "13px",
|
||||||
|
border: `${isInputActive ? "black 2px" : "#9A9AAF 1px"} solid`,
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
height: "48px",
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
data-cy="textfield"
|
||||||
|
/>
|
||||||
|
{isInputActive && inputValue.length >= maxLength - 7 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
marginTop: "5px",
|
||||||
|
marginLeft: "auto",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "-25px",
|
||||||
|
right: "0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography fontSize="14px">{inputValue.length}</Typography>
|
||||||
|
<span>/</span>
|
||||||
|
<Typography fontSize="14px">{maxLength}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
@ -13,7 +13,7 @@ import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg";
|
|||||||
|
|
||||||
import type { MouseEvent } from "react";
|
import type { MouseEvent } from "react";
|
||||||
import CustomTariffAccordion from "@root/components/CustomTariffAccordion";
|
import CustomTariffAccordion from "@root/components/CustomTariffAccordion";
|
||||||
import { setNotEnoughMoneyAmount } from "@root/stores/cart";
|
import { setNotEnoughMoneyAmount } from "@root/stores/allTypesOfPurchases";
|
||||||
|
|
||||||
const name: Record<string, string> = {
|
const name: Record<string, string> = {
|
||||||
templategen: "Шаблонизатор",
|
templategen: "Шаблонизатор",
|
||||||
|
@ -19,8 +19,8 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import { withErrorBoundary } from "react-error-boundary";
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
import { handleComponentError } from "@root/utils/handleComponentError";
|
import { handleComponentError } from "@root/utils/handleComponentError";
|
||||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||||
import { setNotEnoughMoneyAmount, useCartStore } from "@root/stores/cart";
|
import { setNotEnoughMoneyAmount, startPayCartProcess, allTypesOfPurchases } from "@root/stores/allTypesOfPurchases";
|
||||||
import { useDiffMoney } from "@root/stores/diffMoney";
|
import { RSCOpen } from "@root/stores/requestSquizCreate";
|
||||||
|
|
||||||
function Drawers() {
|
function Drawers() {
|
||||||
const [openNotificationsModal, setOpenNotificationsModal] = useState<boolean>(false);
|
const [openNotificationsModal, setOpenNotificationsModal] = useState<boolean>(false);
|
||||||
@ -34,23 +34,30 @@ function Drawers() {
|
|||||||
const cart = useCart();
|
const cart = useCart();
|
||||||
console.log("боковой cart", cart.priceAfterDiscounts)
|
console.log("боковой cart", cart.priceAfterDiscounts)
|
||||||
const userAccount = useUserStore((state) => state.userAccount);
|
const userAccount = useUserStore((state) => state.userAccount);
|
||||||
|
const userId = useUserStore((state) => state.userId) || "";
|
||||||
const tickets = useTicketStore((state) => state.tickets);
|
const tickets = useTicketStore((state) => state.tickets);
|
||||||
const notEnoughMoneyAmount = useCartStore(state => state.notEnoughMoneyAmount);
|
const notEnoughMoneyAmount = allTypesOfPurchases(state => state.notEnoughMoneyAmount);
|
||||||
const { setNewDiff } = useDiffMoney()
|
const siteReadyPayCart = allTypesOfPurchases(state => state.siteReadyPayCart)
|
||||||
|
|
||||||
const notificationsCount = tickets.filter(
|
const notificationsCount = tickets.filter(
|
||||||
({ user, top_message }) => user !== top_message.user_id && top_message.shown.me !== 1
|
({ user, top_message }) =>
|
||||||
|
user !== top_message.user_id &&
|
||||||
|
top_message.shown.me !== 1 &&
|
||||||
|
!top_message.system
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
async function handlePayClick() {
|
async function handlePayClick() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const isCC = cart.services.length > 0 && cart.services[0].tariffs.some(t => t.privileges[0].privilegeId === "quizManual")
|
||||||
|
|
||||||
const [payCartResponse, payCartError] = await payCart();
|
const [payCartResponse, payCartError] = await payCart();
|
||||||
|
|
||||||
if (payCartError) {
|
if (payCartError) {
|
||||||
if (payCartError.includes("insufficient funds: ")) {
|
if (payCartError.includes("insufficient funds: ")) {
|
||||||
const notEnoughMoneyAmount = parseInt(payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, ""));
|
const notEnoughMoneyAmount = parseInt(payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, ""));
|
||||||
setNotEnoughMoneyAmount(notEnoughMoneyAmount);
|
setNotEnoughMoneyAmount(notEnoughMoneyAmount);
|
||||||
|
startPayCartProcess(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -60,6 +67,7 @@ function Drawers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payCartResponse) {
|
if (payCartResponse) {
|
||||||
|
if (isCC) RSCOpen()
|
||||||
setUserAccount(payCartResponse);
|
setUserAccount(payCartResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,10 +77,8 @@ function Drawers() {
|
|||||||
|
|
||||||
function handleReplenishWallet() {
|
function handleReplenishWallet() {
|
||||||
setIsDrawerOpen(false);
|
setIsDrawerOpen(false);
|
||||||
if (location.pathname.includes("/payment")) {
|
if (siteReadyPayCart === null) startPayCartProcess(userId)
|
||||||
setNewDiff(notEnoughMoneyAmount)
|
navigate("/payment");
|
||||||
}
|
|
||||||
navigate("/payment", { state: { notEnoughMoneyAmount } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -131,7 +137,10 @@ function Drawers() {
|
|||||||
setOpen={setOpenNotificationsModal}
|
setOpen={setOpenNotificationsModal}
|
||||||
anchorElement={bellRef.current}
|
anchorElement={bellRef.current}
|
||||||
notifications={tickets
|
notifications={tickets
|
||||||
.filter(({ user, top_message }) => user !== top_message.user_id)
|
.filter(({ user, top_message }) =>
|
||||||
|
user !== top_message.user_id &&
|
||||||
|
!top_message.system
|
||||||
|
)
|
||||||
.map((ticket) => ({
|
.map((ticket) => ({
|
||||||
text: "У вас новое сообщение от техподдержки",
|
text: "У вас новое сообщение от техподдержки",
|
||||||
date: new Date(ticket.updated_at).toLocaleDateString(),
|
date: new Date(ticket.updated_at).toLocaleDateString(),
|
||||||
@ -180,7 +189,6 @@ function Drawers() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<Drawer anchor={"right"} open={isDrawerOpen} onClose={() => {
|
<Drawer anchor={"right"} open={isDrawerOpen} onClose={() => {
|
||||||
setIsDrawerOpen(false)
|
setIsDrawerOpen(false)
|
||||||
setNotEnoughMoneyAmount(0)
|
|
||||||
}} sx={{ background: "rgba(0, 0, 0, 0.55)" }}>
|
}} sx={{ background: "rgba(0, 0, 0, 0.55)" }}>
|
||||||
<SectionWrapper
|
<SectionWrapper
|
||||||
maxWidth="lg"
|
maxWidth="lg"
|
||||||
@ -248,7 +256,7 @@ function Drawers() {
|
|||||||
Здесь написана окончательная стоимость всех услуг сложенных в корзину с учётом всех скидок.
|
Здесь написана окончательная стоимость всех услуг сложенных в корзину с учётом всех скидок.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color={theme.palette.gray.dark}>
|
<Typography color={theme.palette.gray.dark}>
|
||||||
После нажатия кнопки оплатить, вы будете перенаправлены на форму оплаты, для оплаты ВСЕЙ корзины (рекомендуем перед оплатой, убрать все лишнее)
|
После нажатия кнопки оплатить (пополнить), вы будете перенаправлены на форму оплаты, для оплаты ВСЕЙ корзины (рекомендуем перед оплатой, убрать все лишнее)
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
@ -109,7 +109,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
const workingHoursMessage =
|
const workingHoursMessage =
|
||||||
"Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
|
"Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
|
||||||
const offHoursMessage =
|
const offHoursMessage =
|
||||||
"Здравствуйте, к сожалению, сейчас операторы не работают. Задайте ваш вопрос, и вам ответят в 10:00 по московскому времени";
|
"Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const currentHourUTC = date.getUTCHours();
|
const currentHourUTC = date.getUTCHours();
|
||||||
const MscTime = 3; // Москва UTC+3;
|
const MscTime = 3; // Москва UTC+3;
|
||||||
@ -129,6 +129,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
shown: { me: 1 },
|
shown: { me: 1 },
|
||||||
ticket_id: "111",
|
ticket_id: "111",
|
||||||
user_id: "greetingMessage",
|
user_id: "greetingMessage",
|
||||||
|
system: true,
|
||||||
};
|
};
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
@ -151,8 +152,6 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
}, []),
|
}, []),
|
||||||
onFetchStateChange: setUnauthTicketMessageFetchState,
|
onFetchStateChange: setUnauthTicketMessageFetchState,
|
||||||
});
|
});
|
||||||
console.log("sessionData")
|
|
||||||
console.log(sessionData)
|
|
||||||
useSSESubscription<TicketMessage>({
|
useSSESubscription<TicketMessage>({
|
||||||
enabled: sseEnabled && isActiveSSETab && Boolean(sessionData),
|
enabled: sseEnabled && isActiveSSETab && Boolean(sessionData),
|
||||||
url:
|
url:
|
||||||
@ -160,10 +159,6 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
`/heruvym/v1.0.0/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
`/heruvym/v1.0.0/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
||||||
|
|
||||||
onNewData: (ticketMessages) => {
|
onNewData: (ticketMessages) => {
|
||||||
console.log("Chat")
|
|
||||||
|
|
||||||
console.log("ticketMessages useSSESubscription ")
|
|
||||||
console.log(ticketMessages)
|
|
||||||
const isTicketClosed = ticketMessages.some(
|
const isTicketClosed = ticketMessages.some(
|
||||||
(message) => message.session_id === "close"
|
(message) => message.session_id === "close"
|
||||||
);
|
);
|
||||||
@ -176,13 +171,10 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("under checking some close message -----------------------------------------")
|
|
||||||
|
|
||||||
updateSSEValue(ticketMessages);
|
updateSSEValue(ticketMessages);
|
||||||
addOrUpdateUnauthMessages(ticketMessages);
|
addOrUpdateUnauthMessages(ticketMessages);
|
||||||
},
|
},
|
||||||
onDisconnect: useCallback(() => {
|
onDisconnect: useCallback(() => {
|
||||||
console.log("DISCONNECT")
|
|
||||||
setUnauthIsPreventAutoscroll(false);
|
setUnauthIsPreventAutoscroll(false);
|
||||||
setSseEnabled(false);
|
setSseEnabled(false);
|
||||||
}, []),
|
}, []),
|
||||||
@ -258,41 +250,42 @@ console.log("under checking some close message ---------------------------------
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && messages.length > 1) {
|
if (open && messages.length > 1) {
|
||||||
|
// Ищем последнее непрочитанное сообщение НЕ от текущего пользователя
|
||||||
let lastUnreadMessage = null;
|
let lastUnreadMessage = null;
|
||||||
|
|
||||||
// Ищем последнее непрочитанное чужое сообщение до первого прочитанного или своего
|
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const message = messages[i];
|
const message = messages[i];
|
||||||
const isOwnMessage = (ticket.sessionData?.sessionId || user) === message.user_id;
|
|
||||||
|
|
||||||
// Пропускаем системные сообщения
|
// Пропускаем системные сообщения (приветствие)
|
||||||
if (message.ticket_id === "111") {
|
if (message.ticket_id === "111" || message.system) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если встретили своё сообщение - прерываем поиск
|
// Определяем, является ли сообщение от текущего пользователя
|
||||||
|
const currentUserId = user || ticket.sessionData?.sessionId;
|
||||||
|
const isOwnMessage = currentUserId === message.user_id;
|
||||||
|
|
||||||
|
// Если сообщение от нас - пропускаем
|
||||||
if (isOwnMessage) {
|
if (isOwnMessage) {
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если встретили прочитанное сообщение - прерываем поиск
|
// Если сообщение уже прочитано - пропускаем
|
||||||
if (message.shown?.me === 1) {
|
if (message.shown?.me === 1) {
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если сообщение чужое и не прочитано - запоминаем его
|
// Нашли непрочитанное сообщение не от нас - запоминаем его
|
||||||
if (!isOwnMessage && message.shown?.me !== 1) {
|
|
||||||
lastUnreadMessage = message;
|
lastUnreadMessage = message;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Если нашли непрочитанное сообщение - отмечаем его как прочитанное
|
// Отправляем shown только на последнее непрочитанное сообщение не от нас
|
||||||
if (lastUnreadMessage) {
|
if (lastUnreadMessage) {
|
||||||
shownMessage(lastUnreadMessage.id);
|
shownMessage(lastUnreadMessage.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [open, messages]);
|
}, [open, messages, user, ticket.sessionData?.sessionId]);
|
||||||
|
|
||||||
const loadNewMessages = (
|
const loadNewMessages = (
|
||||||
event: WheelEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>
|
event: WheelEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>
|
||||||
@ -463,14 +456,6 @@ console.log("under checking some close message ---------------------------------
|
|||||||
>
|
>
|
||||||
онлайн-консультант
|
онлайн-консультант
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
время работы 10:00-3:00 по мск
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
@ -42,7 +42,7 @@ export default function FloatingSupportChat() {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
||||||
const user = useUserStore((state) => state.user?._id);
|
const user = useUserStore((state) => state.user?._id);
|
||||||
const { messages } = useTicketStore(
|
const { messages, sessionData } = useTicketStore(
|
||||||
(state) => state[user ? "authData" : "unauthData"]
|
(state) => state[user ? "authData" : "unauthData"]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -72,17 +72,44 @@ export default function FloatingSupportChat() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const unreadMessagesCount = useMemo(() => {
|
const unreadMessagesCount = useMemo(() => {
|
||||||
let count = 0;
|
if (messages.length === 0) return 0;
|
||||||
|
|
||||||
// Идём с конца массива к началу
|
// Получаем последнее сообщение
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
|
||||||
|
// Определяем, является ли сообщение от текущего пользователя
|
||||||
|
const currentUserId = user || sessionData?.sessionId;
|
||||||
|
const isLastMessageOwn = currentUserId === lastMessage.user_id;
|
||||||
|
|
||||||
|
// Если последнее сообщение моё - не показываем количество
|
||||||
|
if (isLastMessageOwn) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если последнее сообщение не моё, но прочитано - не показываем количество
|
||||||
|
if (lastMessage.shown?.me === 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если последнее сообщение не моё и не прочитано - считаем его +1
|
||||||
|
let count = 1;
|
||||||
|
|
||||||
|
// Считаем все предыдущие непрочитанные сообщения до своего
|
||||||
|
for (let i = messages.length - 2; i >= 0; i--) {
|
||||||
const message = messages[i];
|
const message = messages[i];
|
||||||
|
|
||||||
// Пропускаем сообщение с id "111"
|
// Пропускаем системные сообщения
|
||||||
if (message.id === "111") continue;
|
if (message.id === "111" || message.system) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Если сообщение не прочитано (shown.me !== 1)
|
// Если встретили своё сообщение - прекращаем подсчёт
|
||||||
if (message.shown.me !== 1) {
|
if (currentUserId === message.user_id) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если сообщение не прочитано - увеличиваем счетчик
|
||||||
|
if (message.shown?.me !== 1) {
|
||||||
count++;
|
count++;
|
||||||
} else {
|
} else {
|
||||||
// Встретили прочитанное сообщение - прекращаем подсчёт
|
// Встретили прочитанное сообщение - прекращаем подсчёт
|
||||||
@ -91,7 +118,7 @@ export default function FloatingSupportChat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}, [messages]); // Зависимость от messages - пересчитывается при их изменении
|
}, [messages, user, sessionData?.sessionId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
|
50
src/components/InfoButton.tsx
Normal file
50
src/components/InfoButton.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { IconButton, SxProps } from "@mui/material";
|
||||||
|
|
||||||
|
type InfoProps = {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
sx?: SxProps;
|
||||||
|
onClick?: any;
|
||||||
|
className?: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function InfoButton({
|
||||||
|
width = 20,
|
||||||
|
height = 20,
|
||||||
|
sx,
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
color = "#7e2aea",
|
||||||
|
}: InfoProps) {
|
||||||
|
return (
|
||||||
|
<IconButton sx={sx} className={className} onClick={onClick}>
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.25 9.25H10V14.5H10.75"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.8125 7C10.4338 7 10.9375 6.49632 10.9375 5.875C10.9375 5.25368 10.4338 4.75 9.8125 4.75C9.19118 4.75 8.6875 5.25368 8.6875 5.875C8.6875 6.49632 9.19118 7 9.8125 7Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
@ -28,7 +28,8 @@ import {
|
|||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
||||||
import { clearTickets } from "@root/stores/tickets";
|
import { clearTickets } from "@root/stores/tickets";
|
||||||
import {setNotEnoughMoneyAmount} from "@stores/cart"
|
import {setNotEnoughMoneyAmount} from "@stores/allTypesOfPurchases"
|
||||||
|
import { logoutAndRedirect } from "@root/utils/logout";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
@ -44,15 +45,8 @@ export default function NavbarFull({ isLoggedIn }: Props) {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
async function handleLogoutClick() {
|
||||||
clearAuthToken();
|
logoutAndRedirect(navigate);
|
||||||
clearUserData();
|
|
||||||
clearCustomTariffs();
|
|
||||||
clearTickets();
|
|
||||||
setNotEnoughMoneyAmount(0)
|
|
||||||
navigate("/");
|
|
||||||
|
|
||||||
const [_, logoutError] = await logout();
|
const [_, logoutError] = await logout();
|
||||||
|
|
||||||
if (logoutError) {
|
if (logoutError) {
|
||||||
return enqueueSnackbar(logoutError);
|
return enqueueSnackbar(logoutError);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@ import { logout } from "@root/api/auth";
|
|||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
||||||
import { clearTickets } from "@root/stores/tickets";
|
import { clearTickets } from "@root/stores/tickets";
|
||||||
import {setNotEnoughMoneyAmount} from "@stores/cart"
|
import {setNotEnoughMoneyAmount} from "@stores/allTypesOfPurchases"
|
||||||
|
import { logoutAndRedirect } from "@root/utils/logout";
|
||||||
|
|
||||||
type MenuItem = {
|
type MenuItem = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -66,15 +67,8 @@ export default function DialogMenu({ handleClose }: DialogMenuProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
async function handleLogoutClick() {
|
||||||
clearAuthToken();
|
logoutAndRedirect(navigate);
|
||||||
clearUserData();
|
|
||||||
clearCustomTariffs();
|
|
||||||
clearTickets();
|
|
||||||
setNotEnoughMoneyAmount(0)
|
|
||||||
navigate("/");
|
|
||||||
|
|
||||||
const [_, logoutError] = await logout();
|
const [_, logoutError] = await logout();
|
||||||
|
|
||||||
if (logoutError) {
|
if (logoutError) {
|
||||||
return enqueueSnackbar(logoutError);
|
return enqueueSnackbar(logoutError);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,9 @@ export default function NavbarCollapsed({ children }: Props) {
|
|||||||
|
|
||||||
const notificationsCount = tickets.filter(
|
const notificationsCount = tickets.filter(
|
||||||
({ user, top_message }) =>
|
({ user, top_message }) =>
|
||||||
user !== top_message.user_id && top_message.shown.me !== 1
|
user !== top_message.user_id &&
|
||||||
|
top_message.shown.me !== 1 &&
|
||||||
|
!top_message.system
|
||||||
).length
|
).length
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -186,7 +188,10 @@ export default function NavbarCollapsed({ children }: Props) {
|
|||||||
setOpen={setOpenNotificationsModal}
|
setOpen={setOpenNotificationsModal}
|
||||||
anchorElement={bellRef.current}
|
anchorElement={bellRef.current}
|
||||||
notifications={tickets
|
notifications={tickets
|
||||||
.filter(({ user, top_message }) => user !== top_message.user_id)
|
.filter(({ user, top_message }) =>
|
||||||
|
user !== top_message.user_id &&
|
||||||
|
!top_message.system
|
||||||
|
)
|
||||||
.map((ticket) => ({
|
.map((ticket) => ({
|
||||||
text: "У вас новое сообщение от техподдержки",
|
text: "У вас новое сообщение от техподдержки",
|
||||||
date: new Date(ticket.updated_at).toLocaleDateString(),
|
date: new Date(ticket.updated_at).toLocaleDateString(),
|
||||||
|
@ -17,7 +17,8 @@ import { currencyFormatter } from "@root/utils/currencyFormatter";
|
|||||||
import { clearTickets } from "@root/stores/tickets";
|
import { clearTickets } from "@root/stores/tickets";
|
||||||
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import {setNotEnoughMoneyAmount} from "@stores/cart"
|
import {setNotEnoughMoneyAmount} from "@stores/allTypesOfPurchases"
|
||||||
|
import { logoutAndRedirect } from "@root/utils/logout";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -30,15 +31,8 @@ export default function NavbarFull({ children }: Props) {
|
|||||||
const initials = useUserStore((state) => state.initials);
|
const initials = useUserStore((state) => state.initials);
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
async function handleLogoutClick() {
|
||||||
clearAuthToken();
|
logoutAndRedirect(navigate);
|
||||||
clearUserData();
|
|
||||||
clearCustomTariffs();
|
|
||||||
clearTickets();
|
|
||||||
setNotEnoughMoneyAmount(0)
|
|
||||||
navigate("/");
|
|
||||||
|
|
||||||
const [_, logoutError] = await logout();
|
const [_, logoutError] = await logout();
|
||||||
|
|
||||||
if (logoutError) {
|
if (logoutError) {
|
||||||
return enqueueSnackbar(logoutError);
|
return enqueueSnackbar(logoutError);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ import { clearTickets } from "@root/stores/tickets";
|
|||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
|
||||||
import walletIcon from "@root/assets/Icons/wallet_icon.svg";
|
import walletIcon from "@root/assets/Icons/wallet_icon.svg";
|
||||||
import {setNotEnoughMoneyAmount} from "@stores/cart"
|
import {setNotEnoughMoneyAmount} from "@stores/allTypesOfPurchases"
|
||||||
|
import { logoutAndRedirect } from "@root/utils/logout";
|
||||||
|
|
||||||
export const NavbarPanel = () => {
|
export const NavbarPanel = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -28,15 +29,8 @@ export const NavbarPanel = () => {
|
|||||||
const initials = useUserStore((state) => state.initials);
|
const initials = useUserStore((state) => state.initials);
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
async function handleLogoutClick() {
|
||||||
clearAuthToken();
|
logoutAndRedirect(navigate);
|
||||||
clearUserData();
|
|
||||||
clearCustomTariffs();
|
|
||||||
clearTickets();
|
|
||||||
setNotEnoughMoneyAmount(0)
|
|
||||||
navigate("/");
|
|
||||||
|
|
||||||
const [_, logoutError] = await logout();
|
const [_, logoutError] = await logout();
|
||||||
|
|
||||||
if (logoutError) {
|
if (logoutError) {
|
||||||
return enqueueSnackbar(logoutError);
|
return enqueueSnackbar(logoutError);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ export default function ProtectedLayout() {
|
|||||||
process.env.REACT_APP_DOMAIN +
|
process.env.REACT_APP_DOMAIN +
|
||||||
`/heruvym/v1.0.0/subscribe?Authorization=${token}`,
|
`/heruvym/v1.0.0/subscribe?Authorization=${token}`,
|
||||||
onNewData: (data) => {
|
onNewData: (data) => {
|
||||||
console.log("ProtectedLayout")
|
|
||||||
updateSSEValue(data);
|
updateSSEValue(data);
|
||||||
updateTickets(data.filter((d) => Boolean(d.id)));
|
updateTickets(data.filter((d) => Boolean(d.id)));
|
||||||
setTicketCount(data.length);
|
setTicketCount(data.length);
|
||||||
|
@ -72,10 +72,10 @@ export const Select = ({ items, selectedItem, setSelectedItem }: SelectProps) =>
|
|||||||
className="select"
|
className="select"
|
||||||
value=""
|
value=""
|
||||||
open={opened}
|
open={opened}
|
||||||
|
onClick={() => setOpened((isOpened) => !isOpened)}
|
||||||
MenuProps={{ disablePortal: true }}
|
MenuProps={{ disablePortal: true }}
|
||||||
sx={{ width: "100%",zIndex: 1, }}
|
sx={{ width: "100%",zIndex: 1, }}
|
||||||
onChange={selectItem}
|
onChange={selectItem}
|
||||||
onClick={() => setOpened((isOpened) => !isOpened)}
|
|
||||||
>
|
>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<MenuItem key={item + index} value={index} sx={{ padding: "12px" }}>
|
<MenuItem key={item + index} value={index} sx={{ padding: "12px" }}>
|
||||||
|
@ -7,8 +7,10 @@ import { Loader } from "./Loader";
|
|||||||
|
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
import { payCart } from "@root/api/cart";
|
import { payCart } from "@root/api/cart";
|
||||||
import { setUserAccount } from "@root/stores/user";
|
import { setUserAccount, useUserStore } from "@root/stores/user";
|
||||||
import { setNotEnoughMoneyAmount, useCartStore } from "@root/stores/cart";
|
import { setNotEnoughMoneyAmount, startPayCartProcess, allTypesOfPurchases } from "@root/stores/allTypesOfPurchases";
|
||||||
|
import { RSCOpen } from "@root/stores/requestSquizCreate";
|
||||||
|
import { useCart } from "@root/utils/hooks/useCart";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
priceBeforeDiscounts: number;
|
priceBeforeDiscounts: number;
|
||||||
@ -19,13 +21,16 @@ interface Props {
|
|||||||
export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts, isConstructor = false }: Props) {
|
export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts, isConstructor = false }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const notEnoughMoneyAmount = useCartStore(state => state.notEnoughMoneyAmount);
|
const notEnoughMoneyAmount = allTypesOfPurchases(state => state.notEnoughMoneyAmount);
|
||||||
|
const userId = useUserStore(store => store.userId) || ""
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const cart = useCart();
|
||||||
|
|
||||||
async function handlePayClick() {
|
async function handlePayClick() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const isCC = cart.services.length > 0 && cart.services[0].tariffs.some(t => t.privileges[0].privilegeId === "quizManual")
|
||||||
const [payCartResponse, payCartError] = await payCart();
|
const [payCartResponse, payCartError] = await payCart();
|
||||||
|
|
||||||
if (payCartError) {
|
if (payCartError) {
|
||||||
@ -41,6 +46,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payCartResponse) {
|
if (payCartResponse) {
|
||||||
|
if (isCC) RSCOpen()
|
||||||
setUserAccount(payCartResponse);
|
setUserAccount(payCartResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +54,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleReplenishWallet() {
|
function handleReplenishWallet() {
|
||||||
|
startPayCartProcess(userId)
|
||||||
navigate("/payment", { state: { notEnoughMoneyAmount } });
|
navigate("/payment", { state: { notEnoughMoneyAmount } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import {
|
import {
|
||||||
BrowserRouter,
|
BrowserRouter,
|
||||||
@ -9,10 +8,10 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { CssBaseline, ThemeProvider } from "@mui/material";
|
import { CssBaseline, ThemeProvider } from "@mui/material";
|
||||||
|
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||||
import Faq from "./pages/Faq/Faq";
|
import Faq from "./pages/Faq/Faq";
|
||||||
import Wallet from "./pages/Wallet";
|
import Wallet from "./pages/Wallet";
|
||||||
import Payment from "./pages/Payment/Payment";
|
import Payment from "./pages/Payment/Payment";
|
||||||
import QuizPayment from "./pages/QuizPayment/QuizPayment";
|
|
||||||
import Support from "./pages/Support/Support";
|
import Support from "./pages/Support/Support";
|
||||||
import ChatImageNewWindow from "./pages/Support/ChatImageNewWindow";
|
import ChatImageNewWindow from "./pages/Support/ChatImageNewWindow";
|
||||||
import AccountSettings from "./pages/AccountSettings/AccountSettings";
|
import AccountSettings from "./pages/AccountSettings/AccountSettings";
|
||||||
@ -35,6 +34,8 @@ import {
|
|||||||
setUser,
|
setUser,
|
||||||
setUserAccount,
|
setUserAccount,
|
||||||
useUserStore,
|
useUserStore,
|
||||||
|
OriginalUserSquizAccount,
|
||||||
|
setQuizUserAccount
|
||||||
} from "./stores/user";
|
} from "./stores/user";
|
||||||
import TariffConstructor from "./pages/TariffConstructor/TariffConstructor";
|
import TariffConstructor from "./pages/TariffConstructor/TariffConstructor";
|
||||||
import {
|
import {
|
||||||
@ -44,6 +45,7 @@ import {
|
|||||||
useUserFetcher,
|
useUserFetcher,
|
||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import { pdfjs } from "react-pdf";
|
import { pdfjs } from "react-pdf";
|
||||||
|
import { ruRU } from "@mui/x-date-pickers/locales";
|
||||||
import { theme } from "./utils/theme";
|
import { theme } from "./utils/theme";
|
||||||
import PPofData from "@root/docs/PPofData";
|
import PPofData from "@root/docs/PPofData";
|
||||||
import Docs from "@root/docs/docs";
|
import Docs from "@root/docs/docs";
|
||||||
@ -53,17 +55,29 @@ import PrivacyPolicy from "@root/docs/content/PrivacyPolicy";
|
|||||||
import RecoverPassword from "@root/pages/auth/RecoverPassword";
|
import RecoverPassword from "@root/pages/auth/RecoverPassword";
|
||||||
import OutdatedLink from "@root/pages/auth/OutdatedLink";
|
import OutdatedLink from "@root/pages/auth/OutdatedLink";
|
||||||
import { verify } from "./pages/AccountSettings/helper";
|
import { verify } from "./pages/AccountSettings/helper";
|
||||||
import AfterPay from "./pages/AfterPay";
|
|
||||||
import { PageNotFound } from "./pages/PageNotFound";
|
import { PageNotFound } from "./pages/PageNotFound";
|
||||||
import {setNotEnoughMoneyAmount} from "@stores/cart"
|
import { usePipeSubscriber } from "./utils/hooks/usePipeSubscriber";
|
||||||
|
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
|
import { ModalRequestCreate } from "./pages/Tariffs/ModalRequestCreate";
|
||||||
|
import * as crutch from "./useUserAccountFetcher";
|
||||||
|
import { useTryBuy } from "./utils/hooks/useTryBuy";
|
||||||
|
import AnyServicePayment from "./pages/AnyServicePayment/AnyServicePayment";
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import { ApologyFallback } from "./pages/ApologyPage";
|
||||||
|
import { handleComponentError } from "./utils/handleComponentError";
|
||||||
|
import { createMakeRequestConfig } from "@frontend/kitui";
|
||||||
|
|
||||||
|
console.log("Я здесь для отладки и спешу сообщить, что деплой был успешно завершен!")
|
||||||
|
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
|
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
|
||||||
|
|
||||||
|
const localeText =
|
||||||
|
ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const userId = useUserStore((state) => state.userId);
|
const userId = useUserStore(state => state.userId);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
if(location.pathname !== "/cart"){setNotEnoughMoneyAmount(0)}
|
|
||||||
useUserFetcher({
|
useUserFetcher({
|
||||||
url: process.env.REACT_APP_DOMAIN + `/user/${userId}`,
|
url: process.env.REACT_APP_DOMAIN + `/user/${userId}`,
|
||||||
userId,
|
userId,
|
||||||
@ -79,7 +93,7 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useUserAccountFetcher({
|
useUserAccountFetcher({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/customer/v1.0.0/account",
|
url: process.env.REACT_APP_DOMAIN + "/customer/v1.0.1/account",
|
||||||
userId,
|
userId,
|
||||||
onNewUserAccount: setUserAccount,
|
onNewUserAccount: setUserAccount,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@ -93,6 +107,24 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
crutch.useUserAccountFetcher<OriginalUserSquizAccount>({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
|
||||||
|
userId,
|
||||||
|
onNewUserAccount: setQuizUserAccount,
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
|
if (errorMessage) {
|
||||||
|
enqueueSnackbar(errorMessage);
|
||||||
|
clearUserData();
|
||||||
|
clearAuthToken();
|
||||||
|
navigate("/signin");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
usePipeSubscriber();
|
||||||
|
useTryBuy();
|
||||||
|
|
||||||
verify(userId);
|
verify(userId);
|
||||||
|
|
||||||
if (location.state?.redirectTo)
|
if (location.state?.redirectTo)
|
||||||
@ -115,6 +147,9 @@ const App = () => {
|
|||||||
<Route path="/changepwd/expired" element={<OutdatedLink />} />
|
<Route path="/changepwd/expired" element={<OutdatedLink />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ModalRequestCreate />
|
||||||
|
|
||||||
<Routes location={location.state?.backgroundLocation || location}>
|
<Routes location={location.state?.backgroundLocation || location}>
|
||||||
<Route path="/" element={<Landing />} />
|
<Route path="/" element={<Landing />} />
|
||||||
<Route
|
<Route
|
||||||
@ -142,7 +177,7 @@ const App = () => {
|
|||||||
to="/"
|
to="/"
|
||||||
replace
|
replace
|
||||||
state={{
|
state={{
|
||||||
redirectTo: window.location.pathname + window.location.search,
|
redirectTo: `/changepwd${location.search}`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -159,6 +194,7 @@ const App = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
||||||
|
|
||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
<Route element={<ProtectedLayout />}>
|
<Route element={<ProtectedLayout />}>
|
||||||
<Route path="/tariffs" element={<Tariffs />} />
|
<Route path="/tariffs" element={<Tariffs />} />
|
||||||
@ -179,8 +215,8 @@ const App = () => {
|
|||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/anyservicepayment" element={<AnyServicePayment />} />
|
||||||
<Route path="/ppdd" element={<PPofData />} />
|
<Route path="/ppdd" element={<PPofData />} />
|
||||||
<Route path="/quizpayment" element={<QuizPayment />} />
|
|
||||||
<Route element={<Docs />}>
|
<Route element={<Docs />}>
|
||||||
<Route path={"/docs/oferta"} element={<Oferta />} />
|
<Route path={"/docs/oferta"} element={<Oferta />} />
|
||||||
<Route
|
<Route
|
||||||
@ -189,25 +225,37 @@ const App = () => {
|
|||||||
/>
|
/>
|
||||||
<Route path={"/docs/privacy"} element={<PrivacyPolicy />} />
|
<Route path={"/docs/privacy"} element={<PrivacyPolicy />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/afterpay" element={<AfterPay />} />
|
|
||||||
<Route path="*" element={<PageNotFound />} />
|
<Route path="*" element={<PageNotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
createMakeRequestConfig(undefined, handleComponentError, () => []);
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById("root") as HTMLElement
|
document.getElementById("root") as HTMLElement
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
// <React.StrictMode>
|
// <React.StrictMode>
|
||||||
|
<ErrorBoundary
|
||||||
|
FallbackComponent={ApologyFallback}
|
||||||
|
onError={handleComponentError}
|
||||||
|
>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
<LocalizationProvider
|
||||||
|
dateAdapter={AdapterMoment}
|
||||||
|
adapterLocale="ru"
|
||||||
|
localeText={localeText}
|
||||||
|
>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<SnackbarProvider />
|
<SnackbarProvider />
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</LocalizationProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
// </React.StrictMode>
|
// </React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { Attachment } from "@root/model/attachment"
|
import type { Attachment } from "@root/model/attachment"
|
||||||
|
import { UserAccount } from "@frontend/kitui"
|
||||||
|
|
||||||
export type File = {
|
export type File = {
|
||||||
name: "inn" | "rule" | "egrule" | "certificate";
|
name: "inn" | "rule" | "egrule" | "certificate";
|
||||||
@ -8,21 +9,21 @@ export type File = {
|
|||||||
export interface Verification {
|
export interface Verification {
|
||||||
_id: string;
|
_id: string;
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
status: "org" | "nko";
|
status: UserAccount["status"];
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
files: File[];
|
files: File[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendDocumentsArgs = {
|
export type SendDocumentsArgs = {
|
||||||
status: "org" | "nko";
|
status: UserAccount["status"];
|
||||||
inn: Attachment;
|
inn: Attachment;
|
||||||
rule: Attachment;
|
rule: Attachment;
|
||||||
certificate?: Attachment;
|
certificate?: Attachment;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateDocumentsArgs = {
|
export type UpdateDocumentsArgs = {
|
||||||
status: "org" | "nko";
|
status: UserAccount["status"];
|
||||||
inn?: Attachment;
|
inn?: Attachment;
|
||||||
rule?: Attachment;
|
rule?: Attachment;
|
||||||
certificate?: Attachment;
|
certificate?: Attachment;
|
||||||
|
2
src/model/autoPay.ts
Normal file
2
src/model/autoPay.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type FromDomain = "quiz" | "squiz" | "hub" | "shub" | "localhost:3000";
|
||||||
|
export type Action = "topupwallet" | "createquizcc" | "buy";
|
@ -33,6 +33,8 @@ function AccountSettings() {
|
|||||||
const comment = useUserStore((state) => state.comment)
|
const comment = useUserStore((state) => state.comment)
|
||||||
const userId = useUserStore((state) => state.userId) ?? ""
|
const userId = useUserStore((state) => state.userId) ?? ""
|
||||||
|
|
||||||
|
console.log(user)
|
||||||
|
|
||||||
const [onChangeTypeLP, setOnChangeTypeLP] = useState<LP>("")
|
const [onChangeTypeLP, setOnChangeTypeLP] = useState<LP>("")
|
||||||
|
|
||||||
const [readySend, setReadySend] = useState(false)
|
const [readySend, setReadySend] = useState(false)
|
||||||
@ -51,10 +53,10 @@ function AccountSettings() {
|
|||||||
const type = onChangeTypeLP
|
const type = onChangeTypeLP
|
||||||
setOnChangeTypeLP("")
|
setOnChangeTypeLP("")
|
||||||
setReadySend(false)
|
setReadySend(false)
|
||||||
if (type === "email" && user?.email !== settingsFields.email.value) setSettingsField("email", user?.email || "")
|
if (type === "email" && user?.email !== settingsFields.email.value) setSettingsField("email", user?.email || user?.login || "")
|
||||||
if (type === "password") setSettingsField("password", "")
|
if (type === "password") setSettingsField("password", "")
|
||||||
if (type === "all") {
|
if (type === "all") {
|
||||||
setSettingsField("email", user?.email || "")
|
setSettingsField("email", user?.email || user?.login || "")
|
||||||
setSettingsField("password", "")
|
setSettingsField("password", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ export default function DocumentItem({
|
|||||||
.then(e => {
|
.then(e => {
|
||||||
setReadyShowDocument(true)
|
setReadyShowDocument(true)
|
||||||
})
|
})
|
||||||
.catch(e => console.log(e))
|
.catch(e => console.error(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -47,7 +47,7 @@ export default function DocumentUploadItem({
|
|||||||
.then(e => {
|
.then(e => {
|
||||||
setReadyShowDocument(true)
|
setReadyShowDocument(true)
|
||||||
})
|
})
|
||||||
.catch(e => console.log(e))
|
.catch(e => console.error(e))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setReadyShowDocument(true)
|
setReadyShowDocument(true)
|
||||||
|
@ -19,7 +19,7 @@ export default function UserFields({
|
|||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
||||||
|
|
||||||
const { settingsFields, user } = useUserStore((state) => state)
|
const { settingsFields, user } = useUserStore((state) => state)
|
||||||
|
const a = useUserStore((state) => state)
|
||||||
|
|
||||||
const textFieldProps = {
|
const textFieldProps = {
|
||||||
gap: upMd ? "16px" : "10px",
|
gap: upMd ? "16px" : "10px",
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
useMediaQuery,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { payCart } from "@root/api/cart";
|
|
||||||
import { useUserStore } from "@root/stores/user";
|
|
||||||
import wallet_icon from "@root/assets/Icons/ColorWallet.svg";
|
|
||||||
import { Link, useSearchParams } from "react-router-dom";
|
|
||||||
|
|
||||||
const MINUTE = 1000 * 60;
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const [redirectUrl, setRedirectUrl] = useState<string>("/");
|
|
||||||
const theme = useTheme();
|
|
||||||
const phone = useMediaQuery(theme.breakpoints.down(375));
|
|
||||||
const userId = useUserStore((state) => state.user?._id);
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const paymentUserId = searchParams.get("userid");
|
|
||||||
const wayback = searchParams.get("wayback");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const from = searchParams.get("from") || "hub";
|
|
||||||
const purpose = searchParams.get("purpose");
|
|
||||||
|
|
||||||
if (purpose === "paycart" || from !== "quiz") {
|
|
||||||
let tryCount = 0;
|
|
||||||
|
|
||||||
if (userId !== paymentUserId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payCartPendingRequestDeadline = localStorage.getItem(
|
|
||||||
"payCartPendingRequestDeadline"
|
|
||||||
);
|
|
||||||
const deadline = payCartPendingRequestDeadline
|
|
||||||
? Number(payCartPendingRequestDeadline)
|
|
||||||
: Date.now() + 20 * MINUTE;
|
|
||||||
|
|
||||||
localStorage.setItem(
|
|
||||||
"payCartPendingRequestDeadline",
|
|
||||||
deadline.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
tryPayCart();
|
|
||||||
|
|
||||||
async function tryPayCart() {
|
|
||||||
tryCount += 1;
|
|
||||||
|
|
||||||
|
|
||||||
if (Date.now() > deadline) {
|
|
||||||
localStorage.removeItem("payCartPendingRequestDeadline");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, payCartError] = await payCart();
|
|
||||||
|
|
||||||
if (!payCartError) {
|
|
||||||
localStorage.removeItem("payCartPendingRequestDeadline");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(tryPayCart, tryCount > 5 ? MINUTE : MINUTE / 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = window.location.hostname;
|
|
||||||
const domain = (host.includes("s") ? "s" : "") + from;
|
|
||||||
const pathname = from === "hub" ? "/tariffs" : "/list";
|
|
||||||
|
|
||||||
let redirect = `https://${domain}.pena.digital${pathname}?afterpay=${true}&userid=${userId}`
|
|
||||||
if (wayback) redirect += `&wayback=${wayback}`
|
|
||||||
|
|
||||||
setRedirectUrl(redirect);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "100vw",
|
|
||||||
height: "100vh",
|
|
||||||
p: phone ? "76px 16px" : "117px 0 0 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="img"
|
|
||||||
src={wallet_icon}
|
|
||||||
sx={{
|
|
||||||
width: phone ? "338px" : "auto",
|
|
||||||
height: phone ? "215px" : "auto",
|
|
||||||
mb: "60px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: phone ? "24px" : "36px",
|
|
||||||
fontWeight: 500,
|
|
||||||
lineHeight: "28.44px",
|
|
||||||
textAlign: "center",
|
|
||||||
mb: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Спасибо за оплату!
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "18px",
|
|
||||||
lineHeight: "21.33px",
|
|
||||||
textAlign: "center",
|
|
||||||
color: theme.palette.gray.dark,
|
|
||||||
mb: "30px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Ваш платеж принят и в течение 10 минут товары будут зачислены
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
to={redirectUrl.toString()}
|
|
||||||
replace={true}
|
|
||||||
variant="pena-contained-dark"
|
|
||||||
>
|
|
||||||
На главную
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//https://shub.pena.digital/quizpayment?action=squizpay&dif=50000&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NDZlODc4hLWF1d__aACP1IpA&userid=6846e878c149e4d24ebf50f3&from=AI&wayback=ai_27964
|
|
102
src/pages/AnyServicePayment/AnyServicePayment.tsx
Normal file
102
src/pages/AnyServicePayment/AnyServicePayment.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ApologyPage } from "../ApologyPage";
|
||||||
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
clearAuthToken,
|
||||||
|
getMessageFromFetchError,
|
||||||
|
setAuthToken,
|
||||||
|
useUserAccountFetcher,
|
||||||
|
useUserFetcher,
|
||||||
|
getAuthToken,
|
||||||
|
} from "@frontend/kitui";
|
||||||
|
import {
|
||||||
|
clearUserData,
|
||||||
|
setUserId,
|
||||||
|
useUserStore,
|
||||||
|
} from "@root/stores/user";
|
||||||
|
import { logout } from "@root/api/auth";
|
||||||
|
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
||||||
|
import { clearTickets } from "@root/stores/tickets";
|
||||||
|
import { cancelPayCartProcess, setAction, setBackWay, setFromDomain, setNotEnoughMoneyAmount, startPayCartProcess } from "@stores/allTypesOfPurchases"
|
||||||
|
import { Action, FromDomain } from "@root/model/autoPay";
|
||||||
|
import { logoutAndRedirect, clearUserSession } from "@root/utils/logout";
|
||||||
|
//http://localhost:3001/payment?fromdomain=localhost:3000&action=buy&dif=96900&userid=68773ec66816e2c659b36dad&sec=
|
||||||
|
let first = true;
|
||||||
|
|
||||||
|
export default function AnyServicePayment() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const [message, setMessage] = useState("Идёт загрузка");
|
||||||
|
const user = useUserStore((state) => state.user);
|
||||||
|
|
||||||
|
let URLfromDomain = searchParams.get("fromdomain");//домен откуда произошёл запрос на финансовые работы
|
||||||
|
let URLaction = searchParams.get("action");//что мы, собсна, хотим: оплатить, пополнить, купить и создать квиз
|
||||||
|
let URLuserId = searchParams.get("userid");//тот кто начал всё это действо
|
||||||
|
const URLwayBack = searchParams.get("wayback") ?? "";//путь по которому нужно будет пройти после того как закончим манипуляции (без домена)
|
||||||
|
let URLmoneyDifferent = Number(searchParams.get("dif"));//сколько нужно деняк
|
||||||
|
let URLtoken = searchParams.get("sec");
|
||||||
|
const URLadditionalinformation = searchParams.get("additionalinformation") ?? "";//дополнительная информация
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
function redirectIfSignedIn() {
|
||||||
|
console.log("---------------------------")
|
||||||
|
console.log(URLfromDomain)
|
||||||
|
console.log(URLaction)
|
||||||
|
console.log(URLuserId)
|
||||||
|
console.log(URLwayBack)
|
||||||
|
console.log(URLmoneyDifferent)
|
||||||
|
console.log(URLtoken)
|
||||||
|
console.log(URLadditionalinformation)
|
||||||
|
console.log("---------------------------")
|
||||||
|
if (!first && user?._id === URLuserId) {
|
||||||
|
let returnUrl = `/payment?fromdomain=${URLfromDomain}&action=${URLaction}&dif=${URLmoneyDifferent}&userid=${URLuserId}`
|
||||||
|
if (URLwayBack) returnUrl += `&wayback=${URLwayBack}`
|
||||||
|
if (URLadditionalinformation) returnUrl += `&additionalinformation=${URLadditionalinformation}`
|
||||||
|
console.log("AnyServicePayment - формируем URL для payment:", returnUrl);
|
||||||
|
navigate(returnUrl, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[navigate, user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
navigate(`/anyservicepayment`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (user?._id === URLuserId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (URLaction && URLmoneyDifferent && URLtoken) {
|
||||||
|
(async () => {
|
||||||
|
if (getAuthToken()) {
|
||||||
|
logoutAndRedirect(navigate);
|
||||||
|
await logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
setAuthToken(URLtoken);
|
||||||
|
setUserId(URLuserId);
|
||||||
|
return;
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.href = `https://${URLfromDomain}/${URLwayBack}`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setMessage("Произошла ошибка");
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.href = `https://${URLfromDomain}/${URLwayBack}`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ApologyPage message={message} />;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { FallbackProps } from "react-error-boundary";
|
||||||
|
|
||||||
export const ApologyPage = ({ message }: { message: string }) => {
|
export const ApologyPage = ({ message }: { message: string }) => {
|
||||||
return (
|
return (
|
||||||
@ -20,3 +21,7 @@ export const ApologyPage = ({ message }: { message: string }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function ApologyFallback({ error }: FallbackProps) {
|
||||||
|
return <ApologyPage message={error?.message || "что-то пошло не так"} />;
|
||||||
|
}
|
||||||
|
@ -63,7 +63,7 @@ export default function Section2() {
|
|||||||
</Box>
|
</Box>
|
||||||
{/* {upMd ? <WideTemplCard name="PenaDoc" desc="Сервис автоматизации, избавляющий от рутины */}
|
{/* {upMd ? <WideTemplCard name="PenaDoc" desc="Сервис автоматизации, избавляющий от рутины */}
|
||||||
{/*в документообороте. 1 раз делаешь шаблон и используешь всю жизнь " light={false} sx={{ marginTop: "126px" }} /> : <TemplCardPhonePink />}*/}
|
{/*в документообороте. 1 раз делаешь шаблон и используешь всю жизнь " light={false} sx={{ marginTop: "126px" }} /> : <TemplCardPhonePink />}*/}
|
||||||
{upMd ? <WideTemplCard name="PenaQuiz" desc="Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика" image={card2ImageBig} light={false} sx={{ marginTop: "126px" }} />
|
{upMd ? <WideTemplCard name="PenaQuiz" desc="Конструктор квиз опросов, для любых видов исследований и квиз маркетинга, арбитража трафика" image={card2ImageBig} light={false} sx={{ marginTop: "126px" }} />
|
||||||
:
|
:
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -78,7 +78,7 @@ export default function Section2() {
|
|||||||
>
|
>
|
||||||
<CardWithLink
|
<CardWithLink
|
||||||
headerText="PenaQuiz"
|
headerText="PenaQuiz"
|
||||||
text="Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"
|
text="Конструктор квиз опросов, для любых видов исследований и квиз маркетинга, арбитража трафика"
|
||||||
linkHref="#"
|
linkHref="#"
|
||||||
image={card2Image}
|
image={card2Image}
|
||||||
isHighlighted={!upMd}
|
isHighlighted={!upMd}
|
||||||
|
@ -11,11 +11,8 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { activatePromocode } from "@root/api/promocode";
|
import { activatePromocode } from "@root/api/promocode";
|
||||||
import { sendPayment, sendRSPayment } from "@root/api/wallet";
|
import { sendPayment, sendRSPayment } from "@root/api/wallet";
|
||||||
import b2bLogo from "@root/assets/bank-logo/b2b.png";
|
|
||||||
import tinkoffLogo from "@root/assets/bank-logo/logo-tinkoff.png";
|
|
||||||
import rsPayLogo from "@root/assets/bank-logo/rs-pay.png";
|
import rsPayLogo from "@root/assets/bank-logo/rs-pay.png";
|
||||||
import sberpayLogo from "@root/assets/bank-logo/sberpay.png";
|
import sberpayLogo from "@root/assets/bank-logo/sberpay.png";
|
||||||
import spbLogo from "@root/assets/bank-logo/spb.png";
|
|
||||||
import umoneyLogo from "@root/assets/bank-logo/umaney.png";
|
import umoneyLogo from "@root/assets/bank-logo/umaney.png";
|
||||||
import bankCardLogo from "@root/assets/bank-logo/bankcard.png";
|
import bankCardLogo from "@root/assets/bank-logo/bankcard.png";
|
||||||
import InputTextfield from "@root/components/InputTextfield";
|
import InputTextfield from "@root/components/InputTextfield";
|
||||||
@ -25,15 +22,15 @@ import { currencyFormatter } from "@root/utils/currencyFormatter";
|
|||||||
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
|
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
|
||||||
import { cardShadow } from "@root/utils/theme";
|
import { cardShadow } from "@root/utils/theme";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useEffect, useLayoutEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import CollapsiblePromocodeField from "./CollapsiblePromocodeField";
|
import CollapsiblePromocodeField from "./CollapsiblePromocodeField";
|
||||||
import PaymentMethodCard from "./PaymentMethodCard";
|
import PaymentMethodCard from "./PaymentMethodCard";
|
||||||
import { SorryModal } from "./SorryModal";
|
import { SorryModal } from "./SorryModal";
|
||||||
import { WarnModal } from "./WarnModal";
|
import { WarnModal } from "./WarnModal";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { useCartStore } from "@root/stores/cart";
|
import { allTypesOfPurchases, cancelPayCartProcess } from "@root/stores/allTypesOfPurchases";
|
||||||
import { useDiffMoney } from "@root/stores/diffMoney";
|
import { useAutoPay } from "./useAutoPay";
|
||||||
|
|
||||||
type PaymentMethod = {
|
type PaymentMethod = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -58,110 +55,85 @@ export default function Payment() {
|
|||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const [promocodeField, setPromocodeField] = useState<string>("");
|
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
|
||||||
useState<PaymentMethodType | null>("");
|
|
||||||
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
|
|
||||||
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
|
|
||||||
const [paymentValueField, setPaymentValueField] = useState<string>("0");
|
|
||||||
const [paymentLink, setPaymentLink] = useState<string>("");
|
|
||||||
const location = useLocation();
|
|
||||||
const verificationStatus = useUserStore((state) => state.verificationStatus);
|
|
||||||
const userId = useUserStore((state) => state.userId);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const handleCustomBackNavigation = useHistoryTracker();
|
const handleCustomBackNavigation = useHistoryTracker();
|
||||||
|
|
||||||
|
const userId = useUserStore((state) => state.userId) || "";
|
||||||
|
const notEnoughMoneyAmount = allTypesOfPurchases(state => state.notEnoughMoneyAmount);
|
||||||
|
const siteReadyPayCart = allTypesOfPurchases(state => state.siteReadyPayCart)
|
||||||
|
const verificationStatus = useUserStore((state) => state.verificationStatus);
|
||||||
|
const action = allTypesOfPurchases(state => state.action);
|
||||||
|
const fromDomain = allTypesOfPurchases(state => state.fromDomain);
|
||||||
|
const backWay = allTypesOfPurchases(state => state.backWay);
|
||||||
|
const additionalinformation = allTypesOfPurchases(state => state.additionalinformation);
|
||||||
|
|
||||||
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
|
console.log("Payment - значения из Zustand store:");
|
||||||
const [waybackToSomeSite, setWaybackToSomeSite] = useState<string>("");
|
console.log("action:", action);
|
||||||
|
console.log("fromDomain:", fromDomain);
|
||||||
|
console.log("backWay:", backWay);
|
||||||
|
console.log("additionalinformation:", additionalinformation);
|
||||||
|
console.log("notEnoughMoneyAmount:", notEnoughMoneyAmount);
|
||||||
|
|
||||||
|
const [promocodeField, setPromocodeField] = useState<string>("");
|
||||||
|
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethodType | null>("");
|
||||||
|
|
||||||
const { diffMoney, setNewDiff } = useDiffMoney()
|
const [paymentValueField, setPaymentValueField] = useAutoPay();
|
||||||
|
|
||||||
const notEnoughMoneyAmount =
|
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
|
||||||
(location.state?.notEnoughMoneyAmount as number) ?? 0;
|
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const paymentValue = parseFloat(
|
// paymentValue в копейках для API
|
||||||
bigDecimal.multiply(parseFloat(paymentValueField), 100)
|
const paymentValue = Number(
|
||||||
|
bigDecimal.multiply(parseFloat(paymentValueField || "0"), 100)
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (diffMoney > 0) {
|
return () => {
|
||||||
setNewDiff(0);
|
cancelPayCartProcess();
|
||||||
setPaymentValueField((diffMoney / 100).toString());
|
|
||||||
}
|
}
|
||||||
}, [diffMoney])
|
}, [])
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
//https://shub.pena.digital/quizpayment?action=squizpay&dif=9800&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2ODVhNTc4OTgzZWU3N2Y4ZTFlNjNkYyIsImF1ZCI6InBlbmEiLCJpc3MiOiJwZW5hLWF1dGgtc2VydmljZSIsImlhdCI6MTcyMjIyMjgzMywiZXhwIjoxNzI3NDA2ODMzfQ.My1KJWFk034MiMdImQSlzf5p4Sn5Dhboj2VvPQteh59tD_CwXyPtePEyev3thV_58IbOOgJ5cgeBm0JKn7atgMgRMpNQVdeYKtf6HYvVoAqkrMcT1LHgAlEQ0TcaXssFKCQGuiCVltHY3UE-kQv5TeydBpO3U9BDKvMqRqv5-Xo&userid=6685a578983ee77f8e1e63dc
|
||||||
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
|
const handlePaymentClick = () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
// Проверяем оба источника данных
|
||||||
const fromSquiz = params.get("action");
|
const hasPaymentValue = Number(paymentValueField) > 0;
|
||||||
if (fromSquiz === "squizpay") {
|
const hasNotEnoughMoney = notEnoughMoneyAmount > 0;
|
||||||
setIsFromSquiz(true);
|
|
||||||
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const wayback = params.get("wayback");
|
console.log("handlePaymentClick - проверка:");
|
||||||
if (wayback) setWaybackToSomeSite(wayback);
|
console.log("paymentValueField:", paymentValueField);
|
||||||
console.log(" я получил wayback = " + wayback)
|
console.log("Number(paymentValueField):", Number(paymentValueField));
|
||||||
|
console.log("notEnoughMoneyAmount:", notEnoughMoneyAmount);
|
||||||
|
console.log("hasPaymentValue:", hasPaymentValue);
|
||||||
|
console.log("hasNotEnoughMoney:", hasNotEnoughMoney);
|
||||||
|
|
||||||
navigate(`/payment`, {
|
if (!hasPaymentValue && !hasNotEnoughMoney) {
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function handleChoosePaymentClick() {
|
|
||||||
if (!selectedPaymentMethod) {
|
|
||||||
enqueueSnackbar("Введите метод оплаты");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Number(paymentValueField) === 0) {
|
|
||||||
enqueueSnackbar("Введите сумму");
|
enqueueSnackbar("Введите сумму");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedPaymentMethod !== "rspay") {
|
if (selectedPaymentMethod === "rspay") {
|
||||||
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
|
const amountToCheck = hasPaymentValue ? Number(paymentValueField) : Number(bigDecimal.divide(notEnoughMoneyAmount, 100));
|
||||||
userId: userId ?? "",
|
if (amountToCheck < 900) {
|
||||||
fromSquiz,
|
enqueueSnackbar("Минимальная сумма 900р");
|
||||||
wayback: waybackToSomeSite,
|
|
||||||
body: {
|
|
||||||
type: selectedPaymentMethod,
|
|
||||||
amount: Number(
|
|
||||||
bigDecimal.floor(
|
|
||||||
bigDecimal.multiply(Number(paymentValueField), 100)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
paymentPurpose: notEnoughMoneyAmount ? "paycart" : "replenishwallet",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sendPaymentError) {
|
|
||||||
return enqueueSnackbar(sendPaymentError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendPaymentResponse) {
|
|
||||||
setPaymentLink(sendPaymentResponse.link);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
setWarnModalOpen(true);
|
setWarnModalOpen(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
startPayRS()
|
||||||
if (Number(paymentValueField) < 900) {
|
} else {
|
||||||
enqueueSnackbar("Минимальная сумма 900р");
|
startPayCard()
|
||||||
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [sendRSPaymentResponse] = await sendRSPayment(
|
const startPayRS = async () => {
|
||||||
Number(paymentValueField)
|
// Определяем сумму для оплаты
|
||||||
);
|
const hasPaymentValue = Number(paymentValueField) > 0;
|
||||||
|
const amountInRubles = hasPaymentValue ? Number(paymentValueField) : Number(bigDecimal.divide(notEnoughMoneyAmount, 100));
|
||||||
|
|
||||||
|
const [sendRSPaymentResponse] = await sendRSPayment(amountInRubles);
|
||||||
|
|
||||||
if (sendRSPaymentResponse) {
|
if (sendRSPaymentResponse) {
|
||||||
return enqueueSnackbar(sendRSPaymentResponse);
|
return enqueueSnackbar(sendRSPaymentResponse);
|
||||||
@ -173,8 +145,49 @@ export default function Payment() {
|
|||||||
|
|
||||||
navigate("/settings");
|
navigate("/settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startPayCard = async () => {
|
||||||
|
|
||||||
|
if (!selectedPaymentMethod) {
|
||||||
|
enqueueSnackbar("Введите метод оплаты");
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Определяем сумму для оплаты
|
||||||
|
const hasPaymentValue = Number(paymentValueField) > 0;
|
||||||
|
const amountInRubles = hasPaymentValue ? Number(paymentValueField) : Number(bigDecimal.divide(notEnoughMoneyAmount, 100));
|
||||||
|
const amountInKopecks = Number(bigDecimal.floor(bigDecimal.multiply(amountInRubles, 100)));
|
||||||
|
|
||||||
|
console.log("startPayCard - параметры:");
|
||||||
|
console.log("action:", action);
|
||||||
|
console.log("fromDomain:", fromDomain);
|
||||||
|
console.log("backWay:", backWay);
|
||||||
|
console.log("amountInRubles:", amountInRubles);
|
||||||
|
console.log("amountInKopecks:", amountInKopecks);
|
||||||
|
|
||||||
|
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
|
||||||
|
userId,
|
||||||
|
body: {
|
||||||
|
type: selectedPaymentMethod,
|
||||||
|
amount: amountInKopecks,
|
||||||
|
},
|
||||||
|
action: action || undefined,
|
||||||
|
fromDomain: fromDomain || undefined,
|
||||||
|
backWay: backWay || undefined,
|
||||||
|
additionalinformation: additionalinformation || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sendPaymentError) {
|
||||||
|
return enqueueSnackbar(sendPaymentError);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Произошёл запрос на пополнение счёта. Нам вернули ссылку для перехода на страницу пополнения.
|
||||||
|
if (sendPaymentResponse) {
|
||||||
|
document.location.href = sendPaymentResponse.link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function handleApplyPromocode() {
|
async function handleApplyPromocode() {
|
||||||
if (!promocodeField) return;
|
if (!promocodeField) return;
|
||||||
|
|
||||||
@ -257,7 +270,6 @@ export default function Payment() {
|
|||||||
image={image}
|
image={image}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedPaymentMethod(name);
|
setSelectedPaymentMethod(name);
|
||||||
setPaymentLink("");
|
|
||||||
}}
|
}}
|
||||||
unpopular={false}
|
unpopular={false}
|
||||||
/>
|
/>
|
||||||
@ -268,7 +280,6 @@ export default function Payment() {
|
|||||||
image={rsPayLogo}
|
image={rsPayLogo}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setSelectedPaymentMethod("rspay");
|
setSelectedPaymentMethod("rspay");
|
||||||
setPaymentLink("");
|
|
||||||
}}
|
}}
|
||||||
unpopular={false}
|
unpopular={false}
|
||||||
/>
|
/>
|
||||||
@ -304,7 +315,8 @@ export default function Payment() {
|
|||||||
>
|
>
|
||||||
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
|
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
|
||||||
<Typography mb="20px">К оплате</Typography>
|
<Typography mb="20px">К оплате</Typography>
|
||||||
{paymentLink ? (
|
{
|
||||||
|
siteReadyPayCart?.[userId] && notEnoughMoneyAmount > 0 ?
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
@ -314,10 +326,10 @@ export default function Payment() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currencyFormatter.format(
|
{currencyFormatter.format(
|
||||||
Number(bigDecimal.divide(bigDecimal.floor(paymentValue), 100))
|
Number(bigDecimal.divide(notEnoughMoneyAmount, 100))
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
:
|
||||||
<InputTextfield
|
<InputTextfield
|
||||||
TextfieldProps={{
|
TextfieldProps={{
|
||||||
placeholder: "К оплате",
|
placeholder: "К оплате",
|
||||||
@ -335,30 +347,13 @@ export default function Payment() {
|
|||||||
color={"#F2F3F7"}
|
color={"#F2F3F7"}
|
||||||
FormInputSx={{ mb: "28px" }}
|
FormInputSx={{ mb: "28px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
{paymentLink ? (
|
|
||||||
<Button
|
|
||||||
variant="pena-outlined-light"
|
|
||||||
component="a"
|
|
||||||
href={paymentLink}
|
|
||||||
sx={{
|
|
||||||
mt: "auto",
|
|
||||||
color: "black",
|
|
||||||
border: `1px solid ${theme.palette.purple.main}`,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.purple.dark,
|
|
||||||
border: `1px solid ${theme.palette.purple.dark}`,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Оплатить
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
<Button
|
||||||
variant="pena-outlined-light"
|
variant="pena-outlined-light"
|
||||||
disabled={!isFinite(paymentValue)}
|
disabled={!isFinite(paymentValue)}
|
||||||
onClick={handleChoosePaymentClick}
|
onClick={handlePaymentClick}
|
||||||
sx={{
|
sx={{
|
||||||
mt: "auto",
|
mt: "auto",
|
||||||
color: "black",
|
color: "black",
|
||||||
@ -371,9 +366,8 @@ export default function Payment() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Выбрать
|
Оплатить
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
|
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
|
||||||
|
109
src/pages/Payment/useAutoPay.ts
Normal file
109
src/pages/Payment/useAutoPay.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import { cancelPayCartProcess, setNotEnoughMoneyAmount, startPayCartProcess } from "@root/stores/allTypesOfPurchases";
|
||||||
|
import { useUserStore } from "@root/stores/user";
|
||||||
|
import { setAction, setBackWay, setFromDomain, setAdditionalinformation } from "@root/stores/allTypesOfPurchases";
|
||||||
|
import { Action, FromDomain } from "@root/model/autoPay";
|
||||||
|
|
||||||
|
export const useAutoPay = (): [string, React.Dispatch<React.SetStateAction<string>>] => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const hasProcessed = useRef(false);
|
||||||
|
|
||||||
|
const userId = useUserStore(store => store.userId)
|
||||||
|
const [paymentValueField, setPaymentValueField] = useState<string>("0");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Если уже обработали, не выполняем повторно
|
||||||
|
if (hasProcessed.current) {
|
||||||
|
console.log("useAutoPay - уже обработано, пропускаем");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("useAutoPay - useEffect запущен");
|
||||||
|
|
||||||
|
// Сначала извлекаем все параметры
|
||||||
|
let URLfromDomain = searchParams.get("fromdomain");//домен откуда произошёл запрос на финансовые работы
|
||||||
|
let URLaction = searchParams.get("action");//что мы, собсна, хотим: оплатить, пополнить, купить и создать квиз
|
||||||
|
let URLuserId = searchParams.get("userid");//тот кто начал всё это действо
|
||||||
|
const URLwayBack = searchParams.get("wayback") ?? "";//путь по которому нужно будет пройти после того как закончим манипуляции (без домена)
|
||||||
|
let URLmoneyDifferent = Number(searchParams.get("dif"));//сколько нужно деняк (в копейках)
|
||||||
|
const URLadditionalinformation = searchParams.get("additionalinformation") ?? "";//дополнительная информация
|
||||||
|
|
||||||
|
console.log("useAutoPay - извлеченные параметры:");
|
||||||
|
console.log("URLfromDomain:", URLfromDomain);
|
||||||
|
console.log("URLaction:", URLaction);
|
||||||
|
console.log("URLuserId:", URLuserId);
|
||||||
|
console.log("URLwayBack:", URLwayBack);
|
||||||
|
console.log("URLmoneyDifferent (копейки):", URLmoneyDifferent);
|
||||||
|
console.log("URLadditionalinformation:", URLadditionalinformation);
|
||||||
|
console.log("userId:", userId);
|
||||||
|
|
||||||
|
// Если нет URL параметров, но мы уже на странице /payment, не делаем ничего
|
||||||
|
if (!URLfromDomain && !URLaction && !URLuserId && URLmoneyDifferent === 0) {
|
||||||
|
console.log("useAutoPay - нет URL параметров, но мы уже на /payment, пропускаем");
|
||||||
|
hasProcessed.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Анализ нужно ли вообще вмешательство этого хука
|
||||||
|
const condition1 = URLuserId !== null && URLuserId && URLuserId === userId;
|
||||||
|
const condition2 = URLfromDomain === "quiz" || URLfromDomain === "squiz" || URLfromDomain === "hub" || URLfromDomain === "shub" || URLfromDomain === "localhost:3000";
|
||||||
|
const condition3 = URLaction === "topupwallet" || URLaction === "createquizcc" || URLaction === "buy";
|
||||||
|
const condition4 = !Number.isNaN(URLmoneyDifferent);
|
||||||
|
|
||||||
|
console.log("useAutoPay - проверка условий:");
|
||||||
|
console.log("condition1 (userId совпадает):", condition1);
|
||||||
|
console.log("condition2 (fromDomain валидный):", condition2);
|
||||||
|
console.log("condition3 (action валидный):", condition3);
|
||||||
|
console.log("condition4 (moneyDifferent валидный):", condition4);
|
||||||
|
|
||||||
|
if (condition1 && condition2 && condition3 && condition4) {
|
||||||
|
console.log("useAutoPay - условия выполнены, устанавливаем значения");
|
||||||
|
|
||||||
|
// Ставим флаг, что сайт в состоянии ожидания пополнения счёта для оплаты
|
||||||
|
startPayCartProcess(URLuserId);
|
||||||
|
|
||||||
|
console.log("useAutoPay - устанавливаем fromDomain:", URLfromDomain);
|
||||||
|
setFromDomain(URLfromDomain as FromDomain);
|
||||||
|
|
||||||
|
const processedBackWay = URLwayBack.startsWith('/') ? URLwayBack.slice(1) : URLwayBack;
|
||||||
|
console.log("useAutoPay - устанавливаем backWay:", processedBackWay);
|
||||||
|
setBackWay(processedBackWay);
|
||||||
|
|
||||||
|
console.log("useAutoPay - устанавливаем action:", URLaction);
|
||||||
|
setAction(URLaction as Action);
|
||||||
|
|
||||||
|
console.log("useAutoPay - устанавливаем additionalinformation:", URLadditionalinformation);
|
||||||
|
setAdditionalinformation(URLadditionalinformation);
|
||||||
|
|
||||||
|
// URLmoneyDifferent в копейках, конвертируем в рубли для отображения
|
||||||
|
const rubles = URLmoneyDifferent / 100;
|
||||||
|
setPaymentValueField(rubles.toString());
|
||||||
|
setNotEnoughMoneyAmount(URLmoneyDifferent);
|
||||||
|
|
||||||
|
console.log("useAutoPay - установлено paymentValueField (рубли):", rubles.toString());
|
||||||
|
console.log("useAutoPay - установлено backWay:", processedBackWay);
|
||||||
|
console.log("useAutoPay - установлено additionalinformation:", URLadditionalinformation);
|
||||||
|
|
||||||
|
// Только после установки всех значений делаем навигацию
|
||||||
|
navigate("/payment", { replace: true });
|
||||||
|
} else {
|
||||||
|
console.log("useAutoPay - условия не выполнены, отменяем процесс");
|
||||||
|
console.log("Причины:");
|
||||||
|
if (!condition1) console.log("- userId не совпадает");
|
||||||
|
if (!condition2) console.log("- fromDomain не валидный:", URLfromDomain);
|
||||||
|
if (!condition3) console.log("- action не валидный:", URLaction);
|
||||||
|
if (!condition4) console.log("- moneyDifferent не валидный:", URLmoneyDifferent);
|
||||||
|
cancelPayCartProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отмечаем, что обработали
|
||||||
|
hasProcessed.current = true;
|
||||||
|
|
||||||
|
// Убираем cleanup функцию, так как она вызывается слишком рано
|
||||||
|
// Cleanup будет в Payment компоненте при размонтировании
|
||||||
|
}, [searchParams, userId, navigate])
|
||||||
|
|
||||||
|
return [paymentValueField, setPaymentValueField];
|
||||||
|
}
|
@ -1,107 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { ApologyPage } from "../ApologyPage";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
clearAuthToken,
|
|
||||||
getMessageFromFetchError,
|
|
||||||
setAuthToken,
|
|
||||||
useUserAccountFetcher,
|
|
||||||
useUserFetcher,
|
|
||||||
getAuthToken,
|
|
||||||
} from "@frontend/kitui";
|
|
||||||
import {
|
|
||||||
clearUserData,
|
|
||||||
setUser,
|
|
||||||
setUserAccount,
|
|
||||||
setUserId,
|
|
||||||
useUserStore,
|
|
||||||
} from "@root/stores/user";
|
|
||||||
import { logout } from "@root/api/auth";
|
|
||||||
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
|
||||||
import { clearTickets } from "@root/stores/tickets";
|
|
||||||
import { setNotEnoughMoneyAmount } from "@stores/cart"
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const action = params.get("action");
|
|
||||||
const dif = params.get("dif");
|
|
||||||
const token = params.get("data");
|
|
||||||
const userId = params.get("userid");
|
|
||||||
const wayback = params.get("wayback");
|
|
||||||
|
|
||||||
let first = true;
|
|
||||||
|
|
||||||
export default function QuizPayment() {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [message, setMessage] = useState("Идёт загрузка");
|
|
||||||
const user = useUserStore((state) => state.user);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function redirectIfSignedIn() {
|
|
||||||
if (!first && user?._id === userId) {
|
|
||||||
let returnUrl = `/payment?action=${action}&dif=${dif}&user=${userId}`
|
|
||||||
if (wayback) returnUrl += `&wayback=${wayback}`
|
|
||||||
navigate(returnUrl, {
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[navigate, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
navigate(`/quizpayment`, {
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
if (user?._id === userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action && dif && token) {
|
|
||||||
(async () => {
|
|
||||||
if (getAuthToken()) {
|
|
||||||
clearAuthToken();
|
|
||||||
clearUserData();
|
|
||||||
clearCustomTariffs();
|
|
||||||
clearTickets();
|
|
||||||
setNotEnoughMoneyAmount(0)
|
|
||||||
await logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
setAuthToken(token);
|
|
||||||
|
|
||||||
// setAuthToken(data.data.accessToken)
|
|
||||||
setUserId(userId);
|
|
||||||
|
|
||||||
// useUserFetcher({
|
|
||||||
// url: process.env.REACT_APP_DOMAIN + `/user/${userId}`,
|
|
||||||
// userId,
|
|
||||||
// onNewUser: (user) => {
|
|
||||||
// setUser(user)
|
|
||||||
// navigate(`/payment?action=${action}&dif=${dif}`, { replace: true })
|
|
||||||
|
|
||||||
// },
|
|
||||||
// onError: () => { },
|
|
||||||
// })
|
|
||||||
return;
|
|
||||||
})();
|
|
||||||
} else {
|
|
||||||
var link = document.createElement("a");
|
|
||||||
link.href = "https://quiz.pena.digital/tariffs";
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setMessage("Произошла ошибка");
|
|
||||||
var link = document.createElement("a");
|
|
||||||
link.href = "https://quiz.pena.digital/tariffs";
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ApologyPage message={message} />;
|
|
||||||
}
|
|
@ -101,17 +101,8 @@ function SupportChat() {
|
|||||||
`/heruvym/v1.0.0/ticket?ticket=${ticketId}&Authorization=${token}`,
|
`/heruvym/v1.0.0/ticket?ticket=${ticketId}&Authorization=${token}`,
|
||||||
|
|
||||||
onNewData: (ticketMessages) => {
|
onNewData: (ticketMessages) => {
|
||||||
console.log("SupportChat")
|
|
||||||
console.log("ticketMessages")
|
|
||||||
console.log(ticketMessages)
|
|
||||||
const data = ticketMessages.filter(t => {
|
const data = ticketMessages.filter(t => {
|
||||||
if (typeof t === "object" && t !== null && "event" in t && t.event !== "ping") {
|
if (typeof t === "object" && t !== null && "event" in t && t.event !== "ping") {
|
||||||
console.log("---------------------------------------------------")
|
|
||||||
console.log(t)
|
|
||||||
console.log(typeof t === "object")
|
|
||||||
console.log("event" in t)
|
|
||||||
console.log(t.event !== "ping")
|
|
||||||
console.log("---------------------------------------------------")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -235,9 +226,6 @@ function SupportChat() {
|
|||||||
setDisableFileButton(false);
|
setDisableFileButton(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("messages messmessagesmessagesmessages messages")
|
|
||||||
console.log(messages)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -353,8 +341,6 @@ function SupportChat() {
|
|||||||
message?.files?.length > 0 &&
|
message?.files?.length > 0 &&
|
||||||
isFileImage()
|
isFileImage()
|
||||||
) {
|
) {
|
||||||
console.log("message NEWNEWNENWNEW _WE__WE_W_EW_E_WENWNEWNENWEWNE")
|
|
||||||
console.log(message)
|
|
||||||
return (
|
return (
|
||||||
<ChatImage
|
<ChatImage
|
||||||
unAuthenticated
|
unAuthenticated
|
||||||
|
@ -23,6 +23,7 @@ function TicketList() {
|
|||||||
const fetchState = useTicketStore(state => state.ticketsFetchState)
|
const fetchState = useTicketStore(state => state.ticketsFetchState)
|
||||||
|
|
||||||
const sortedTickets = tickets
|
const sortedTickets = tickets
|
||||||
|
.filter(ticket => !ticket?.top_message?.system)
|
||||||
.sort(sortTicketsByUpdateTime)
|
.sort(sortTicketsByUpdateTime)
|
||||||
.slice(
|
.slice(
|
||||||
ticketApiPage * ticketsPerPage,
|
ticketApiPage * ticketsPerPage,
|
||||||
|
328
src/pages/Tariffs/ModalRequestCreate.tsx
Normal file
328
src/pages/Tariffs/ModalRequestCreate.tsx
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
import { Box, Button, IconButton, Modal, Typography, useMediaQuery, useTheme } from "@mui/material"
|
||||||
|
|
||||||
|
import { Form, Formik, useFormik } from "formik"
|
||||||
|
import CustomTextField from "@components/CustomTextField"
|
||||||
|
import InfoButton from "@components/InfoButton";
|
||||||
|
import { sendContactFormRequest } from "@api/tariff"
|
||||||
|
import { TimePicker } from "@mui/x-date-pickers"
|
||||||
|
import moment, { Moment } from "moment"
|
||||||
|
import { enqueueSnackbar } from "notistack"
|
||||||
|
import { useState } from "react"
|
||||||
|
import CloseIcon from "@root/assets/Icons/CloseIcon";
|
||||||
|
import { useRequestSquizCreate, RSCClose } from "@root/stores/requestSquizCreate"
|
||||||
|
|
||||||
|
|
||||||
|
interface Values {
|
||||||
|
contact: string;
|
||||||
|
dogiebusiness: string;
|
||||||
|
imagination: string;
|
||||||
|
name: string;
|
||||||
|
time?: Moment | null;
|
||||||
|
}
|
||||||
|
const initialValues: Values = {
|
||||||
|
contact: "",//phone number
|
||||||
|
// whoami: {},
|
||||||
|
dogiebusiness: "",
|
||||||
|
imagination: "",
|
||||||
|
name: "",
|
||||||
|
time: null
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FP {
|
||||||
|
title: string
|
||||||
|
desc?: string
|
||||||
|
placeholder: string
|
||||||
|
value: string
|
||||||
|
onChange: any
|
||||||
|
rows?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Field = ({
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
placeholder,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
rows,
|
||||||
|
}: FP) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
m: "15px 0"
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: "21.33px",
|
||||||
|
mb: "10px",
|
||||||
|
}}
|
||||||
|
>{title}</Typography>
|
||||||
|
{desc && <Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
mb: "10px",
|
||||||
|
color: "#9A9AAF"
|
||||||
|
}}
|
||||||
|
>{desc}</Typography>}
|
||||||
|
<CustomTextField
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
maxLength={200}
|
||||||
|
onChange={onChange}
|
||||||
|
rows={rows || 0}
|
||||||
|
sx={rows !== undefined ? { height: "68px" } : {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModalRequestCreate = () => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||||
|
const [isSending, setIsSending] = useState(false)
|
||||||
|
const open = useRequestSquizCreate(store => store.isRSCOpen)
|
||||||
|
|
||||||
|
if (isSending) return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={() => {
|
||||||
|
RSCClose()
|
||||||
|
setIsSending(false)
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
boxShadow: 24,
|
||||||
|
width: isMobile ? "343px" : "418px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
p: "50px"
|
||||||
|
}}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "24px",
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: "28.44px",
|
||||||
|
textAlign: "center"
|
||||||
|
}}
|
||||||
|
>Спасибо за заявку!</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21.33px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
m: "5px 0 30px 0"
|
||||||
|
|
||||||
|
}}
|
||||||
|
>С вами свяжутся в течение 24 часов</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => {
|
||||||
|
RSCClose()
|
||||||
|
setIsSending(false)
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
py: "12px",
|
||||||
|
bgcolor: "rgb(126, 42, 234)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#581CA7",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>Ок</Button>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={RSCClose}
|
||||||
|
>
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
boxShadow: 24,
|
||||||
|
width: isMobile ? "344px" : "620px",
|
||||||
|
borderRadius: "10px"
|
||||||
|
}}>
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "68px",
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
borderRadius: "10px 10px 0 0"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
padding: "20px",
|
||||||
|
color: "rgb(154, 154, 175)",
|
||||||
|
borderRadius: "10px 10px 0 0"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Заполните форму, чтобы оставить заявку на создание квиза
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<IconButton
|
||||||
|
onClick={RSCClose}
|
||||||
|
sx={{
|
||||||
|
width: "12px",
|
||||||
|
height: "12px",
|
||||||
|
position: "absolute",
|
||||||
|
right: "15px",
|
||||||
|
top: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon sx={{ width: "12px", height: "12px", transform: "scale(1.5)" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: "15px 70px 50px 50px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={async (values, formikHelpers) => {
|
||||||
|
if (values.contact.length < 8) return enqueueSnackbar("Пожалуйста, оставьте контактные данные")
|
||||||
|
const resp = await sendContactFormRequest({
|
||||||
|
contact: values.contact,
|
||||||
|
whoami: JSON.stringify({
|
||||||
|
dogiebusiness: values.dogiebusiness,
|
||||||
|
imagination: values.imagination,
|
||||||
|
name: values.name,
|
||||||
|
time: moment(values.time).format("hh:mm")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
//@ts-ignore
|
||||||
|
if (resp[0]?.status === 200) {
|
||||||
|
enqueueSnackbar("Запрос успешно отправлен")
|
||||||
|
setIsSending(true)
|
||||||
|
}
|
||||||
|
if (resp[1]) {
|
||||||
|
//@ts-ignore
|
||||||
|
enqueueSnackbar(resp[1])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ values, isSubmitting, setFieldValue }) => (<>
|
||||||
|
<Form>
|
||||||
|
<Field
|
||||||
|
title="Ваше имя"
|
||||||
|
placeholder="Иван"
|
||||||
|
value={values.name}
|
||||||
|
onChange={({ target }: any) => setFieldValue("name", target.value)}
|
||||||
|
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
title="Какой у вас бизнес?"
|
||||||
|
placeholder="Логистика"
|
||||||
|
value={values.dogiebusiness}
|
||||||
|
onChange={({ target }: any) => setFieldValue("dogiebusiness", target.value)}
|
||||||
|
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
title="Ваши контактные данные"
|
||||||
|
placeholder="Не менее 8 символов"
|
||||||
|
value={values.contact}
|
||||||
|
onChange={({ target }: any) => setFieldValue("contact", target.value)}
|
||||||
|
desc="(Telegram, WhatsApp, номер телефона)"
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
title="Ваши пожелания к квизу"
|
||||||
|
placeholder="Введите свой текст здесь"
|
||||||
|
value={values.imagination}
|
||||||
|
onChange={({ target }: any) => setFieldValue("imagination", target.value)}
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
m: "15px 0"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: "21.33px",
|
||||||
|
mb: "10px",
|
||||||
|
}}
|
||||||
|
>Во сколько вам можно позвонить? </Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
mb: "10px",
|
||||||
|
color: "#9A9AAF"
|
||||||
|
}}
|
||||||
|
>Москва (GMT+3)</Typography>
|
||||||
|
|
||||||
|
<TimePicker
|
||||||
|
onChange={(e) => setFieldValue("time", e)}
|
||||||
|
ampm={false}
|
||||||
|
value={values.time}
|
||||||
|
views={['hours', 'minutes']} format="hh:mm"
|
||||||
|
sx={{
|
||||||
|
border: "#c4c4c4 1px solid",
|
||||||
|
borderRadius: "4px",
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
display: "flex"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex"
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
sx={{
|
||||||
|
py: "12px",
|
||||||
|
bgcolor: "rgb(126, 42, 234)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#581CA7",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>Отправить</Button>
|
||||||
|
<InfoButton sx={{
|
||||||
|
ml: "15px",
|
||||||
|
width: "48px"
|
||||||
|
}} />
|
||||||
|
</Box>
|
||||||
|
</Form>
|
||||||
|
</>)}
|
||||||
|
</Formik>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal >
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { Box, Typography, Tooltip, SxProps, Theme, Button, Badge, useTheme, useMediaQuery, } from "@mui/material";
|
import { Box, Typography, Tooltip, SxProps, Theme, Button, Badge, useTheme, useMediaQuery, IconButton, } from "@mui/material";
|
||||||
import { MouseEventHandler, ReactNode } from "react";
|
import { MouseEventHandler, ReactNode } from "react";
|
||||||
import { cardShadow } from "@root/utils/theme";
|
import { cardShadow } from "@root/utils/theme";
|
||||||
import { relative } from "path";
|
import { relative } from "path";
|
||||||
|
import NotebookWithPencil from "@root/assets/Icons/NotebookWithPencil copy";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
@ -15,9 +16,10 @@ interface Props {
|
|||||||
text?: string;
|
text?: string;
|
||||||
};
|
};
|
||||||
price?: ReactNode;
|
price?: ReactNode;
|
||||||
|
sendRequestToCreate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TariffCard({ icon, headerText, text, sx, price, buttonProps, discount }: Props) {
|
export default function TariffCard({ icon, headerText, text, sx, price, buttonProps, discount, sendRequestToCreate }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
return (
|
return (
|
||||||
@ -111,6 +113,13 @@ export default function TariffCard({ icon, headerText, text, sx, price, buttonPr
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{/* </Tooltip> */}
|
{/* </Tooltip> */}
|
||||||
|
<Box
|
||||||
|
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
}}>
|
||||||
{buttonProps && (
|
{buttonProps && (
|
||||||
<Button
|
<Button
|
||||||
onClick={buttonProps.onClick}
|
onClick={buttonProps.onClick}
|
||||||
@ -123,6 +132,37 @@ export default function TariffCard({ icon, headerText, text, sx, price, buttonPr
|
|||||||
{buttonProps.text}
|
{buttonProps.text}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{Boolean(sendRequestToCreate) && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
ml: isMobile ? "10px" : "20px",
|
||||||
|
bgcolor: "#F2F3F7",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isMobile ?
|
||||||
|
<IconButton
|
||||||
|
onClick={sendRequestToCreate}
|
||||||
|
sx={{
|
||||||
|
p:"12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NotebookWithPencil />
|
||||||
|
</IconButton>
|
||||||
|
:
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
color: "#7E2AEA",
|
||||||
|
p: "10px 14px",
|
||||||
|
}}
|
||||||
|
onClick={sendRequestToCreate}
|
||||||
|
>
|
||||||
|
Запросить
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -89,14 +89,14 @@ export default function Tariffs() {
|
|||||||
<WideTemplCard
|
<WideTemplCard
|
||||||
sx={{ marginTop: "55px" }}
|
sx={{ marginTop: "55px" }}
|
||||||
name={"PenaQuiz"}
|
name={"PenaQuiz"}
|
||||||
desc={"Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"}
|
desc={"Конструктор квиз опросов, для любых видов исследований и квиз маркетинга, арбитража трафика"}
|
||||||
image={CardImage2}
|
image={CardImage2}
|
||||||
href={"https://quiz.pena.digital"}
|
href={"https://quiz.pena.digital"}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<TemplCardPhoneLight
|
<TemplCardPhoneLight
|
||||||
name={"PenaQuiz"}
|
name={"PenaQuiz"}
|
||||||
desc={"Конструктор quiz опросов, для любых видов исследований и quiz маркетинга, арбитража трафика"}
|
desc={"Конструктор квиз опросов, для любых видов исследований и квиз маркетинга, арбитража трафика"}
|
||||||
image={CardImage2}
|
image={CardImage2}
|
||||||
href={"https://quiz.pena.digital"}
|
href={"https://quiz.pena.digital"}
|
||||||
/>
|
/>
|
||||||
|
@ -28,8 +28,10 @@ import TariffCard from "./TariffCard";
|
|||||||
import { useDiscounts } from "@root/api/price";
|
import { useDiscounts } from "@root/api/price";
|
||||||
import { Select } from "@components/Select";
|
import { Select } from "@components/Select";
|
||||||
import { Tabs } from "@components/Tabs";
|
import { Tabs } from "@components/Tabs";
|
||||||
|
import { useRequestSquizCreate, RSCOpen, RSCClose } from "@root/stores/requestSquizCreate"
|
||||||
|
|
||||||
const subPages = ["Базовый тариф PenaQuiz", 'Убрать логотип "PenaQuiz"'];
|
const subPagesTime = ["Базовый тариф PenaQuiz", 'Убрать логотип "PenaQuiz"'];
|
||||||
|
const subPagesVolume = ["Заявки квиз", 'Заказать создание квиза'];
|
||||||
|
|
||||||
const StepperText: Record<string, string> = {
|
const StepperText: Record<string, string> = {
|
||||||
volume: "Тарифы на объём",
|
volume: "Тарифы на объём",
|
||||||
@ -43,10 +45,12 @@ function TariffPage() {
|
|||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const tariffs = useTariffStore((state) => state.tariffs);
|
const tariffs = useTariffStore((state) => state.tariffs);
|
||||||
const [selectedItem, setSelectedItem] = useState<number>(0);
|
const [selectedItemTime, setSelectedItemTime] = useState<number>(0);
|
||||||
|
const [selectedItemVolume, setSelectedItemVolume] = useState<number>(0);
|
||||||
const purchasesAmount =
|
const purchasesAmount =
|
||||||
useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
||||||
const userId = useUserStore((state) => state.user?._id) ?? "";
|
const userId = useUserStore((state) => state.user?._id) ?? "";
|
||||||
|
const userPrivilegies = useUserStore(store => store.quizUserAccount?.privileges);
|
||||||
const discounts = useDiscounts(userId);
|
const discounts = useDiscounts(userId);
|
||||||
const isUserNko =
|
const isUserNko =
|
||||||
useUserStore((state) => state.userAccount?.status) === "nko";
|
useUserStore((state) => state.userAccount?.status) === "nko";
|
||||||
@ -68,25 +72,14 @@ function TariffPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filteredTariffs = tariffs.filter((tariff) => {
|
const filteredTariffs = tariffs.filter((tariff) => {
|
||||||
if (tariff.privileges[0] === undefined) return false;
|
if (tariff.privileges[0] === undefined || tariff.isDeleted || tariff.isCustom) return false;
|
||||||
if (
|
if (unit === "time") {
|
||||||
(tariff.privileges[0].type === "day") === (unit === "time") &&
|
if (selectedItemTime === 0 && tariff.privileges[0].privilegeId === "quizUnlimTime") return true
|
||||||
!tariff.isDeleted &&
|
if (selectedItemTime === 1 && tariff.privileges[0].privilegeId === "squizHideBadge") return true
|
||||||
!tariff.isCustom
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
((selectedItem === 0 && unit === "time") || unit !== "time") &&
|
|
||||||
tariff.privileges[0].serviceKey === "squiz" &&
|
|
||||||
tariff.privileges[0].privilegeId !== "squizHideBadge"
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (
|
if (unit === "volume") {
|
||||||
selectedItem === 1 &&
|
if (selectedItemVolume === 0 && tariff.privileges[0].privilegeId === "quizCnt") return true
|
||||||
unit === "time" &&
|
if (selectedItemVolume === 1 && tariff.privileges[0].privilegeId === "quizManual") return true
|
||||||
tariff.privileges[0].privilegeId === "squizHideBadge"
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@ -95,6 +88,7 @@ function TariffPage() {
|
|||||||
filteredTariffs: Tariff[],
|
filteredTariffs: Tariff[],
|
||||||
addFreeTariff = false
|
addFreeTariff = false
|
||||||
) => {
|
) => {
|
||||||
|
const isCC = userPrivilegies?.quizManual?.amount !== undefined && userPrivilegies?.quizManual?.amount > 0 && unit === "volume" && selectedItemVolume === 1
|
||||||
const tariffElements = filteredTariffs
|
const tariffElements = filteredTariffs
|
||||||
.filter((tariff) => tariff.privileges.length > 0)
|
.filter((tariff) => tariff.privileges.length > 0)
|
||||||
.map((tariff, index) => {
|
.map((tariff, index) => {
|
||||||
@ -104,7 +98,7 @@ function TariffPage() {
|
|||||||
purchasesAmount,
|
purchasesAmount,
|
||||||
currentTariffs ?? [],
|
currentTariffs ?? [],
|
||||||
isUserNko,
|
isUserNko,
|
||||||
userId
|
userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -133,6 +127,7 @@ function TariffPage() {
|
|||||||
text: "Выбрать",
|
text: "Выбрать",
|
||||||
onClick: () => handleTariffItemClick(tariff._id),
|
onClick: () => handleTariffItemClick(tariff._id),
|
||||||
}}
|
}}
|
||||||
|
sendRequestToCreate={isCC ? RSCOpen : undefined}
|
||||||
headerText={tariff.name}
|
headerText={tariff.name}
|
||||||
text={tariff.description || ""}
|
text={tariff.description || ""}
|
||||||
price={
|
price={
|
||||||
@ -206,15 +201,32 @@ function TariffPage() {
|
|||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<Select
|
<Select
|
||||||
items={subPages}
|
items={subPagesTime}
|
||||||
selectedItem={selectedItem}
|
selectedItem={selectedItemTime}
|
||||||
setSelectedItem={setSelectedItem}
|
setSelectedItem={setSelectedItemTime}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Tabs
|
<Tabs
|
||||||
items={subPages}
|
items={subPagesTime}
|
||||||
selectedItem={selectedItem}
|
selectedItem={selectedItemTime}
|
||||||
setSelectedItem={setSelectedItem}
|
setSelectedItem={setSelectedItemTime}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{unit === "volume" && (
|
||||||
|
<>
|
||||||
|
{isMobile ? (
|
||||||
|
<Select
|
||||||
|
items={subPagesVolume}
|
||||||
|
selectedItem={selectedItemVolume}
|
||||||
|
setSelectedItem={setSelectedItemVolume}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Tabs
|
||||||
|
items={subPagesVolume}
|
||||||
|
selectedItem={selectedItemVolume}
|
||||||
|
setSelectedItem={setSelectedItemVolume}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@ -231,7 +243,7 @@ function TariffPage() {
|
|||||||
}))`,
|
}))`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{createTariffElements(filteredTariffs, true)}
|
{createTariffElements(filteredTariffs, unit === "time" || (unit === "volume" && selectedItemVolume !== 1))}
|
||||||
</Box>
|
</Box>
|
||||||
{/*{recentlyPurchased.length > 0 && (*/}
|
{/*{recentlyPurchased.length > 0 && (*/}
|
||||||
{/* <>*/}
|
{/* <>*/}
|
||||||
|
67
src/stores/allTypesOfPurchases.ts
Normal file
67
src/stores/allTypesOfPurchases.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import moment from "moment";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { devtools, persist } from "zustand/middleware";
|
||||||
|
import type { Action, FromDomain } from "@root/model/autoPay";
|
||||||
|
|
||||||
|
interface CartStore {
|
||||||
|
notEnoughMoneyAmount: number;
|
||||||
|
siteReadyPayCart: Record<string, string> | null;
|
||||||
|
fromDomain: FromDomain | null;
|
||||||
|
backWay: string;
|
||||||
|
action: Action | null;
|
||||||
|
additionalinformation: string;
|
||||||
|
}
|
||||||
|
const initialState: CartStore = {
|
||||||
|
notEnoughMoneyAmount: 0,
|
||||||
|
siteReadyPayCart: null,
|
||||||
|
fromDomain: null,
|
||||||
|
backWay: "",
|
||||||
|
action: null,
|
||||||
|
additionalinformation: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//Была попытка оплатить корзину. Тут записанна недостающая сумма.
|
||||||
|
export const allTypesOfPurchases = create<CartStore>()(
|
||||||
|
devtools(
|
||||||
|
persist(
|
||||||
|
() => initialState,
|
||||||
|
{
|
||||||
|
name: 'siteReadyPayCart', // Ключ для localStorage
|
||||||
|
partialize: (state) => ({
|
||||||
|
// Сохраняем ТОЛЬКО siteReadyPayCart
|
||||||
|
siteReadyPayCart: state.siteReadyPayCart
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{
|
||||||
|
name: "cartStore",
|
||||||
|
enabled: process.env.NODE_ENV === "development",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setSiteReadyPayCart = (flag: Record<string, string> | null) => allTypesOfPurchases.setState({ siteReadyPayCart: flag });
|
||||||
|
export const setNotEnoughMoneyAmount = (amount: number) => allTypesOfPurchases.setState({ notEnoughMoneyAmount: amount });
|
||||||
|
export const setFromDomain = (fromDomain: FromDomain) => allTypesOfPurchases.setState({ fromDomain });
|
||||||
|
export const setBackWay = (backWay: string) => allTypesOfPurchases.setState({ backWay });
|
||||||
|
export const setAction = (action: Action) => allTypesOfPurchases.setState({ action });
|
||||||
|
export const setAdditionalinformation = (additionalinformation: string) => allTypesOfPurchases.setState({ additionalinformation });
|
||||||
|
|
||||||
|
export const startPayCartProcess = (userId: string) => setSiteReadyPayCart({ [userId]: moment().add(20, 'minutes').format("X") });
|
||||||
|
export const cancelPayCartProcess = () => {
|
||||||
|
setSiteReadyPayCart(null);
|
||||||
|
allTypesOfPurchases.setState({
|
||||||
|
fromDomain: null,
|
||||||
|
backWay: "",
|
||||||
|
action: null
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const calcTimeOfReadyPayCart = (deadline: string) => {
|
||||||
|
const ready = Number(deadline) > Number(moment().format("X"))
|
||||||
|
if (!ready) {
|
||||||
|
cancelPayCartProcess()
|
||||||
|
}
|
||||||
|
return ready
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
import { create } from "zustand";
|
|
||||||
import { devtools } from "zustand/middleware";
|
|
||||||
|
|
||||||
interface CartStore {
|
|
||||||
notEnoughMoneyAmount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCartStore = create<CartStore>()(
|
|
||||||
devtools(
|
|
||||||
(get, set) => ({
|
|
||||||
notEnoughMoneyAmount: 0,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: "Cart",
|
|
||||||
enabled: process.env.NODE_ENV === "development",
|
|
||||||
trace: true,
|
|
||||||
actionsBlacklist: "rejected",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const setNotEnoughMoneyAmount = (amount: number) => useCartStore.setState({ notEnoughMoneyAmount: amount });
|
|
@ -1,14 +0,0 @@
|
|||||||
import { HistoryRecord, HistoryRecord2 } from "@root/api/history";
|
|
||||||
import { create } from "zustand";
|
|
||||||
import { devtools, persist } from "zustand/middleware";
|
|
||||||
|
|
||||||
|
|
||||||
type DiffMoneyType = {
|
|
||||||
diffMoney: number;
|
|
||||||
setNewDiff: (diff: number) => void
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDiffMoney = create<DiffMoneyType>((set) => ({
|
|
||||||
diffMoney: 0,
|
|
||||||
setNewDiff: (diff: number) => set({diffMoney: diff})
|
|
||||||
}));
|
|
25
src/stores/requestSquizCreate.ts
Normal file
25
src/stores/requestSquizCreate.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Privilege } from "@frontend/kitui";
|
||||||
|
import { create } from "zustand"
|
||||||
|
import { devtools } from "zustand/middleware"
|
||||||
|
|
||||||
|
|
||||||
|
interface RequestSquizCreate {
|
||||||
|
isRSCOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: RequestSquizCreate = {
|
||||||
|
isRSCOpen: false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRequestSquizCreate = create<RequestSquizCreate>()(
|
||||||
|
devtools(
|
||||||
|
(get, set) => initialState,
|
||||||
|
{
|
||||||
|
name: "Privileges",
|
||||||
|
enabled: process.env.NODE_ENV === "development",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const RSCOpen = () => useRequestSquizCreate.setState({ isRSCOpen: true })
|
||||||
|
export const RSCClose = () => useRequestSquizCreate.setState({ isRSCOpen: false })
|
@ -44,7 +44,7 @@ const initialState: TicketStore = {
|
|||||||
ticketCount: 0,
|
ticketCount: 0,
|
||||||
tickets: [],
|
tickets: [],
|
||||||
apiPage: 0,
|
apiPage: 0,
|
||||||
ticketsPerPage: 10,
|
ticketsPerPage: 100,
|
||||||
ticketsFetchState: "idle",
|
ticketsFetchState: "idle",
|
||||||
authData: initAuthData,
|
authData: initAuthData,
|
||||||
unauthData: initAuthData,
|
unauthData: initAuthData,
|
||||||
|
@ -14,7 +14,15 @@ import { patchUser } from "@root/api/user"
|
|||||||
import { UserAccountSettingsFieldStatus, VerificationStatus } from "@root/model/account"
|
import { UserAccountSettingsFieldStatus, VerificationStatus } from "@root/model/account"
|
||||||
import { patchCurrency, deleteCart, patchCart } from "@root/api/cart"
|
import { patchCurrency, deleteCart, patchCart } from "@root/api/cart"
|
||||||
import { User, UserAccount, UserName, getInitials, patchUserAccount } from "@frontend/kitui"
|
import { User, UserAccount, UserName, getInitials, patchUserAccount } from "@frontend/kitui"
|
||||||
import { setNotEnoughMoneyAmount } from "./cart";
|
import { cancelPayCartProcess, setNotEnoughMoneyAmount, setSiteReadyPayCart, allTypesOfPurchases } from "./allTypesOfPurchases";
|
||||||
|
|
||||||
|
type Privilege = {
|
||||||
|
amount: number;
|
||||||
|
created_at: string;
|
||||||
|
id: string;
|
||||||
|
privilege_id: string;
|
||||||
|
privilege_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface UserStore {
|
interface UserStore {
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
@ -29,8 +37,19 @@ interface UserStore {
|
|||||||
documentsUrl: UserDocumentsUrl;
|
documentsUrl: UserDocumentsUrl;
|
||||||
comment: string;
|
comment: string;
|
||||||
initials: string;
|
initials: string;
|
||||||
|
quizUserAccount: OriginalUserSquizAccount | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type OriginalUserSquizAccount = {
|
||||||
|
created_at: string;
|
||||||
|
deleted: boolean;
|
||||||
|
email: string;
|
||||||
|
id: string;
|
||||||
|
privileges: Record<string, Privilege>;
|
||||||
|
privilege_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultFieldValues = {
|
const defaultFieldValues = {
|
||||||
value: "",
|
value: "",
|
||||||
error: null,
|
error: null,
|
||||||
@ -58,6 +77,7 @@ const initialState: UserStore = {
|
|||||||
userId: null,
|
userId: null,
|
||||||
user: null,
|
user: null,
|
||||||
userAccount: null,
|
userAccount: null,
|
||||||
|
quizUserAccount: null,
|
||||||
settingsFields: { ...defaultFields },
|
settingsFields: { ...defaultFields },
|
||||||
verificationStatus: VerificationStatus.NOT_VERIFICATED,
|
verificationStatus: VerificationStatus.NOT_VERIFICATED,
|
||||||
verificationType: "juridical",
|
verificationType: "juridical",
|
||||||
@ -103,10 +123,22 @@ export const useUserStore = create<UserStore>()(
|
|||||||
export const setVerificationStatus = (verificationStatus: VerificationStatus) =>
|
export const setVerificationStatus = (verificationStatus: VerificationStatus) =>
|
||||||
useUserStore.setState({ verificationStatus })
|
useUserStore.setState({ verificationStatus })
|
||||||
|
|
||||||
export const setVerificationType = (verificationType: "nko" | "org") =>
|
export const setVerificationType = (verificationType: UserAccount["status"]) =>
|
||||||
useUserStore.setState({
|
useUserStore.setState({
|
||||||
verificationType: verificationType === "org" ? "juridical" : "nko",
|
verificationType: verificationType === "org" ? "juridical" : "nko",
|
||||||
})
|
})
|
||||||
|
export const setUserStatus = (status: UserAccount["status"]) =>
|
||||||
|
useUserStore.setState(
|
||||||
|
produce<UserStore>((state) => {
|
||||||
|
if (state.userAccount !== null) state.userAccount.status = status
|
||||||
|
})
|
||||||
|
)
|
||||||
|
export const setWallet = (wallet: UserAccount["wallet"]) =>
|
||||||
|
useUserStore.setState(
|
||||||
|
produce<UserStore>((state) => {
|
||||||
|
if (state.userAccount !== null) state.userAccount.wallet = wallet
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
export const setUserId = (userId: string | null) => useUserStore.setState({ userId })
|
export const setUserId = (userId: string | null) => useUserStore.setState({ userId })
|
||||||
export const setUser = (user: User) =>
|
export const setUser = (user: User) =>
|
||||||
@ -114,7 +146,7 @@ export const setUser = (user: User) =>
|
|||||||
produce<UserStore>((state) => {
|
produce<UserStore>((state) => {
|
||||||
state.user = user
|
state.user = user
|
||||||
|
|
||||||
state.settingsFields.email.value = user?.email ?? ""
|
state.settingsFields.email.value = user?.email || user?.login || ""
|
||||||
state.settingsFields.phoneNumber.value = user?.phoneNumber ?? ""
|
state.settingsFields.phoneNumber.value = user?.phoneNumber ?? ""
|
||||||
state.settingsFields.password.value = ""
|
state.settingsFields.password.value = ""
|
||||||
})
|
})
|
||||||
@ -139,12 +171,36 @@ export const setUserAccount = (user: UserAccount) =>
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const setCart = (cart: string[]) =>
|
export const setQuizUserAccount = (quizUserAccount: OriginalUserSquizAccount) => useUserStore.setState({ quizUserAccount });
|
||||||
|
|
||||||
|
export const setNewNames = (name: UserName) =>
|
||||||
useUserStore.setState(
|
useUserStore.setState(
|
||||||
produce<UserStore>((state) => {
|
produce<UserStore>((state) => {
|
||||||
if (state.userAccount) state.userAccount.cart = cart
|
state.settingsFields.firstname.value = name.firstname ?? ""
|
||||||
|
state.settingsFields.secondname.value = name.secondname ?? ""
|
||||||
|
state.settingsFields.middlename.value = name.middlename ?? ""
|
||||||
|
state.settingsFields.orgname.value = name.orgname ?? ""
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
{
|
||||||
|
type: "setNewNames",
|
||||||
|
payload: name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const setCart = (cart: string[]) => {
|
||||||
|
|
||||||
|
useUserStore.setState(
|
||||||
|
produce<UserStore>((state) => {
|
||||||
|
if (state.userAccount) {
|
||||||
|
state.userAccount.cart = cart
|
||||||
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
//Изменение корзины ведёт к отмене этих 2 параметров всегда
|
||||||
|
setNotEnoughMoneyAmount(0)
|
||||||
|
cancelPayCartProcess()
|
||||||
|
}
|
||||||
|
|
||||||
export const setComment = (comment: string) => useUserStore.setState({ comment })
|
export const setComment = (comment: string) => useUserStore.setState({ comment })
|
||||||
|
|
||||||
@ -273,7 +329,7 @@ export const sendUserData = async () => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
isPatchingUser && patchUser(userPayload).then(([user]) => user && setUser(user)),
|
isPatchingUser && patchUser(userPayload).then(([user]) => user && setUser(user)),
|
||||||
isPatchingUserAccount && patchUserAccount(userAccountPayload, "1.0.1").then(setUserAccount),
|
isPatchingUserAccount && patchUserAccount(userAccountPayload, "v1.0.1").then(setUserAccount),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
src/useUserAccountFetcher.ts
Normal file
60
src/useUserAccountFetcher.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||||
|
import { createUserAccount, devlog, getAuthToken, setAuthToken } from "@frontend/kitui";
|
||||||
|
import { isAxiosError } from "axios";
|
||||||
|
|
||||||
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
|
import type { UserAccount } from "@frontend/kitui";
|
||||||
|
|
||||||
|
export const useUserAccountFetcher = <T = UserAccount>({
|
||||||
|
onError,
|
||||||
|
onNewUserAccount,
|
||||||
|
url,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
url: string;
|
||||||
|
userId: string | null;
|
||||||
|
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.replace("get", "create"), "v1.0.1")
|
||||||
|
.then((result) => {
|
||||||
|
devlog("Created user account", result);
|
||||||
|
onNewUserAccountRef.current(result as T);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.response?.status === 409) return;
|
||||||
|
devlog("Error creating user account", error);
|
||||||
|
onErrorRef.current?.(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onErrorRef.current?.(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => controller.abort();
|
||||||
|
}, [url, userId]);
|
||||||
|
};
|
66
src/utils/hooks/usePipeSubscriber.ts
Normal file
66
src/utils/hooks/usePipeSubscriber.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Ticket, UserAccount, UserName, getAuthToken, useSSESubscription } from "@frontend/kitui"
|
||||||
|
import { setCart, setNewNames, setUserStatus, setWallet, useUserStore } from "@root/stores/user";
|
||||||
|
import { useSSETab } from "./useSSETab";
|
||||||
|
import { cancelPayCartProcess } from "@root/stores/allTypesOfPurchases";
|
||||||
|
|
||||||
|
type Ping = [{ event: "ping" }]
|
||||||
|
|
||||||
|
type SomeChange = [{
|
||||||
|
"id": UserAccount["_id"],
|
||||||
|
"userId": UserAccount["userId"],
|
||||||
|
"cart": UserAccount["cart"],
|
||||||
|
"wallet": {
|
||||||
|
"cash": UserAccount["wallet"]["cash"],
|
||||||
|
"currency": UserAccount["wallet"]["currency"],
|
||||||
|
"spent": UserAccount["wallet"]["spent"],
|
||||||
|
"purchasesAmount": UserAccount["wallet"]["purchasesAmount"],
|
||||||
|
"money": UserAccount["wallet"]["money"],
|
||||||
|
"lastPaymentId": string;
|
||||||
|
},
|
||||||
|
"name": UserName,
|
||||||
|
"status": UserAccount["status"],
|
||||||
|
"isDeleted": UserAccount["isDeleted"],
|
||||||
|
"createdAt": UserAccount["createdAt"];
|
||||||
|
"updatedAt": UserAccount["updatedAt"];
|
||||||
|
"from": string;
|
||||||
|
"partner": string;
|
||||||
|
}]
|
||||||
|
|
||||||
|
type PipeMessage = Ping | SomeChange
|
||||||
|
|
||||||
|
export const usePipeSubscriber = () => {
|
||||||
|
const token = getAuthToken();
|
||||||
|
const userId = useUserStore((state) => state.userId);
|
||||||
|
const { isActiveSSETab, updateSSEValue } = useSSETab<Ticket[]>("pipe");
|
||||||
|
|
||||||
|
useSSESubscription({
|
||||||
|
enabled: Boolean(token) && Boolean(userId) && isActiveSSETab,
|
||||||
|
url:
|
||||||
|
process.env.REACT_APP_DOMAIN +
|
||||||
|
`/customer/v1.0.1/account/pipe?Authorization=${token}`,
|
||||||
|
onNewData: (data) => {
|
||||||
|
let message = data[0] as PipeMessage
|
||||||
|
updateSSEValue(message)
|
||||||
|
|
||||||
|
//Пропускаем пингование
|
||||||
|
if ('event' in message && message.event === "ping") return
|
||||||
|
|
||||||
|
|
||||||
|
if ('cart' in message) {
|
||||||
|
setCart(message.cart as string[])
|
||||||
|
cancelPayCartProcess()
|
||||||
|
}
|
||||||
|
else if ('wallet' in message) {
|
||||||
|
setWallet(message.wallet as UserAccount["wallet"])
|
||||||
|
cancelPayCartProcess()
|
||||||
|
}
|
||||||
|
else if ('name' in message) {
|
||||||
|
setNewNames(message.name as UserName)
|
||||||
|
}
|
||||||
|
else if ('status' in message) {
|
||||||
|
setUserStatus(message.status as UserAccount["status"])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
marker: "pipe",
|
||||||
|
});
|
||||||
|
}
|
105
src/utils/hooks/useReauthorization.ts
Normal file
105
src/utils/hooks/useReauthorization.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { clearAuthToken, getAuthToken, setAuthToken } from '@frontend/kitui';
|
||||||
|
import { logout } from '@root/api/auth';
|
||||||
|
import { setNotEnoughMoneyAmount } from '@root/stores/allTypesOfPurchases';
|
||||||
|
import { clearCustomTariffs } from '@root/stores/customTariffs';
|
||||||
|
import { clearTickets } from '@root/stores/tickets';
|
||||||
|
import { clearUserData, setUserId, useUserStore } from '@root/stores/user';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { clearUserSession, logoutAndRedirect } from "@root/utils/logout";
|
||||||
|
|
||||||
|
interface QuizAuthParams {
|
||||||
|
action?: string;
|
||||||
|
dif?: string;
|
||||||
|
data?: string;
|
||||||
|
userid?: string;
|
||||||
|
wayback?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useReauthorization = () => {
|
||||||
|
const userId = useUserStore(store => store.userId);
|
||||||
|
const user = useUserStore(store => store.user);
|
||||||
|
const { search } = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Этот эффект сработает при каждом изменении query-параметров
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
|
||||||
|
// Обработка старых параметров (userid, sec)
|
||||||
|
const URLuserId = params.get('userid');
|
||||||
|
const URLtoken = params.get('sec');
|
||||||
|
|
||||||
|
// Обработка новых параметров авторизации
|
||||||
|
const quizParams: QuizAuthParams = {
|
||||||
|
action: params.get("action") || undefined,
|
||||||
|
dif: params.get("dif") || undefined,
|
||||||
|
data: params.get("data") || undefined,
|
||||||
|
userid: params.get("userid") || undefined,
|
||||||
|
wayback: params.get("wayback") || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { action, dif, data: token, userid: quizUserId, wayback } = quizParams;
|
||||||
|
|
||||||
|
// Если есть новые параметры авторизации, обрабатываем их
|
||||||
|
if (action && dif && token && quizUserId) {
|
||||||
|
// Если пользователь уже авторизован и это тот же пользователь, перенаправляем на payment
|
||||||
|
if (user?._id === quizUserId) {
|
||||||
|
let returnUrl = `/payment?action=${action}&dif=${dif}&user=${quizUserId}`;
|
||||||
|
if (wayback) returnUrl += `&wayback=${wayback}`;
|
||||||
|
navigate(returnUrl, { replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если уже обрабатываем авторизацию, не запускаем повторно
|
||||||
|
if (isProcessing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsProcessing(true);
|
||||||
|
|
||||||
|
const processQuizAuth = async () => {
|
||||||
|
try {
|
||||||
|
// Если есть текущий токен, очищаем данные
|
||||||
|
if (getAuthToken()) {
|
||||||
|
logoutAndRedirect(navigate);
|
||||||
|
await logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем новый токен и ID пользователя
|
||||||
|
setAuthToken(token);
|
||||||
|
setUserId(quizUserId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка авторизации:", error);
|
||||||
|
|
||||||
|
// Перенаправляем на внешний сайт в случае ошибки
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = "https://quiz.pena.digital/tariffs";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
processQuizAuth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка старых параметров (userid, sec)
|
||||||
|
if (URLuserId !== userId && URLtoken) {
|
||||||
|
// Если есть токен в URL, устанавливаем его
|
||||||
|
// Очищаем данные только если токен действительно изменился
|
||||||
|
if (getAuthToken() !== URLtoken) {
|
||||||
|
clearUserSession();
|
||||||
|
// Не вызываем logout() чтобы избежать перенаправления
|
||||||
|
}
|
||||||
|
setAuthToken(URLtoken);
|
||||||
|
}
|
||||||
|
}, [search, userId, user?._id, navigate, isProcessing]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isProcessing,
|
||||||
|
};
|
||||||
|
};
|
@ -74,6 +74,7 @@ export const useSSETab = <T = unknown>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setOpenTime = () => {
|
const setOpenTime = () => {
|
||||||
|
//Время установлено - пропускаем
|
||||||
if (openTimeSetted) {
|
if (openTimeSetted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
29
src/utils/hooks/useTryBuy.ts
Normal file
29
src/utils/hooks/useTryBuy.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { payCart } from "@root/api/cart";
|
||||||
|
import { allTypesOfPurchases, calcTimeOfReadyPayCart, cancelPayCartProcess } from "@root/stores/allTypesOfPurchases"
|
||||||
|
import { useUserStore } from "@root/stores/user";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
export const useTryBuy = () => {
|
||||||
|
const userId = useUserStore(store => store.userId)
|
||||||
|
const userAccount = useUserStore(state => state.userAccount);
|
||||||
|
const siteReadyPayCart = allTypesOfPurchases(state => state.siteReadyPayCart);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userId !== null && siteReadyPayCart !== null && siteReadyPayCart[userId] !== undefined) {
|
||||||
|
const deadline = siteReadyPayCart[userId]
|
||||||
|
if (calcTimeOfReadyPayCart(deadline)) {
|
||||||
|
|
||||||
|
//Время ещё не вышло. У нас стоит флаг покупать корзину если время не вышло.
|
||||||
|
(async () => {
|
||||||
|
const [, payCartError] = await payCart();
|
||||||
|
|
||||||
|
if (!payCartError) {
|
||||||
|
enqueueSnackbar("Товары успешно приобретены")
|
||||||
|
cancelPayCartProcess()
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userAccount, userId, siteReadyPayCart])
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import type { Attachment } from "@root/model/attachment"
|
import type { Attachment } from "@root/model/attachment"
|
||||||
|
import { transliterate } from 'transliteration';
|
||||||
|
|
||||||
type KeyValue<T = string> = {
|
type KeyValue<T = string> = {
|
||||||
[key: string]: T;
|
[key: string]: T;
|
||||||
@ -24,11 +25,13 @@ export const jsonToFormdata = (
|
|||||||
json: KeyValue<string | Attachment>
|
json: KeyValue<string | Attachment>
|
||||||
): FormData => {
|
): FormData => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
if (json.egrule !== undefined) delete json.egrule
|
||||||
|
|
||||||
for (const key in json) {
|
for (const key in json) {
|
||||||
if (!key) continue
|
if (!key) continue
|
||||||
|
|
||||||
const value = json[key]
|
const value = json[key]
|
||||||
|
if (typeof value !== "string") value.name = transliterate(value.name.replace(/\s/g, '_'))
|
||||||
|
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
formData.append(key, convertBinaryStringToFile(value))
|
formData.append(key, convertBinaryStringToFile(value))
|
||||||
@ -38,6 +41,5 @@ export const jsonToFormdata = (
|
|||||||
|
|
||||||
formData.append(key, value)
|
formData.append(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return formData
|
return formData
|
||||||
}
|
}
|
||||||
|
25
src/utils/logout.ts
Normal file
25
src/utils/logout.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { clearAuthToken } from "@frontend/kitui";
|
||||||
|
import { clearUserData } from "@root/stores/user";
|
||||||
|
import { clearCustomTariffs } from "@root/stores/customTariffs";
|
||||||
|
import { clearTickets } from "@root/stores/tickets";
|
||||||
|
import { setNotEnoughMoneyAmount } from "@root/stores/allTypesOfPurchases";
|
||||||
|
import { NavigateFunction } from "react-router-dom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает все пользовательские данные (без редиректа)
|
||||||
|
*/
|
||||||
|
export function clearUserSession() {
|
||||||
|
clearAuthToken();
|
||||||
|
clearUserData();
|
||||||
|
clearCustomTariffs();
|
||||||
|
clearTickets();
|
||||||
|
setNotEnoughMoneyAmount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет полный логаут пользователя и редиректит на главную страницу
|
||||||
|
*/
|
||||||
|
export function logoutAndRedirect(navigate: NavigateFunction) {
|
||||||
|
clearUserSession();
|
||||||
|
navigate("/");
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import { Navigate, Outlet } from "react-router-dom"
|
import { Navigate, Outlet, useLocation } from "react-router-dom"
|
||||||
import { useUserStore } from "@root/stores/user"
|
import { useUserStore } from "@root/stores/user"
|
||||||
|
|
||||||
|
|
||||||
export default function PrivateRoute() {
|
export default function PrivateRoute() {
|
||||||
|
const location = useLocation()
|
||||||
const user = useUserStore(state => state.user)
|
const user = useUserStore(state => state.user)
|
||||||
|
|
||||||
return user ? <Outlet /> : <Navigate to="/" replace />
|
return user ? <Outlet /> : <Navigate to="/" replace />
|
||||||
|
78
yarn.lock
78
yarn.lock
@ -1059,6 +1059,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541"
|
||||||
integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
|
integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
|
||||||
|
|
||||||
|
"@babel/runtime@^7.25.7", "@babel/runtime@^7.27.6":
|
||||||
|
version "7.27.6"
|
||||||
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6"
|
||||||
|
integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==
|
||||||
|
|
||||||
"@babel/template@^7.27.1", "@babel/template@^7.3.3":
|
"@babel/template@^7.27.1", "@babel/template@^7.3.3":
|
||||||
version "7.27.2"
|
version "7.27.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
|
||||||
@ -1516,10 +1521,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
|
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
|
||||||
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
|
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
|
||||||
|
|
||||||
"@frontend/kitui@^1.0.108":
|
"@frontend/kitui@^1.0.110":
|
||||||
version "1.0.108"
|
version "1.0.110"
|
||||||
resolved "http://gitea.pena/api/packages/skeris/npm/%40frontend%2Fkitui/-/1.0.108/kitui-1.0.108.tgz#1bb609dfe07668b6fd9a7b8618c229e0bb609f1e"
|
resolved "http://gitea.pena/api/packages/skeris/npm/%40frontend%2Fkitui/-/1.0.110/kitui-1.0.110.tgz#0c0a968293338537a2811e7761f8efe933893573"
|
||||||
integrity sha512-4DiF7GHX0RbBMZpFioclc3B87N+HrGLv1B3DveUCdHzukfxvFXyEKnRZQ4wYljO2A3FLSD9+4Dr6cSuZYw95OQ==
|
integrity sha512-XOCev5zNtNZ8fu3IfK6oFNOqT8lE9jlmUX1kQ3OO+H30/LBpnBrww9nV/aHV2TEm0wYXdRMvaEtU6VOb72sDdg==
|
||||||
dependencies:
|
dependencies:
|
||||||
immer "^10.0.2"
|
immer "^10.0.2"
|
||||||
reconnecting-eventsource "^1.6.2"
|
reconnecting-eventsource "^1.6.2"
|
||||||
@ -2104,11 +2109,30 @@
|
|||||||
csstype "^3.1.3"
|
csstype "^3.1.3"
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
|
"@mui/types@^7.4.4":
|
||||||
|
version "7.4.4"
|
||||||
|
resolved "https://registry.npmjs.org/@mui/types/-/types-7.4.4.tgz#0c5cd56905231e27096b41d096f1c948c26bdd5d"
|
||||||
|
integrity sha512-p63yhbX52MO/ajXC7hDHJA5yjzJekvWD3q4YDLl1rSg+OXLczMYPvTuSuviPRCgRX8+E42RXz1D/dz9SxPSlWg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.27.6"
|
||||||
|
|
||||||
"@mui/types@~7.2.15":
|
"@mui/types@~7.2.15":
|
||||||
version "7.2.24"
|
version "7.2.24"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.24.tgz#5eff63129d9c29d80bbf2d2e561bd0690314dec2"
|
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.24.tgz#5eff63129d9c29d80bbf2d2e561bd0690314dec2"
|
||||||
integrity sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==
|
integrity sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==
|
||||||
|
|
||||||
|
"@mui/utils@^5.16.6 || ^6.0.0 || ^7.0.0":
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/@mui/utils/-/utils-7.2.0.tgz#31062697b41aa8ea8ef04e3d3fadca1dec3e1de1"
|
||||||
|
integrity sha512-O0i1GQL6MDzhKdy9iAu5Yr0Sz1wZjROH1o3aoztuivdCXqEeQYnEjTDiRLGuFxI9zrUbTHBwobMyQH5sNtyacw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.27.6"
|
||||||
|
"@mui/types" "^7.4.4"
|
||||||
|
"@types/prop-types" "^15.7.15"
|
||||||
|
clsx "^2.1.1"
|
||||||
|
prop-types "^15.8.1"
|
||||||
|
react-is "^19.1.0"
|
||||||
|
|
||||||
"@mui/utils@^5.17.1":
|
"@mui/utils@^5.17.1":
|
||||||
version "5.17.1"
|
version "5.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.17.1.tgz#72ba4ffa79f7bdf69d67458139390f18484b6e6b"
|
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.17.1.tgz#72ba4ffa79f7bdf69d67458139390f18484b6e6b"
|
||||||
@ -2121,6 +2145,27 @@
|
|||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
react-is "^19.0.0"
|
react-is "^19.0.0"
|
||||||
|
|
||||||
|
"@mui/x-date-pickers@^7.13.0":
|
||||||
|
version "7.29.4"
|
||||||
|
resolved "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.29.4.tgz#b8808cb8e28c1d4e528b37b336effc8074e65faf"
|
||||||
|
integrity sha512-wJ3tsqk/y6dp+mXGtT9czciAMEO5Zr3IIAHg9x6IL0Eqanqy0N3chbmQQZv3iq0m2qUpQDLvZ4utZBUTJdjNzw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.25.7"
|
||||||
|
"@mui/utils" "^5.16.6 || ^6.0.0 || ^7.0.0"
|
||||||
|
"@mui/x-internals" "7.29.0"
|
||||||
|
"@types/react-transition-group" "^4.4.11"
|
||||||
|
clsx "^2.1.1"
|
||||||
|
prop-types "^15.8.1"
|
||||||
|
react-transition-group "^4.4.5"
|
||||||
|
|
||||||
|
"@mui/x-internals@7.29.0":
|
||||||
|
version "7.29.0"
|
||||||
|
resolved "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.29.0.tgz#1f353b697ed1bf5594ac549556ade2e6841f4bf5"
|
||||||
|
integrity sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.25.7"
|
||||||
|
"@mui/utils" "^5.16.6 || ^6.0.0 || ^7.0.0"
|
||||||
|
|
||||||
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
||||||
version "5.1.1-v1"
|
version "5.1.1-v1"
|
||||||
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
|
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
|
||||||
@ -2807,6 +2852,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2"
|
||||||
integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
|
integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
|
||||||
|
|
||||||
|
"@types/prop-types@^15.7.15":
|
||||||
|
version "15.7.15"
|
||||||
|
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7"
|
||||||
|
integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==
|
||||||
|
|
||||||
"@types/q@^1.5.1":
|
"@types/q@^1.5.1":
|
||||||
version "1.5.8"
|
version "1.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
|
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
|
||||||
@ -2834,7 +2884,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-transition-group@^4.4.10":
|
"@types/react-transition-group@^4.4.10", "@types/react-transition-group@^4.4.11":
|
||||||
version "4.4.12"
|
version "4.4.12"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
|
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
|
||||||
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
|
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
|
||||||
@ -8903,6 +8953,11 @@ mlly@^1.7.3, mlly@^1.7.4:
|
|||||||
pkg-types "^1.3.0"
|
pkg-types "^1.3.0"
|
||||||
ufo "^1.5.4"
|
ufo "^1.5.4"
|
||||||
|
|
||||||
|
moment@^2.30.1:
|
||||||
|
version "2.30.1"
|
||||||
|
resolved "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
|
||||||
|
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
@ -10344,7 +10399,7 @@ react-is@^18.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
||||||
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
||||||
|
|
||||||
react-is@^19.0.0:
|
react-is@^19.0.0, react-is@^19.1.0:
|
||||||
version "19.1.0"
|
version "19.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.1.0.tgz#805bce321546b7e14c084989c77022351bbdd11b"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.1.0.tgz#805bce321546b7e14c084989c77022351bbdd11b"
|
||||||
integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==
|
integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==
|
||||||
@ -11854,6 +11909,13 @@ tr46@~0.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||||
|
|
||||||
|
transliteration@^2.3.5:
|
||||||
|
version "2.3.5"
|
||||||
|
resolved "https://registry.npmjs.org/transliteration/-/transliteration-2.3.5.tgz#8f92309575f69e4a8a525dab4ff705ebcf961c45"
|
||||||
|
integrity sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==
|
||||||
|
dependencies:
|
||||||
|
yargs "^17.5.1"
|
||||||
|
|
||||||
tryer@^1.0.1:
|
tryer@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||||
@ -12893,9 +12955,9 @@ yargs@^16.2.0:
|
|||||||
y18n "^5.0.5"
|
y18n "^5.0.5"
|
||||||
yargs-parser "^20.2.2"
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
yargs@^17.3.1:
|
yargs@^17.3.1, yargs@^17.5.1:
|
||||||
version "17.7.2"
|
version "17.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui "^8.0.1"
|
cliui "^8.0.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user