Compare commits

...

21 Commits
eng ... main

Author SHA1 Message Date
93f469f786 --
Some checks failed
Deploy / DeployService (push) Successful in 21s
Deploy / CreateImage (push) Has been cancelled
2025-06-18 00:54:22 +03:00
c486780fea Merge branch 'staging' of gitea.pena:PenaSide/front-hub into staging
Some checks failed
Deploy / DeployService (push) Has been cancelled
Deploy / CreateImage (push) Has been cancelled
2025-06-09 23:39:59 +03:00
3b22f1b3b8 consolelogs 2025-06-09 23:30:52 +03:00
2dfd830106 fix: base image
All checks were successful
Deploy / CreateImage (push) Successful in 4m11s
Deploy / DeployService (push) Successful in 25s
2025-06-09 18:26:18 +03:00
ddc1467089 возможность помнить кудав вернуться в квизе
Some checks failed
Deploy / CreateImage (push) Failing after 36s
Deploy / DeployService (push) Has been skipped
2025-06-09 17:29:40 +03:00
0948e41294 fix chat
All checks were successful
Deploy / CreateImage (push) Successful in 4m6s
Deploy / DeployService (push) Successful in 21s
2025-05-18 15:34:21 +03:00
7c12c70f90 shown consultant to last message
All checks were successful
Deploy / CreateImage (push) Successful in 4m18s
Deploy / DeployService (push) Successful in 21s
2025-05-13 18:25:13 +03:00
b989b55426 Merge branch 'staging' of gitea.pena:PenaSide/front-hub into staging
All checks were successful
Deploy / CreateImage (push) Successful in 4m19s
Deploy / DeployService (push) Successful in 22s
2025-05-12 17:12:23 +03:00
de5ef7e925 fix kit + add ticket check shown
Some checks failed
Deploy / CreateImage (push) Successful in 4m22s
Deploy / DeployService (push) Failing after 21s
2025-05-12 17:00:38 +03:00
401d8f1389 fix deploy rules for staging
Some checks failed
Deploy / DeployService (push) Blocked by required conditions
Deploy / CreateImage (push) Has been cancelled
2025-05-12 16:43:20 +03:00
b228932444 fix kit + add ticket check shown
Some checks failed
Deploy / CreateImage (push) Successful in 4m22s
Deploy / DeployService (push) Failing after 20s
2025-05-11 22:12:48 +03:00
0e352f36ee delete consolelog
All checks were successful
Deploy / CreateImage (push) Successful in 4m18s
Deploy / DeployService (push) Successful in 20s
2025-05-03 22:53:14 +03:00
0e225e9f18 update kit fix sse ping
All checks were successful
Deploy / CreateImage (push) Successful in 4m42s
Deploy / DeployService (push) Successful in 21s
2025-04-09 16:28:29 +03:00
1131ef915f measure ram utilisation on staging hub
All checks were successful
Deploy / CreateImage (push) Successful in 3m48s
Deploy / DeployService (push) Successful in 20s
2025-03-30 19:20:15 +03:00
b6646ed54f tariff getList
All checks were successful
Deploy / CreateImage (push) Successful in 3m36s
Deploy / DeployService (push) Successful in 20s
2025-03-19 06:41:44 +03:00
3a84c260d0 unuseToken in create ticket
All checks were successful
Deploy / CreateImage (push) Successful in 3m47s
Deploy / DeployService (push) Successful in 21s
2025-03-17 03:02:08 +03:00
035fd231c9 --
All checks were successful
Deploy / CreateImage (push) Successful in 4m4s
Deploy / DeployService (push) Successful in 21s
2025-03-07 15:28:55 +03:00
2fba38b604 update kit 92
Some checks failed
Deploy / DeployService (push) Blocked by required conditions
Deploy / CreateImage (push) Has been cancelled
2025-03-07 15:22:34 +03:00
7ea3524524 add System to create ticket
Some checks failed
Deploy / CreateImage (push) Has been cancelled
Deploy / DeployService (push) Has been cancelled
2025-03-07 01:46:07 +03:00
15fc3b1ae9 gprod yml and instruction
Some checks failed
Deploy / CreateImage (push) Failing after 4m55s
Deploy / DeployService (push) Has been skipped
2025-03-05 02:14:59 +03:00
1799263212 deploy prod
Some checks failed
Deploy / CreateImage (push) Failing after 2m33s
Deploy / DeployService (push) Has been skipped
2025-03-03 01:06:18 +03:00
32 changed files with 29179 additions and 2768 deletions

@ -1 +1 @@
REACT_APP_DOMAIN="https://hub.pena.digital"
REACT_APP_DOMAIN=""

@ -0,0 +1,26 @@
name: Deploy
run-name: ${{ gitea.actor }} build image and push to container registry
on:
push:
branches:
- 'main'
jobs:
CreateImage:
runs-on: [skeris]
uses: https://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
with:
runner: skeris
secrets:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
DeployService:
runs-on: [frontprod]
#needs: CreateImage
uses: https://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7
with:
runner: hubprod
actionid: ${{ gitea.run_id }}

@ -0,0 +1,26 @@
name: Deploy
run-name: ${{ gitea.actor }} build image and push to container registry
on:
push:
branches:
- 'staging'
jobs:
CreateImage:
runs-on: [hubstaging]
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
with:
runner: hubstaging
secrets:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
DeployService:
runs-on: [frontstaging]
needs: CreateImage
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7
with:
runner: frontstaging
actionid: ${{ gitea.run_id }}

14
.gitea/workflows/lint.yml Normal file

@ -0,0 +1,14 @@
name: Lint
run-name: ${{ gitea.actor }} produce linting
on:
push:
branches:
- 'dev'
jobs:
Lint:
runs-on: [hubstaging]
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/lint.yml@v1.1.0
with:
runner: hubstaging

1
.npmrc Normal file

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

15
Containerfile Normal file

@ -0,0 +1,15 @@
FROM gitea.pena/penadevops/container-images/node:main as build
RUN apk update && rm -rf /var/cache/apk/*
WORKDIR /usr/app
COPY . .
RUN npm install --force && yarn cache clean
RUN psstat.sh "npm run build"
FROM gitea.pena/penadevops/container-images/nginx:main as result
WORKDIR /usr/share/nginx/html
COPY --from=build /usr/app/build/ /usr/share/nginx/html
COPY fallback.js /usr/share/nginx/html/fallback.js
COPY hub.conf /etc/nginx/conf.d/default.conf

@ -1,15 +0,0 @@
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/node as build
RUN apk update && rm -rf /var/cache/apk/*
WORKDIR /usr/app
COPY . .
RUN yarn install --ignore-scripts --non-interactive --frozen-lockfile && yarn cache clean
RUN yarn build
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/nginx as result
WORKDIR /usr/share/nginx/html
COPY --from=build /usr/app/build/ /usr/share/nginx/html
COPY fallback.js /usr/share/nginx/html/fallback.js
COPY hub.conf /etc/nginx/conf.d/default.conf

@ -1,9 +1,8 @@
version: "3"
services:
hub:
container_name: hub
restart: unless-stopped
image: $CI_REGISTRY_IMAGE/main:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
hostname: hub
image: gitea.pena/penaside/front-hub/main:1118
tty: true

@ -1,14 +1,8 @@
version: "3"
services:
hub:
container_name: hub
restart: unless-stopped
image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
networks:
- marketplace_penahub_frontend
image: gitea.pena/penaside/front-hub/staging:$GITHUB_RUN_NUMBER
hostname: hub
tty: true
networks:
marketplace_penahub_frontend:
external: true

26099
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "craco start",
"start": "NODE_OPTIONS=\"--max-old-space-size=1024\" craco start",
"build": "craco build",
"test": "craco test --env=node --transformIgnorePatterns \"node_modules/(?!@frontend)/\"",
"test:cart": "vitest ./src/utils/calcCart",
@ -16,7 +16,7 @@
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@frontend/kitui": "^1.0.82",
"@frontend/kitui": "^1.0.108",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@popperjs/core": "^2.11.8",
@ -36,7 +36,7 @@
"react-error-boundary": "^4.0.11",
"react-pdf": "^7.1.2",
"react-router-dom": "^6.23.0",
"react-slick": "^0.29.0",
"react-slick": "^0.30.3",
"slick-carousel": "^1.8.1",
"swr": "^2.2.5",
"use-debounce": "^10.0.0",
@ -53,7 +53,7 @@
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-slick": "^0.23.10",
"@types/react-slick": "^0.23.13",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"craco-alias": "^3.0.1",
@ -75,5 +75,6 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

@ -26,7 +26,7 @@ export const getTariffs = async (
try {
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
method: "GET",
url: `${API_URL}/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
url: `${API_URL}/tariff/getList?page=${apiPage}&limit=${tariffsPerPage}`,
useToken: true,
signal,
});

@ -77,12 +77,14 @@ export const sendFile = async (
export const createTicket = async (
ticketNameField: string,
ticketBodyField: string
ticketBodyField: string,
isToken?: boolean
): Promise<[CreateTicketResponse | null, string?]> => {
try {
const createTicketResponse = await createTicketRequest({
url: `${API_URL}/create`,
body: { Title: ticketNameField, Message: ticketBodyField },
body: { Title: ticketNameField, Message: ticketBodyField, System: false },
useToken: isToken
});
return [createTicketResponse];

@ -16,15 +16,29 @@ interface PaymentBody {
export const sendPayment = async ({
userId,
wayback,
body,
fromSquiz,
paymentPurpose,
}: {
userId: string;
wayback: string;
body: PaymentBody;
fromSquiz: boolean;
paymentPurpose: "paycart" | "replenishwallet";
}): Promise<[SendPaymentResponse | null, string?]> => {
console.log(" я sendPayment и вот мой wayback = " + wayback)
let returnUrl= `https://${isStaging}hub.pena.digital/afterpay?from=${
fromSquiz ? "quiz" : "hub"
}&purpose=${paymentPurpose}&userid=${userId}`
if (wayback) returnUrl += `&wayback=${wayback}`
console.log("returnUrl")
console.log(returnUrl)
const reqeustBody = {
currency: "RUB",
bankCard: {
@ -36,9 +50,7 @@ export const sendPayment = async ({
},
phoneNumber: "79000000000",
login: "login_test",
returnUrl: `https://${isStaging}hub.pena.digital/afterpay?from=${
fromSquiz ? "quiz" : "hub"
}&purpose=${paymentPurpose}&userid=${userId}`,
returnUrl,
...body,
};

@ -11,6 +11,7 @@ import {
useTheme,
} from "@mui/material";
import {
getAuthToken,
getMessageFromFetchError,
throttle,
TicketMessage,
@ -150,12 +151,19 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
}, []),
onFetchStateChange: setUnauthTicketMessageFetchState,
});
console.log("sessionData")
console.log(sessionData)
useSSESubscription<TicketMessage>({
enabled: sseEnabled && isActiveSSETab && Boolean(sessionData),
url:
process.env.REACT_APP_DOMAIN +
`/heruvym/v1.0.0/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
onNewData: (ticketMessages) => {
console.log("Chat")
console.log("ticketMessages useSSESubscription ")
console.log(ticketMessages)
const isTicketClosed = ticketMessages.some(
(message) => message.session_id === "close"
);
@ -167,10 +175,14 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
}
return;
}
console.log("under checking some close message -----------------------------------------")
updateSSEValue(ticketMessages);
addOrUpdateUnauthMessages(ticketMessages);
},
onDisconnect: useCallback(() => {
console.log("DISCONNECT")
setUnauthIsPreventAutoscroll(false);
setSseEnabled(false);
}, []),
@ -202,7 +214,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
},
onFetchStateChange: () => {},
onFetchStateChange: () => { },
enabled: Boolean(user),
});
@ -245,12 +257,40 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
);
useEffect(() => {
if (open) {
const newMessages = messages.filter(({ shown }) => shown.me !== 1);
if (open && messages.length > 1) {
let lastUnreadMessage = null;
// Ищем последнее непрочитанное чужое сообщение до первого прочитанного или своего
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
const isOwnMessage = (ticket.sessionData?.sessionId || user) === message.user_id;
// Пропускаем системные сообщения
if (message.ticket_id === "111") {
continue;
}
// Если встретили своё сообщение - прерываем поиск
if (isOwnMessage) {
break;
}
// Если встретили прочитанное сообщение - прерываем поиск
if (message.shown?.me === 1) {
break;
}
// Если сообщение чужое и не прочитано - запоминаем его
if (!isOwnMessage && message.shown?.me !== 1) {
lastUnreadMessage = message;
break;
}
}
newMessages.map(async ({ id }) => {
await shownMessage(id);
});
// Если нашли непрочитанное сообщение - отмечаем его как прочитанное
if (lastUnreadMessage) {
shownMessage(lastUnreadMessage.id);
}
}
}, [open, messages]);
@ -270,7 +310,9 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const [createTicketresult, createTicketerror] = await createTicket(
"Unauth title",
messageField
messageField,
//При создании тикета, если нет у клиента токена, то хедер authorization вовсе не нужно слать
Boolean(getAuthToken())
);
if (createTicketerror) {

@ -1,4 +1,4 @@
import { useState, useEffect, forwardRef } from "react";
import { useState, useEffect, forwardRef, useMemo } from "react";
import {
Box,
Fab,
@ -71,6 +71,28 @@ export default function FloatingSupportChat() {
},
};
const unreadMessagesCount = useMemo(() => {
let count = 0;
// Идём с конца массива к началу
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
// Пропускаем сообщение с id "111"
if (message.id === "111") continue;
// Если сообщение не прочитано (shown.me !== 1)
if (message.shown.me !== 1) {
count++;
} else {
// Встретили прочитанное сообщение - прекращаем подсчёт
break;
}
}
return count;
}, [messages]); // Зависимость от messages - пересчитывается при их изменении
useEffect(() => {
const onResize = () => {
if (document.fullscreenElement) {
@ -153,7 +175,7 @@ export default function FloatingSupportChat() {
/>
)}
<Badge
badgeContent={messages.filter(({ shown }) => shown.me !== 1).length}
badgeContent={unreadMessagesCount}
sx={{
"& .MuiBadge-badge": {
display: isChatOpened ? "none" : "flex",

@ -40,6 +40,7 @@ export default function ProtectedLayout() {
process.env.REACT_APP_DOMAIN +
`/heruvym/v1.0.0/subscribe?Authorization=${token}`,
onNewData: (data) => {
console.log("ProtectedLayout")
updateSSEValue(data);
updateTickets(data.filter((d) => Boolean(d.id)));
setTicketCount(data.length);
@ -63,6 +64,7 @@ export default function ProtectedLayout() {
});
useAllTariffsFetcher({
baseUrl: process.env.REACT_APP_DOMAIN + "/strator/tariff/getList",
onSuccess: updateTariffs,
onError: (error) => {
const errorMessage = getMessageFromFetchError(error);

@ -63,10 +63,8 @@ export default function DocumentItem({
useEffect(() => {
if (typeof documentUrl === 'string') {
if (!documentUrl.includes("pena.digital")) {
console.log(documentUrl)
fetch(documentUrl)
.then(e => {
console.log(e)
setReadyShowDocument(true)
})
.catch(e => console.log(e))

@ -43,10 +43,8 @@ export default function DocumentUploadItem({
useEffect(() => {
if (typeof urlOrFile === 'string') {
if (!urlOrFile.includes("pena.digital")) {
console.log(documentUrl)
fetch(documentUrl)
.then(e => {
console.log(e)
setReadyShowDocument(true)
})
.catch(e => console.log(e))

@ -20,6 +20,7 @@ export default () => {
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";
@ -70,9 +71,10 @@ export default () => {
const domain = (host.includes("s") ? "s" : "") + from;
const pathname = from === "hub" ? "/tariffs" : "/list";
setRedirectUrl(
`https://${domain}.pena.digital${pathname}?afterpay=${true}&userid=${userId}`
);
let redirect = `https://${domain}.pena.digital${pathname}?afterpay=${true}&userid=${userId}`
if (wayback) redirect += `&wayback=${wayback}`
setRedirectUrl(redirect);
}, []);
return (
@ -133,3 +135,5 @@ export default () => {
</Box>
);
};
//https://shub.pena.digital/quizpayment?action=squizpay&dif=50000&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NDZlODc4hLWF1d__aACP1IpA&userid=6846e878c149e4d24ebf50f3&from=AI&wayback=ai_27964

@ -65,16 +65,18 @@ export default function Payment() {
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
const [paymentValueField, setPaymentValueField] = useState<string>("0");
const [paymentLink, setPaymentLink] = useState<string>("");
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
const location = useLocation();
console.log("location", location);
console.log("location", location.state);
const verificationStatus = useUserStore((state) => state.verificationStatus);
const userId = useUserStore((state) => state.userId);
const navigate = useNavigate();
const handleCustomBackNavigation = useHistoryTracker();
const {diffMoney, setNewDiff} = useDiffMoney()
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
const [waybackToSomeSite, setWaybackToSomeSite] = useState<string>("");
const { diffMoney, setNewDiff } = useDiffMoney()
const notEnoughMoneyAmount =
(location.state?.notEnoughMoneyAmount as number) ?? 0;
@ -83,13 +85,12 @@ export default function Payment() {
bigDecimal.multiply(parseFloat(paymentValueField), 100)
);
useEffect(() => {
console.log(diffMoney)
if (diffMoney > 0) {
setNewDiff(0)
setPaymentValueField((diffMoney / 100).toString())
}
}, [diffMoney])
useEffect(() => {
if (diffMoney > 0) {
setNewDiff(0);
setPaymentValueField((diffMoney / 100).toString());
}
}, [diffMoney])
useLayoutEffect(() => {
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
@ -99,10 +100,15 @@ export default function Payment() {
setIsFromSquiz(true);
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString());
}
const wayback = params.get("wayback");
if (wayback) setWaybackToSomeSite(wayback);
console.log(" я получил wayback = " + wayback)
navigate(`/payment`, {
replace: true,
});
}, [ ]);
}, []);
async function handleChoosePaymentClick() {
if (!selectedPaymentMethod) {
@ -119,6 +125,7 @@ export default function Payment() {
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
userId: userId ?? "",
fromSquiz,
wayback: waybackToSomeSite,
body: {
type: selectedPaymentMethod,
amount: Number(
@ -374,3 +381,4 @@ export default function Payment() {
</SectionWrapper>
);
}
//https://shub.pena.digital/quizpayment?action=squizpay&dif=50000&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NDcxNjAxYzE0OWU0ZDI0ZWJmNTExNyIsImF1ZCI6InBlbmEiLCJpc3MiOiJwZW5hLWF1dGgtc2VydmljZSIsImlhdCI6MTc0OTQ4OTE1MywiZXhwIjoxNzU0NjczMTUzfQ.SqEXFSPzP3ugIscqLywGkjFJmFqx13zOtxGAjZQ6SzUw8w9ZXjE9uFn8VbLBMGPqeJbvcT2jRV2bB5qtqMy1T3aNuSZ9AZW0jY1hGvFB-bSrYguMV1yErLkR45SvdK2jGI6dg3p6LRqCHb2JMl8vur_KKaus3GiJlsTNNR0fhgI&userid=68471601c149e4d24ebf5117&from=AI&wayback=ai_27969

@ -19,13 +19,14 @@ import {
import { logout } from "@root/api/auth";
import { clearCustomTariffs } from "@root/stores/customTariffs";
import { clearTickets } from "@root/stores/tickets";
import {setNotEnoughMoneyAmount} from "@stores/cart"
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;
@ -36,10 +37,13 @@ export default function QuizPayment() {
useEffect(
function redirectIfSignedIn() {
if (!first && user?._id === userId)
navigate(`/payment?action=${action}&dif=${dif}&user=${userId}`, {
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]
);

@ -99,9 +99,24 @@ function SupportChat() {
url:
process.env.REACT_APP_DOMAIN +
`/heruvym/v1.0.0/ticket?ticket=${ticketId}&Authorization=${token}`,
onNewData: (ticketMessages) => {
updateSSEValue(ticketMessages);
addOrUpdateMessages(ticketMessages);
console.log("SupportChat")
console.log("ticketMessages")
console.log(ticketMessages)
const data = ticketMessages.filter(t => {
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
}
})
updateSSEValue(data);
addOrUpdateMessages(data);
},
onDisconnect: useCallback(() => {
clearMessageState();
@ -185,11 +200,11 @@ function SupportChat() {
month: "2-digit",
day: "2-digit",
}) +
" " +
createdAt.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
});
" " +
createdAt.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
});
const sendFile = async (file: File) => {
if (file === undefined) return true;
@ -220,6 +235,9 @@ function SupportChat() {
setDisableFileButton(false);
};
console.log("messages messmessagesmessagesmessages messages")
console.log(messages)
return (
<Box
sx={{
@ -331,23 +349,26 @@ function SupportChat() {
}
};
if (
message.files !== null &&
message.files.length > 0 &&
message?.files !== null &&
message?.files?.length > 0 &&
isFileImage()
) {
console.log("message NEWNEWNENWNEW _WE__WE_W_EW_E_WENWNEWNENWEWNE")
console.log(message)
return (
<ChatImage
unAuthenticated
key={message.id}
file={message.files[0]}
file={message?.files[0]}
createdAt={message.created_at}
isSelf={ticket.user === message.user_id}
/>
);
}
if (
message.files !== null &&
message.files.length > 0 &&
message?.files !== undefined &&
message?.files !== null &&
message?.files.length > 0 &&
isFileVideo()
) {
return (

@ -42,8 +42,6 @@ function TariffConstructor() {
}}
>
{Object.entries(customTariffs).filter(([serviceKey]) => serviceKey === "squiz").map(([serviceKey, privileges], index) => {
console.log("privileges")
console.log(privileges)
return (
<Box key={serviceKey}>
<Box

@ -53,8 +53,6 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
);
function handleSliderChange(measurement: PrivilegeName) {
console.log(measurement)
console.log(sliderSettingsByType)
return (value: number | number[]) => {
if (Number(value) < Number(sliderSettingsByType[measurement]?.min)) {

@ -1 +1,2 @@
export { Slider } from "./slider"
import Slider from "react-slick";

@ -104,7 +104,7 @@ export const Slider = ({ items }: SliderProps) => {
sx={{ "& .slick-track": { marginLeft: marginLeft + "px", columnGap: items.length === 1 ? "0px" : "40px" } }}
ref={sliderRef}
>
{(items.length < 4 && !isMiddle && !isTablet) ||
{/* {(items.length < 4 && !isMiddle && !isTablet) ||
(items.length < 3 && isMiddle && !isTablet) ||
(items.length < 1 && isTablet) ? (
<Box
@ -140,7 +140,7 @@ export const Slider = ({ items }: SliderProps) => {
>
{items}
</SliderSlick>
)}
)} */}
</Box>
)
}

@ -72,7 +72,8 @@ export const setTicketApiPage = (apiPage: number) =>
export const updateTickets = (receivedTickets: Ticket[]) => {
const state = useTicketStore.getState();
const ticketIdToTicketMap: { [ticketId: string]: Ticket } = {};
//@ts-ignore
// [...state.tickets, ...receivedTickets].filter(t=>!Boolean(t?.top_message.system)).forEach(
[...state.tickets, ...receivedTickets].forEach(
(ticket) => (ticketIdToTicketMap[ticket.id] = ticket)
);

@ -273,7 +273,7 @@ export const sendUserData = async () => {
await Promise.all([
isPatchingUser && patchUser(userPayload).then(([user]) => user && setUser(user)),
isPatchingUserAccount && patchUserAccount(userAccountPayload).then(setUserAccount),
isPatchingUserAccount && patchUserAccount(userAccountPayload, "1.0.1").then(setUserAccount),
])
}

@ -0,0 +1,73 @@
import { TicketMessage } from "@frontend/kitui";
import { useUserStore } from "@root/stores/user";
interface UseMessageOwnershipProps {
message: TicketMessage;
sessionId?: string;
ticketUserId?: string;
}
interface UseMessageOwnershipResult {
isOwnMessage: boolean;
isUnread: boolean;
}
export const useMessageOwnership = ({
message,
sessionId,
ticketUserId
}: UseMessageOwnershipProps): UseMessageOwnershipResult => {
const userId = useUserStore((state) => state.user?._id);
// Определяем владельца сообщения
const isOwnMessage = (sessionId || userId || ticketUserId) === message.user_id;
// Сообщение считается непрочитанным если:
// 1. Оно не моё
// 2. Не отмечено как прочитанное (shown.me !== 1)
// 3. Не является системным (ticket_id !== "111")
const isUnread = !isOwnMessage && message.shown?.me !== 1 && message.ticket_id !== "111";
return {
isOwnMessage,
isUnread
};
};
interface UseUnreadMessagesCountProps {
messages: TicketMessage[];
sessionId?: string;
ticketUserId?: string;
}
export const useUnreadMessagesCount = ({
messages,
sessionId,
ticketUserId
}: UseUnreadMessagesCountProps): number => {
const userId = useUserStore((state) => state.user?._id);
// Считаем непрочитанные сообщения до первого прочитанного или своего
let unreadCount = 0;
for (const message of messages) {
const isOwnMessage = (sessionId || userId || ticketUserId) === message.user_id;
// Если это моё сообщение - прерываем подсчет
if (isOwnMessage) {
break;
}
// Если это чужое сообщение и оно не прочитано и не системное - увеличиваем счетчик
if (!isOwnMessage && message.shown?.me !== 1 && message.ticket_id !== "111") {
unreadCount++;
}
// Если встретили прочитанное сообщение - прерываем подсчет
if (message.shown?.me === 1) {
break;
}
}
return unreadCount;
};

@ -16,7 +16,6 @@ export function useCart(): CartData {
return calcCart(cartTariffs ?? [], discounts ?? [], purchasesAmount, userId, isUserNko);
}, [cartTariffs, discounts, purchasesAmount, userId, isUserNko]);
console.log("cart ", cart)
return cart;
}

5428
yarn.lock

File diff suppressed because it is too large Load Diff