add service and sertificate prices ,
This commit is contained in:
@@ -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…",
|
||||
|
||||
@@ -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…",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,31 +102,30 @@ export function PaymentsTable() {
|
||||
<p className="text-sm">{t('noPayments')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-slate-50 border-b border-slate-100">
|
||||
{[
|
||||
t('tableNum'),
|
||||
t('service'),
|
||||
t('amount'),
|
||||
t('discount'),
|
||||
t('date'),
|
||||
t('status'),
|
||||
].map((h) => (
|
||||
<th
|
||||
key={h}
|
||||
className="text-left px-5 py-3 text-[11px] font-semibold text-slate-400 uppercase tracking-wider whitespace-nowrap"
|
||||
>
|
||||
{h}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-50">
|
||||
{data.map((row) => {
|
||||
// const service_fee = row.total_price + row.discount;
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-slate-50 border-b border-slate-100">
|
||||
{[
|
||||
t('tableNum'),
|
||||
t('service'),
|
||||
t('amount'),
|
||||
t('discount'),
|
||||
t('date'),
|
||||
t('status'),
|
||||
].map((h) => (
|
||||
<th
|
||||
key={h}
|
||||
className="text-left px-5 py-3 text-[11px] font-semibold text-slate-400 uppercase tracking-wider whitespace-nowrap"
|
||||
>
|
||||
{h}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-50">
|
||||
{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>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={setCurrentPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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) */}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user