diff --git a/src/shared/config/i18n/messages/en.json b/src/shared/config/i18n/messages/en.json index 27a3b05..00d46ce 100644 --- a/src/shared/config/i18n/messages/en.json +++ b/src/shared/config/i18n/messages/en.json @@ -53,7 +53,9 @@ "certificateDescription": "An official certificate will be attached to your originality report.", "submitting": "Submitting…", "submitButton": "Submit for Originality Check", - "dismiss": "Dismiss" + "dismiss": "Dismiss", + "service_price": "Service price {PLAGIAT_SERVICE_FEE} UZS", + "sertificate_price": "Certificate price {SERTIFICATE_PRICE} UZS" }, "HistoryPage": { "title": "Check History", @@ -63,8 +65,8 @@ "date": "Date", "amount": "Amount", "result": "Result", - "fileName":"File name", - "count":"N_", + "fileName": "File name", + "count": "N_", "actions": "", "state": "Payment status", "emptyMessage": "No plagiarism checks found.", @@ -239,7 +241,7 @@ "security": "Secured by Payme · SSL encrypted", "serviceFee": "Service fee", "discountLabel": "Discount", - "sertificateLabel":"Certificate", + "sertificateLabel": "Certificate", "total": "Total", "paymentRequired": "Payment not completed", "connecting": "Connecting to Payme…", diff --git a/src/shared/config/i18n/messages/ru.json b/src/shared/config/i18n/messages/ru.json index 6370e67..33f0672 100644 --- a/src/shared/config/i18n/messages/ru.json +++ b/src/shared/config/i18n/messages/ru.json @@ -53,7 +53,9 @@ "certificateDescription": "Официальный сертификат будет прикреплен к вашему отчету об оригинальности.", "submitting": "Отправка…", "submitButton": "Отправить на проверку оригинальности", - "dismiss": "Закрыть" + "dismiss": "Закрыть", + "service_price": "Стоимость услуги {PLAGIAT_SERVICE_FEE} сум", + "sertificate_price": "Стоимость сертификата {SERTIFICATE_PRICE} сум" }, "HistoryPage": { "title": "История проверок", @@ -63,7 +65,7 @@ "date": "Дата", "amount": "Сумма", "result": "Результат", - "count":"H_", + "count": "H_", "actions": "", "state": "Статус оплаты", "emptyMessage": "Проверки на плагиат не найдены.", @@ -238,7 +240,7 @@ "security": "Защищено Payme · SSL шифрование", "serviceFee": "Стоимость услуги", "discountLabel": "Скидка", - "sertificateLabel":"Сертификат", + "sertificateLabel": "Сертификат", "total": "Итого", "paymentRequired": "Оплата не произведена", "connecting": "Подключение к Payme…", diff --git a/src/shared/config/i18n/messages/uz.d.json.ts b/src/shared/config/i18n/messages/uz.d.json.ts index 5e7418e..076b287 100644 --- a/src/shared/config/i18n/messages/uz.d.json.ts +++ b/src/shared/config/i18n/messages/uz.d.json.ts @@ -57,6 +57,8 @@ declare const messages: { submitting: 'Yuborilmoqda…'; submitButton: 'Orijinallik tekshiruvi uchun yuborish'; dismiss: 'Yopish'; + service_price: "Xizmat narxi {PLAGIAT_SERVICE_FEE} so'm"; + sertificate_price: "Sertifikat narxi {SERTIFICATE_PRICE} so'm"; }; HistoryPage: { title: 'Tekshiruv tarixi'; diff --git a/src/shared/config/i18n/messages/uz.json b/src/shared/config/i18n/messages/uz.json index 2144c0e..0a2ce18 100644 --- a/src/shared/config/i18n/messages/uz.json +++ b/src/shared/config/i18n/messages/uz.json @@ -53,15 +53,17 @@ "certificateDescription": "Rasmiy sertifikat sizning orijinallik hisobotingizga ilova qilinadi.", "submitting": "Yuborilmoqda…", "submitButton": "Orijinallik tekshiruvi uchun yuborish", - "dismiss": "Yopish" + "dismiss": "Yopish", + "service_price": "Xizmat narxi {PLAGIAT_SERVICE_FEE} so'm", + "sertificate_price": "Sertifikat narxi {SERTIFICATE_PRICE} so'm" }, "HistoryPage": { "title": "Tekshiruv tarixi", "description": "Siz tomonidan yuborilgan barcha plagiat tekshiruvlari", "sender": "Yuboruvchi", "file": "Fayl", - "fileName":"Fayl nomi", - "count":"N_", + "fileName": "Fayl nomi", + "count": "N_", "date": "Sana", "amount": "Summa", "result": "Natija", @@ -128,8 +130,8 @@ "downloadCertificate": "Sertifikatni yuklab olish", "unknownError": "Noma'lum xato", "words": "so'z", - "aiProbabilityText":"Ai yordamida yaratilganlik ehtimoli aniqlandi", - "documentNumber":"Dokument mavzusi", + "aiProbabilityText": "Ai yordamida yaratilganlik ehtimoli aniqlandi", + "documentNumber": "Dokument mavzusi", "scoreAiContent": "O'zidan iqtibos keltirish", "scoreOriginality": "Originallik", "scorePlagiarism": "Plagiat", @@ -239,9 +241,9 @@ "security": "Payme tomonidan himoyalangan · SSL shifrlash", "serviceFee": "Xizmat to'lovi", "discountLabel": "Chegirma", - "sertificateLabel":"Sertifikat", + "sertificateLabel": "Sertifikat", "total": "Jami", - "paymentRequired":"To'lov qilinmagan", + "paymentRequired": "To'lov qilinmagan", "connecting": "Paymega ulanmoqda…", "payButton": "Payme orqali to'lash" }, diff --git a/src/shared/request/apiRequest.ts b/src/shared/request/apiRequest.ts index 8c06aa9..e1f9c92 100644 --- a/src/shared/request/apiRequest.ts +++ b/src/shared/request/apiRequest.ts @@ -143,11 +143,19 @@ api.interceptors.response.use( }; const status = error.response?.status; + // const responseData = error.response?.data as Record | undefined; const requestUrl = originalRequest.url ?? ''; const isAuthEndpoint = requestUrl.includes('/users/login/') || requestUrl.includes('/users/register/'); + // 403 with token_not_valid means the token is expired — clear and redirect + if (status === 403) { + TokenStorage.clear(); + redirectToMain(); + return Promise.reject(error); + } + // For auth endpoints, 401 means wrong credentials — show error, don't refresh if (isAuthEndpoint || status !== 401 || originalRequest._retry) { toast.error(extractErrorMessage(error)); diff --git a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx index ee5af60..fea1139 100644 --- a/src/widgets/cabinet/ui/tables/PaymentsTable.tsx +++ b/src/widgets/cabinet/ui/tables/PaymentsTable.tsx @@ -1,11 +1,12 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { Clock, XCircle, ReceiptText } from 'lucide-react'; import { useQuery } from '@tanstack/react-query'; import { useTranslations } from 'next-intl'; import { apiRequest } from '@/shared/request/apiRequest'; import { links } from '@/shared/request/links'; import PaymentStatus from '@/widgets/detail/paidStatus'; +import { Pagination } from '@/widgets/history/ui/pagination'; // import { toast } from 'react-toastify'; // import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; @@ -36,9 +37,11 @@ function formatPrice(price: string) { // ─── Component ───────────────────────────────────────────────────────────────── +const PAGE_SIZE = 10; + export function PaymentsTable() { const t = useTranslations('Cabinet'); - // const [isPaymentOpen, setIsPaymentOpen] = useState(false); + const [currentPage, setCurrentPage] = useState(1); const { data, isLoading } = useQuery({ queryKey: ['pay_history'], queryFn: (): Promise => @@ -47,6 +50,12 @@ export function PaymentsTable() { ), }); + const totalPages = Math.ceil((data?.length ?? 0) / PAGE_SIZE); + const pageItems = data?.slice( + (currentPage - 1) * PAGE_SIZE, + currentPage * PAGE_SIZE, + ); + // const payment = useMutation({ // mutationKey: ['payload'], // mutationFn: ({ order_id }: { order_id: number }) => @@ -93,31 +102,30 @@ export function PaymentsTable() {

{t('noPayments')}

) : ( -
- - - - {[ - t('tableNum'), - t('service'), - t('amount'), - t('discount'), - t('date'), - t('status'), - ].map((h) => ( - - ))} - - - - {data.map((row) => { - // const service_fee = row.total_price + row.discount; - return ( + <> +
+
- {h} -
+ + + {[ + t('tableNum'), + t('service'), + t('amount'), + t('discount'), + t('date'), + t('status'), + ].map((h) => ( + + ))} + + + + {pageItems!.map((row) => ( )} - {/* setIsPaymentOpen(false)} - price={{ - service_fee: Number(service_fee), - discount: Number(row.discount) || 0, - total_price: Number(row.total_price) || 0, - }} - onConfirmPayment={() => { - handleSubmit({ document_id: 0 }); - }} - isLoading={payment.isPending} - /> */} - ); - })} - -
+ {h} +
-
+ ))} + + + + + )} diff --git a/src/widgets/cabinet/ui/tables/SiTable.tsx b/src/widgets/cabinet/ui/tables/SiTable.tsx index 6aef4d3..73f5846 100644 --- a/src/widgets/cabinet/ui/tables/SiTable.tsx +++ b/src/widgets/cabinet/ui/tables/SiTable.tsx @@ -1,5 +1,5 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { Download, CreditCard, Eye } from 'lucide-react'; import { useMutation } from '@tanstack/react-query'; import { useTranslations } from 'next-intl'; @@ -10,6 +10,7 @@ import { links } from '@/shared/request/links'; import { toast } from 'react-toastify'; import type { SiDocument } from '../../lib/types'; import { useRouter, useParams } from 'next/navigation'; +import { Pagination } from '@/widgets/history/ui/pagination'; // ─── State badge ─────────────────────────────────────────────────────────────── @@ -180,9 +181,18 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({ // ─── SiTable ─────────────────────────────────────────────────────────────────── +const PAGE_SIZE = 10; + export const SiTable: React.FC = () => { const t = useTranslations('Cabinet'); const { items, isLoading, isError } = useSiHistory(); + const [currentPage, setCurrentPage] = useState(1); + + const totalPages = Math.ceil(items.length / PAGE_SIZE); + const pageItems = items.slice( + (currentPage - 1) * PAGE_SIZE, + currentPage * PAGE_SIZE, + ); return (
@@ -228,12 +238,21 @@ export const SiTable: React.FC = () => { {!isLoading && !isError && items.length === 0 && } {!isLoading && !isError && - items.map((item, i) => ( - + pageItems.map((item, i) => ( + ))}
+ ); diff --git a/src/widgets/history/lib/useHistory.ts b/src/widgets/history/lib/useHistory.ts index 3bf4fc2..702fa69 100644 --- a/src/widgets/history/lib/useHistory.ts +++ b/src/widgets/history/lib/useHistory.ts @@ -36,14 +36,15 @@ export const useHistory = (pageSize = DEFAULT_PAGE_SIZE): UseHistoryReturn => { useEffect(() => { if (data) { + const start = (currentPage - 1) * pageSize; setState({ - items: data?.results || [], + items: (data.results || []).slice(start, start + pageSize), status: 'success', error: null, }); setTotal(data.total || 0); } - }, [data]); + }, [data, currentPage, pageSize]); useEffect(() => { refetch(); diff --git a/src/widgets/home/index.tsx b/src/widgets/home/index.tsx index 441e3c8..cce3b1e 100644 --- a/src/widgets/home/index.tsx +++ b/src/widgets/home/index.tsx @@ -9,7 +9,7 @@ import { useRouter } from '@/shared/config/i18n/navigation'; const PlagiarismLanding = () => { const route = useRouter(); useEffect(() => { - const data = localStorage.getItem('user'); + const data = localStorage.getItem('access_token'); if (data) { route.push('/plagiat'); diff --git a/src/widgets/plagiatCheck/index.ts b/src/widgets/plagiatCheck/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx index afb9ae1..b6dacb1 100644 --- a/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx +++ b/src/widgets/plagiatCheck/ui/Plagiraismcheckform.tsx @@ -13,6 +13,7 @@ import { usePlagiarismForm } from '../lib/usePlagiraism'; import { useTranslations } from 'next-intl'; import { PaymentModal } from '@/features/modals/paymentModal/ui/Paymentmodal'; import DocumentsTypes from './documentsType'; +import { PLAGIAT_SERVICE_FEE } from '@/shared/lib/metadata'; export const inputCls = ` w-full px-3.5 py-3.5 text-[14px] text-slate-800 @@ -113,7 +114,7 @@ export function PlagiarismCheckForm() { )} {/* left part */} -
+
{/* Topic */} +

+ {t('service_price', { PLAGIAT_SERVICE_FEE })} +

{/* Sender Full Name (read-only) */} diff --git a/src/widgets/plagiatCheck/ui/Plagiraismui.tsx b/src/widgets/plagiatCheck/ui/Plagiraismui.tsx index b7c1634..b7052a1 100644 --- a/src/widgets/plagiatCheck/ui/Plagiraismui.tsx +++ b/src/widgets/plagiatCheck/ui/Plagiraismui.tsx @@ -1,3 +1,5 @@ +import { SERTIFICATE_PRICE } from '@/shared/lib/metadata'; +import { useTranslations } from 'next-intl'; import React, { useEffect } from 'react'; // ─── FieldWrapper ──────────────────────────────────────────────────────────── @@ -219,6 +221,7 @@ export function CertificateCheckbox({ title = 'Return result with certificate', description = 'An official certificate will be attached to your originality report.', }: CertificateCheckboxProps) { + const t = useTranslations('PlagiarismCheck'); return (