add service and sertificate prices ,

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-18 13:50:32 +05:00
parent 75bd3467e9
commit aea8854a13
12 changed files with 112 additions and 66 deletions

View File

@@ -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…",

View File

@@ -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…",

View File

@@ -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';

View File

@@ -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"
},

View File

@@ -143,11 +143,19 @@ api.interceptors.response.use(
};
const status = error.response?.status;
// const responseData = error.response?.data as Record<string, unknown> | 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));

View File

@@ -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<Inspection[]> =>
@@ -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,6 +102,7 @@ export function PaymentsTable() {
<p className="text-sm">{t('noPayments')}</p>
</div>
) : (
<>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
@@ -115,9 +125,7 @@ export function PaymentsTable() {
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{data.map((row) => {
// const service_fee = row.total_price + row.discount;
return (
{pageItems!.map((row) => (
<tr
key={row.id}
className="hover:bg-slate-50/60 transition-colors"
@@ -155,25 +163,17 @@ export function PaymentsTable() {
</span>
)}
</td>
{/* <PaymentModal
isOpen={isPaymentOpen}
onClose={() => 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}
/> */}
</tr>
);
})}
))}
</tbody>
</table>
</div>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</>
)}
</div>
</div>

View File

@@ -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 (
<div className="space-y-4">
@@ -228,12 +238,21 @@ export const SiTable: React.FC = () => {
{!isLoading && !isError && items.length === 0 && <EmptyState />}
{!isLoading &&
!isError &&
items.map((item, i) => (
<SiRow key={item.id} item={item} index={i + 1} />
pageItems.map((item, i) => (
<SiRow
key={item.id}
item={item}
index={(currentPage - 1) * PAGE_SIZE + i + 1}
/>
))}
</tbody>
</table>
</div>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div>
</div>
);

View File

@@ -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();

View File

@@ -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');

View File

@@ -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 */}
<div className="flex flex-col gap-9 md:max-w-[50%] w-full">
<div className="flex flex-col gap-6 md:max-w-[50%] w-full">
{/* Topic */}
<FieldWrapper
label={t('documentTopic')}
@@ -131,6 +132,9 @@ export function PlagiarismCheckForm() {
maxLength={200}
disabled={isLoading}
/>
<p className="text-sm text-stone-500 ml-2 ">
{t('service_price', { PLAGIAT_SERVICE_FEE })}
</p>
</FieldWrapper>
{/* Sender Full Name (read-only) */}

View File

@@ -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 (
<label
className={`
@@ -260,6 +263,9 @@ export function CertificateCheckbox({
</div>
<div>
<p className="text-sm font-semibold text-stone-800">{title}</p>
<p className="text-sm text-stone-500">
{t('sertificate_price', { SERTIFICATE_PRICE })}
</p>
<p className="text-xs text-stone-500 mt-0.5">{description}</p>
</div>
</label>