last push
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
Loader2,
|
||||
CheckCircle2,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useCertificateModal } from './useSertificateModal';
|
||||
import { Field, inputCls } from './modalField';
|
||||
import { SertificateModalProps } from './types';
|
||||
@@ -20,6 +20,7 @@ export default function SertificateModal({
|
||||
open,
|
||||
setOpen,
|
||||
}: SertificateModalProps) {
|
||||
const t = useTranslations('CertificateModal');
|
||||
const {
|
||||
form,
|
||||
updateField,
|
||||
@@ -44,7 +45,7 @@ export default function SertificateModal({
|
||||
onKeyDown={handleKeyDown}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Sertifikat yaratish"
|
||||
aria-label={t('title')}
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
@@ -75,7 +76,7 @@ export default function SertificateModal({
|
||||
<Award className="w-5 h-5 text-emerald-600" strokeWidth={1.8} />
|
||||
</div>
|
||||
<h2 className="text-[17px] font-semibold text-slate-800 tracking-tight">
|
||||
Sertifikat yaratish
|
||||
{t('title')}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
@@ -84,7 +85,7 @@ export default function SertificateModal({
|
||||
className="flex items-center justify-center w-8 h-8 rounded-lg
|
||||
text-slate-400 hover:text-slate-600 hover:bg-slate-100
|
||||
transition-all duration-150 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
aria-label="Yopish"
|
||||
aria-label={t('close')}
|
||||
>
|
||||
<X className="w-4 h-4" strokeWidth={2.2} />
|
||||
</button>
|
||||
@@ -101,7 +102,7 @@ export default function SertificateModal({
|
||||
icon={
|
||||
<User className="w-3.5 h-3.5 text-slate-400" strokeWidth={2} />
|
||||
}
|
||||
label="Muallifning to'liq ismi"
|
||||
label={t('authorName')}
|
||||
>
|
||||
<input
|
||||
id="fullname"
|
||||
@@ -110,7 +111,7 @@ export default function SertificateModal({
|
||||
value={form.fullname}
|
||||
onChange={(e) => updateField('fullname', e.target.value)}
|
||||
disabled={loading || success}
|
||||
placeholder="Ismingizni kiriting..."
|
||||
placeholder={t('namePlaceholder')}
|
||||
className={inputCls}
|
||||
/>
|
||||
</Field>
|
||||
@@ -124,7 +125,7 @@ export default function SertificateModal({
|
||||
strokeWidth={2}
|
||||
/>
|
||||
}
|
||||
label="Hujjat mavzusi"
|
||||
label={t('documentTheme')}
|
||||
>
|
||||
<input
|
||||
id="document_theme"
|
||||
@@ -132,15 +133,15 @@ export default function SertificateModal({
|
||||
value={form.document_theme}
|
||||
onChange={(e) => updateField('document_theme', e.target.value)}
|
||||
disabled={loading || success}
|
||||
placeholder="Mavzuni kiriting..."
|
||||
placeholder={t('themePlaceholder')}
|
||||
className={inputCls}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{/* Document type */}
|
||||
<DocumentsTypes
|
||||
value={String(form.document_type)}
|
||||
onChange={(val) => updateField('document_type', val)}
|
||||
value={form.type}
|
||||
onChange={(val) => updateField('type', val)}
|
||||
disabled={loading || success}
|
||||
/>
|
||||
|
||||
@@ -151,7 +152,9 @@ export default function SertificateModal({
|
||||
strokeWidth={1.8}
|
||||
/>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span className="text-[13px] text-slate-500">Hujjat ID</span>
|
||||
<span className="text-[13px] text-slate-500">
|
||||
{t('documentId')}
|
||||
</span>
|
||||
<span className="text-[13px] font-mono font-medium text-slate-700">
|
||||
#{document_id}
|
||||
</span>
|
||||
@@ -177,15 +180,15 @@ export default function SertificateModal({
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" strokeWidth={2.5} />
|
||||
<span>Yaratilmoqda...</span>
|
||||
<span>{t('creating')}</span>
|
||||
</>
|
||||
) : success ? (
|
||||
<>
|
||||
<CheckCircle2 className="w-4 h-4" strokeWidth={2.5} />
|
||||
<span>Sertifikat yaratildi!</span>
|
||||
<span>{t('created')}</span>
|
||||
</>
|
||||
) : (
|
||||
<span>Sertifikat yaratish</span>
|
||||
<span>{t('create')}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface CertificateFormData {
|
||||
fullname: string;
|
||||
document_theme: string;
|
||||
document_type: string | number;
|
||||
type: number;
|
||||
document_id: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
import { apiRequest } from '@/shared/request/apiRequest';
|
||||
import { links } from '@/shared/request/links';
|
||||
import { CertificateFormData } from './types';
|
||||
import { SIPaymentResponse } from '../siModal/utils/useFileUpload';
|
||||
|
||||
interface UseCertificateModalProps {
|
||||
document_id: number;
|
||||
@@ -24,13 +26,21 @@ export function useCertificateModal({
|
||||
const [form, setForm] = useState<CertificateFormData>({
|
||||
fullname: '',
|
||||
document_theme: '',
|
||||
document_type: '',
|
||||
type: 0,
|
||||
document_id,
|
||||
});
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const payment = useMutation({
|
||||
mutationFn: (id: number) =>
|
||||
apiRequest<SIPaymentResponse>('POST', links.demo_pay(id)),
|
||||
onSuccess: (res) => {
|
||||
window.open(res?.data?.payment_link, '_self');
|
||||
},
|
||||
});
|
||||
|
||||
const certificateMutation = useMutation({
|
||||
mutationFn: (payload: CertificatePayload) =>
|
||||
apiRequest('POST', links.sertifikat(document_id), payload).then(
|
||||
@@ -53,13 +63,18 @@ export function useCertificateModal({
|
||||
resetForm();
|
||||
}, 1500);
|
||||
},
|
||||
onError: (error: AxiosError<{ code?: string }>) => {
|
||||
if (error?.response?.data?.code === 'not_paid') {
|
||||
payment.mutate(document_id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const resetForm = () => {
|
||||
setForm({
|
||||
fullname: '',
|
||||
document_theme: '',
|
||||
document_type: '',
|
||||
type: 0,
|
||||
document_id,
|
||||
});
|
||||
};
|
||||
@@ -90,16 +105,14 @@ export function useCertificateModal({
|
||||
) => setForm((prev) => ({ ...prev, [field]: value }));
|
||||
|
||||
const isFormValid =
|
||||
!!form.fullname.trim() &&
|
||||
!!form.document_theme.trim() &&
|
||||
!!form.document_type;
|
||||
!!form.fullname.trim() && !!form.document_theme.trim() && !!form.type;
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!isFormValid || certificateMutation.isPending) return;
|
||||
certificateMutation.mutate({
|
||||
full_name: form.fullname,
|
||||
file_name: form.document_theme,
|
||||
document_type: Number(form.document_type),
|
||||
document_type: Number(form.type),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import { useState } from 'react';
|
||||
import { ArrowRight, BrainCircuit, Plus } from 'lucide-react';
|
||||
import { FileUploadModal } from './ui/fileUploadModal';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export function SiCTACard() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const t = useTranslations('Cabinet');
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -17,12 +19,14 @@ export function SiCTACard() {
|
||||
<BrainCircuit size={72} className="text-white" />
|
||||
</div>
|
||||
<BrainCircuit size={26} className="text-white mb-4" />
|
||||
<h3 className="text-white font-semibold text-base mb-1">SI detektor</h3>
|
||||
<h3 className="text-white font-semibold text-base mb-1">
|
||||
{t('siDetector')}
|
||||
</h3>
|
||||
<p className="text-violet-100 text-sm mb-4 leading-relaxed">
|
||||
Matnni sun'iy intellekt uchun tekshiring
|
||||
{t('siDesc')}
|
||||
</p>
|
||||
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
||||
Yuborish <ArrowRight size={12} />
|
||||
{t('submit')} <ArrowRight size={12} />
|
||||
</span>
|
||||
</button>
|
||||
<FileUploadModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
||||
@@ -32,6 +36,7 @@ export function SiCTACard() {
|
||||
|
||||
export function SiButton() {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const t = useTranslations('Cabinet');
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -39,7 +44,9 @@ export function SiButton() {
|
||||
className="flex items-center gap-2 py-1 px-2 group relative overflow-hidden rounded-sm bg-linear-to-br from-violet-500 to-violet-600 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
||||
>
|
||||
<Plus size={15} className="text-white" />
|
||||
<h3 className="text-white font-semibold text-base">SI detektor</h3>
|
||||
<h3 className="text-white font-semibold text-base">
|
||||
{t('siDetector')}
|
||||
</h3>
|
||||
</button>
|
||||
<FileUploadModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
||||
</>
|
||||
|
||||
@@ -20,7 +20,7 @@ interface CreateSIOrderResponse {
|
||||
order_id: number;
|
||||
}
|
||||
|
||||
interface SIPaymentResponse {
|
||||
export interface SIPaymentResponse {
|
||||
payment_link: string;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function useFileUpload(): UseFileUploadReturn {
|
||||
const siPayment = useMutation({
|
||||
mutationKey: ['si-payment'],
|
||||
mutationFn: (order_id: number) =>
|
||||
apiRequest<SIPaymentResponse>('POST', links.si_payment(order_id)),
|
||||
apiRequest<SIPaymentResponse>('POST', links.demo_pay(order_id)),
|
||||
onSuccess: (res) => {
|
||||
window.open(res.data.payment_link, '_self');
|
||||
},
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
"resultClean": "Clean",
|
||||
"resultPlagiarismFound": "Plagiarism Found",
|
||||
"resultPending": "Pending",
|
||||
"resultFailed": "Failed"
|
||||
"resultFailed": "Failed",
|
||||
"plagiatCheck": "Plagiarism Check"
|
||||
},
|
||||
"DetailPage": {
|
||||
"id": "ID",
|
||||
@@ -126,7 +127,11 @@
|
||||
"unknownError": "Unknown error",
|
||||
"words": "words",
|
||||
"aiProbabilityText": "Probability that the text was generated with AI has been detected",
|
||||
"documentNumber": "Document subject"
|
||||
"documentNumber": "Document subject",
|
||||
"scoreAiContent": "Self-citation",
|
||||
"scoreOriginality": "Originality",
|
||||
"scorePlagiarism": "Plagiarism",
|
||||
"scoreCitation": "Citation"
|
||||
},
|
||||
"Hero": {
|
||||
"badge": "Academic Integrity Platform",
|
||||
@@ -240,5 +245,68 @@
|
||||
},
|
||||
"unknownUser": "Username not found",
|
||||
"file": "File",
|
||||
"upload": "Download certificate"
|
||||
"upload": "Download certificate",
|
||||
"Cabinet": {
|
||||
"plagiatCheck": "Plagiarism Check",
|
||||
"checkDesc": "Check your document for originality",
|
||||
"submit": "Submit",
|
||||
"siDetector": "AI Detector",
|
||||
"siDesc": "Check text for AI content",
|
||||
"home": "Home",
|
||||
"plagiat": "Plagiarism",
|
||||
"siNav": "AI Detector",
|
||||
"payments": "Payment History",
|
||||
"profile": "Profile",
|
||||
"close": "Close",
|
||||
"personalCabinet": "Personal Cabinet",
|
||||
"plagiatChecks": "Plagiarism Checks",
|
||||
"dashboard": "Dashboard"
|
||||
},
|
||||
"SiDetail": {
|
||||
"siCheck": "AI Check",
|
||||
"basicInfo": "Basic Information",
|
||||
"documentInfo": "Document Information",
|
||||
"documentName": "Document Name",
|
||||
"checker": "Checker",
|
||||
"uploadedAt": "Document Upload Time",
|
||||
"originalFileName": "Original File Name",
|
||||
"wordCount": "Word Count",
|
||||
"fileExt": "File Extension",
|
||||
"fileSize": "File Size",
|
||||
"amountCharged": "Amount Charged",
|
||||
"downloadOriginal": "Download Original Document",
|
||||
"siResultsTitle": "Document AI Detector Results",
|
||||
"siResultsDesc": "This window displays the analysis results of the text uploaded by the user regarding the probability of being written with the help of artificial intelligence. The detector evaluates the stylistic, grammatical and semantic features of the text and shows in percentage how likely it was generated by artificial intelligence.",
|
||||
"download": "Download",
|
||||
"originalText": "Original Text",
|
||||
"possibleAi": "Possible AI",
|
||||
"aiContent": "Artificial Intelligence"
|
||||
},
|
||||
"PlagiatResult": {
|
||||
"plagiarismLevel": "Plagiarism Level",
|
||||
"aiWritten": "AI Written",
|
||||
"originality": "Originality",
|
||||
"citation": "Citation",
|
||||
"plagiat": "Plagiarism",
|
||||
"aiGeneration": "AI Generation",
|
||||
"original": "Original",
|
||||
"checked": "Checked"
|
||||
},
|
||||
"CertificateModal": {
|
||||
"title": "Create Certificate",
|
||||
"close": "Close",
|
||||
"authorName": "Author's Full Name",
|
||||
"namePlaceholder": "Enter your name...",
|
||||
"documentTheme": "Document Theme",
|
||||
"themePlaceholder": "Enter the topic...",
|
||||
"documentId": "Document ID",
|
||||
"creating": "Creating...",
|
||||
"created": "Certificate Created!",
|
||||
"create": "Create Certificate"
|
||||
},
|
||||
"DocumentTypes": {
|
||||
"label": "Document Type",
|
||||
"loading": "Loading...",
|
||||
"placeholder": "Select document type..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
"resultClean": "Чисто",
|
||||
"resultPlagiarismFound": "Обнаружен плагиат",
|
||||
"resultPending": "В ожидании",
|
||||
"resultFailed": "Не удалось"
|
||||
"resultFailed": "Не удалось",
|
||||
"plagiatCheck": "Проверка на плагиат"
|
||||
},
|
||||
"DetailPage": {
|
||||
"id": "ID",
|
||||
@@ -125,7 +126,11 @@
|
||||
"unknownError": "Неизвестная ошибка",
|
||||
"words": "слов",
|
||||
"aiProbabilityText": "Обнаружена вероятность того, что текст создан с помощью ИИ",
|
||||
"documentNumber": "Тема документа"
|
||||
"documentNumber": "Тема документа",
|
||||
"scoreAiContent": "Самоцитирование",
|
||||
"scoreOriginality": "Оригинальность",
|
||||
"scorePlagiarism": "Плагиат",
|
||||
"scoreCitation": "Цитирование"
|
||||
},
|
||||
"Hero": {
|
||||
"badge": "Платформа академической честности",
|
||||
@@ -239,5 +244,68 @@
|
||||
},
|
||||
"unknownUser": "Имя пользователя не найдено",
|
||||
"file": "Файл",
|
||||
"upload": "Скачать сертификат"
|
||||
"upload": "Скачать сертификат",
|
||||
"Cabinet": {
|
||||
"plagiatCheck": "Проверка на плагиат",
|
||||
"checkDesc": "Проверьте ваш документ на оригинальность",
|
||||
"submit": "Отправить",
|
||||
"siDetector": "ИИ детектор",
|
||||
"siDesc": "Проверьте текст на искусственный интеллект",
|
||||
"home": "Главная",
|
||||
"plagiat": "Плагиат",
|
||||
"siNav": "ИИ детектор",
|
||||
"payments": "История платежей",
|
||||
"profile": "Профиль",
|
||||
"close": "Закрыть",
|
||||
"personalCabinet": "Личный кабинет",
|
||||
"plagiatChecks": "Проверки на плагиат",
|
||||
"dashboard": "Dashboard"
|
||||
},
|
||||
"SiDetail": {
|
||||
"siCheck": "Проверка ИИ",
|
||||
"basicInfo": "Основная информация",
|
||||
"documentInfo": "Информация о документе",
|
||||
"documentName": "Название документа",
|
||||
"checker": "Проверяющий",
|
||||
"uploadedAt": "Дата загрузки документа",
|
||||
"originalFileName": "Оригинальное имя файла",
|
||||
"wordCount": "Количество слов",
|
||||
"fileExt": "Расширение файла",
|
||||
"fileSize": "Размер файла",
|
||||
"amountCharged": "Списанная сумма",
|
||||
"downloadOriginal": "Скачать оригинальный документ",
|
||||
"siResultsTitle": "Результаты ИИ детектора документа",
|
||||
"siResultsDesc": "В этом окне отображены результаты анализа текста, загруженного пользователем, на вероятность написания с помощью искусственного интеллекта. Детектор оценивает стилистические, грамматические и семантические особенности текста и показывает в процентах, насколько вероятно, что он был сгенерирован искусственным интеллектом.",
|
||||
"download": "Скачать",
|
||||
"originalText": "Оригинальный текст",
|
||||
"possibleAi": "Возможный ИИ",
|
||||
"aiContent": "Искусственный интеллект"
|
||||
},
|
||||
"PlagiatResult": {
|
||||
"plagiarismLevel": "Уровень плагиата",
|
||||
"aiWritten": "Написано ИИ",
|
||||
"originality": "Оригинальность",
|
||||
"citation": "Цитирование",
|
||||
"plagiat": "Плагиат",
|
||||
"aiGeneration": "Генерация ИИ",
|
||||
"original": "Оригинал",
|
||||
"checked": "Проверено"
|
||||
},
|
||||
"CertificateModal": {
|
||||
"title": "Создать сертификат",
|
||||
"close": "Закрыть",
|
||||
"authorName": "Полное имя автора",
|
||||
"namePlaceholder": "Введите ваше имя...",
|
||||
"documentTheme": "Тема документа",
|
||||
"themePlaceholder": "Введите тему...",
|
||||
"documentId": "ID документа",
|
||||
"creating": "Создание...",
|
||||
"created": "Сертификат создан!",
|
||||
"create": "Создать сертификат"
|
||||
},
|
||||
"DocumentTypes": {
|
||||
"label": "Тип документа",
|
||||
"loading": "Загрузка...",
|
||||
"placeholder": "Выберите тип документа..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ declare const messages: {
|
||||
resultPlagiarismFound: 'Plagiat topildi';
|
||||
resultPending: 'Kutilmoqda';
|
||||
resultFailed: 'Muvaffaqiyatsiz';
|
||||
plagiatCheck: 'Plagiat tekshiruvi';
|
||||
};
|
||||
DetailPage: {
|
||||
id: 'ID';
|
||||
@@ -130,6 +131,10 @@ declare const messages: {
|
||||
words: "so'z";
|
||||
aiProbabilityText: 'Ai yordamida yaratilganlik ehtimoli aniqlandi';
|
||||
documentNumber: 'Dokument mavzusi';
|
||||
scoreAiContent: "O'zidan iqtibos keltirish";
|
||||
scoreOriginality: 'Originallik';
|
||||
scorePlagiarism: 'Plagiat';
|
||||
scoreCitation: 'Iqtibos';
|
||||
};
|
||||
Hero: {
|
||||
badge: 'Akademik halollik platformasi';
|
||||
@@ -244,5 +249,68 @@ declare const messages: {
|
||||
unknownUser: 'Foydalanuvchi topilmadi';
|
||||
file: 'Fayl';
|
||||
upload: 'Sertifikatni yuklab olish';
|
||||
Cabinet: {
|
||||
plagiatCheck: 'Plagiat tekshiruvi';
|
||||
checkDesc: 'Hujjatingizni originallik uchun tekshiring';
|
||||
submit: 'Yuborish';
|
||||
siDetector: 'SI detektor';
|
||||
siDesc: "Matnni sun'iy intellekt uchun tekshiring";
|
||||
home: 'Bosh sahifa';
|
||||
plagiat: 'Plagiat';
|
||||
siNav: 'SI detektor';
|
||||
payments: "To'lovlar tarixi";
|
||||
profile: 'Profil';
|
||||
close: 'Yopish';
|
||||
personalCabinet: 'Shaxsiy kabinet';
|
||||
plagiatChecks: 'Plagiat tekshiruvlar';
|
||||
dashboard: 'Dashboard';
|
||||
};
|
||||
SiDetail: {
|
||||
siCheck: 'SI tekshiruv';
|
||||
basicInfo: "Asosiy ma'lumotlar";
|
||||
documentInfo: "Hujjat haqida ma'lumotlar";
|
||||
documentName: 'Hujjat nomi';
|
||||
checker: 'Tekshiruvchi';
|
||||
uploadedAt: 'Hujjat yuklangan vaqti';
|
||||
originalFileName: 'Hujjat faylining original nomi';
|
||||
wordCount: "So'zlar soni";
|
||||
fileExt: 'Hujjat fayli kenggaytmasi';
|
||||
fileSize: "Hujjat fayli o'lchami";
|
||||
amountCharged: 'Yechilgan summa';
|
||||
downloadOriginal: 'Original hujjatni yuklab olish';
|
||||
siResultsTitle: 'Hujjatning SI detektori natijalari';
|
||||
siResultsDesc: "Ushbu oynada foydalanuvchi tomonidan yuklangan matn sun'iy intellekt (SI) yordamida yozilgan bo'lish ehtimoli bo'yicha tahlil natijalari aks etirilgan. Detektor matnning stilistik, grammatik va semantik xususiyatlarini baholab, uning qanchalik darajada sun'iy intellekt tomonidan generatsiya qilingan bo'lishi mumkinligini foizlik ko'rinishida ko'rsatadi.";
|
||||
download: 'Yuklab olish';
|
||||
originalText: 'Original matn';
|
||||
possibleAi: "Ehtimoliy Sun'iy Intellekt";
|
||||
aiContent: "Sun'iy Intellekt";
|
||||
};
|
||||
PlagiatResult: {
|
||||
plagiarismLevel: 'Plagiat darajasi';
|
||||
aiWritten: 'AI yozgan';
|
||||
originality: 'Originallik';
|
||||
citation: 'Iqtibos';
|
||||
plagiat: 'Plagiat';
|
||||
aiGeneration: 'AI generatsiya';
|
||||
original: 'Original';
|
||||
checked: 'Tekshirilgan';
|
||||
};
|
||||
CertificateModal: {
|
||||
title: 'Sertifikat yaratish';
|
||||
close: 'Yopish';
|
||||
authorName: "Muallifning to'liq ismi";
|
||||
namePlaceholder: 'Ismingizni kiriting...';
|
||||
documentTheme: 'Hujjat mavzusi';
|
||||
themePlaceholder: 'Mavzuni kiriting...';
|
||||
documentId: 'Hujjat ID';
|
||||
creating: 'Yaratilmoqda...';
|
||||
created: 'Sertifikat yaratildi!';
|
||||
create: 'Sertifikat yaratish';
|
||||
};
|
||||
DocumentTypes: {
|
||||
label: 'Hujjat turi';
|
||||
loading: 'Yuklanmoqda...';
|
||||
placeholder: 'Hujjat turini tanlang...';
|
||||
};
|
||||
};
|
||||
export default messages;
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
"resultClean": "Toza",
|
||||
"resultPlagiarismFound": "Plagiat topildi",
|
||||
"resultPending": "Kutilmoqda",
|
||||
"resultFailed": "Muvaffaqiyatsiz"
|
||||
"resultFailed": "Muvaffaqiyatsiz",
|
||||
"plagiatCheck": "Plagiat tekshiruvi"
|
||||
},
|
||||
"DetailPage": {
|
||||
"id": "ID",
|
||||
@@ -126,7 +127,11 @@
|
||||
"unknownError": "Noma'lum xato",
|
||||
"words": "so'z",
|
||||
"aiProbabilityText":"Ai yordamida yaratilganlik ehtimoli aniqlandi",
|
||||
"documentNumber":"Dokument mavzusi"
|
||||
"documentNumber":"Dokument mavzusi",
|
||||
"scoreAiContent": "O'zidan iqtibos keltirish",
|
||||
"scoreOriginality": "Originallik",
|
||||
"scorePlagiarism": "Plagiat",
|
||||
"scoreCitation": "Iqtibos"
|
||||
},
|
||||
"Hero": {
|
||||
"badge": "Akademik halollik platformasi",
|
||||
@@ -240,5 +245,68 @@
|
||||
},
|
||||
"unknownUser": "Foydalanuvchi topilmadi",
|
||||
"file": "Fayl",
|
||||
"upload": "Sertifikatni yuklab olish"
|
||||
"upload": "Sertifikatni yuklab olish",
|
||||
"Cabinet": {
|
||||
"plagiatCheck": "Plagiat tekshiruvi",
|
||||
"checkDesc": "Hujjatingizni originallik uchun tekshiring",
|
||||
"submit": "Yuborish",
|
||||
"siDetector": "SI detektor",
|
||||
"siDesc": "Matnni sun'iy intellekt uchun tekshiring",
|
||||
"home": "Bosh sahifa",
|
||||
"plagiat": "Plagiat",
|
||||
"siNav": "SI detektor",
|
||||
"payments": "To'lovlar tarixi",
|
||||
"profile": "Profil",
|
||||
"close": "Yopish",
|
||||
"personalCabinet": "Shaxsiy kabinet",
|
||||
"plagiatChecks": "Plagiat tekshiruvlar",
|
||||
"dashboard": "Dashboard"
|
||||
},
|
||||
"SiDetail": {
|
||||
"siCheck": "SI tekshiruv",
|
||||
"basicInfo": "Asosiy ma'lumotlar",
|
||||
"documentInfo": "Hujjat haqida ma'lumotlar",
|
||||
"documentName": "Hujjat nomi",
|
||||
"checker": "Tekshiruvchi",
|
||||
"uploadedAt": "Hujjat yuklangan vaqti",
|
||||
"originalFileName": "Hujjat faylining original nomi",
|
||||
"wordCount": "So'zlar soni",
|
||||
"fileExt": "Hujjat fayli kenggaytmasi",
|
||||
"fileSize": "Hujjat fayli o'lchami",
|
||||
"amountCharged": "Yechilgan summa",
|
||||
"downloadOriginal": "Original hujjatni yuklab olish",
|
||||
"siResultsTitle": "Hujjatning SI detektori natijalari",
|
||||
"siResultsDesc": "Ushbu oynada foydalanuvchi tomonidan yuklangan matn sun'iy intellekt (SI) yordamida yozilgan bo'lish ehtimoli bo'yicha tahlil natijalari aks etirilgan. Detektor matnning stilistik, grammatik va semantik xususiyatlarini baholab, uning qanchalik darajada sun'iy intellekt tomonidan generatsiya qilingan bo'lishi mumkinligini foizlik ko'rinishida ko'rsatadi.",
|
||||
"download": "Yuklab olish",
|
||||
"originalText": "Original matn",
|
||||
"possibleAi": "Ehtimoliy Sun'iy Intellekt",
|
||||
"aiContent": "Sun'iy Intellekt"
|
||||
},
|
||||
"PlagiatResult": {
|
||||
"plagiarismLevel": "Plagiat darajasi",
|
||||
"aiWritten": "AI yozgan",
|
||||
"originality": "Originallik",
|
||||
"citation": "Iqtibos",
|
||||
"plagiat": "Plagiat",
|
||||
"aiGeneration": "AI generatsiya",
|
||||
"original": "Original",
|
||||
"checked": "Tekshirilgan"
|
||||
},
|
||||
"CertificateModal": {
|
||||
"title": "Sertifikat yaratish",
|
||||
"close": "Yopish",
|
||||
"authorName": "Muallifning to'liq ismi",
|
||||
"namePlaceholder": "Ismingizni kiriting...",
|
||||
"documentTheme": "Hujjat mavzusi",
|
||||
"themePlaceholder": "Mavzuni kiriting...",
|
||||
"documentId": "Hujjat ID",
|
||||
"creating": "Yaratilmoqda...",
|
||||
"created": "Sertifikat yaratildi!",
|
||||
"create": "Sertifikat yaratish"
|
||||
},
|
||||
"DocumentTypes": {
|
||||
"label": "Hujjat turi",
|
||||
"loading": "Yuklanmoqda...",
|
||||
"placeholder": "Hujjat turini tanlang..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,5 +195,7 @@ export const apiRequest = async <T>(
|
||||
},
|
||||
});
|
||||
|
||||
console.log('resposne: ', response);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -6,15 +6,16 @@ export const links = {
|
||||
detail: (id: number) => `/shared/documents/${id}/`,
|
||||
payment: (order_id: number) => `/users/payme/link/${order_id}/`,
|
||||
sertifikat: (document_id: number) =>
|
||||
`/shared/certificate/${document_id}/pdf/`,
|
||||
`/shared/certificate/${document_id}/download/`,
|
||||
si: '/shared/ai_document/list/',
|
||||
si_id: (si_id: number) => `/shared/ai_document/list/${si_id}/`,
|
||||
si_id: (si_id: number) => `/shared/ai_document/${si_id}/`,
|
||||
si_payment: (document_id: number) =>
|
||||
`/shared/ai_document/pay/${document_id}/`,
|
||||
si_create: '/shared/ai_document/create/',
|
||||
document_types: '/shared/document_types/',
|
||||
document_types: '/shared/documents/types/',
|
||||
pay_history: '/shared/orders/all/',
|
||||
statistics: '/shared/statistics/',
|
||||
wordCount: '/shared/check_file/',
|
||||
users: '/users/profile/',
|
||||
demo_pay: (order_id: number) => `/users/payme/link/${order_id}/`,
|
||||
};
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { Menu } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import type { CabinetSection } from '../lib/types';
|
||||
|
||||
// ─── Labels ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const SECTION_LABELS: Record<CabinetSection, string> = {
|
||||
dashboard: 'Dashboard',
|
||||
plagiat: 'Plagiat tekshiruvlar',
|
||||
si: 'SI detektor',
|
||||
payments: "To'lovlar tarixi",
|
||||
profile: 'Profil',
|
||||
};
|
||||
|
||||
// ─── Props ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface CabinetNavProps {
|
||||
@@ -25,19 +16,31 @@ interface CabinetNavProps {
|
||||
export const CabinetNav: React.FC<CabinetNavProps> = ({
|
||||
activeSection,
|
||||
onMenuClick,
|
||||
}) => (
|
||||
<header className="h-14 px-4 md:px-6 flex lg:hidden items-center justify-between border-b border-slate-100 bg-white/80 backdrop-blur-sm sticky top-0 z-20">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={onMenuClick}
|
||||
className="lg:hidden p-2 rounded-lg text-slate-500 hover:bg-slate-100 transition-colors"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<Menu size={18} />
|
||||
</button>
|
||||
<h1 className="text-sm font-semibold text-slate-800">
|
||||
{SECTION_LABELS[activeSection]}
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}) => {
|
||||
const t = useTranslations('Cabinet');
|
||||
|
||||
const SECTION_LABELS: Record<CabinetSection, string> = {
|
||||
dashboard: t('dashboard'),
|
||||
plagiat: t('plagiatChecks'),
|
||||
si: t('siNav'),
|
||||
payments: t('payments'),
|
||||
profile: t('profile'),
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="h-14 px-4 md:px-6 flex lg:hidden items-center justify-between border-b border-slate-100 bg-white/80 backdrop-blur-sm sticky top-0 z-20">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={onMenuClick}
|
||||
className="lg:hidden p-2 rounded-lg text-slate-500 hover:bg-slate-100 transition-colors"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<Menu size={18} />
|
||||
</button>
|
||||
<h1 className="text-sm font-semibold text-slate-800">
|
||||
{SECTION_LABELS[activeSection]}
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,23 +10,9 @@ import {
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { Link } from '@/shared/config/i18n/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import type { CabinetSection } from '../lib/types';
|
||||
|
||||
// ─── Nav items ─────────────────────────────────────────────────────────────────
|
||||
|
||||
type NavItemDef =
|
||||
| { id: CabinetSection; label: string; icon: React.ElementType; href?: never }
|
||||
| { id: 'home'; label: string; icon: React.ElementType; href: string };
|
||||
|
||||
const NAV_ITEMS: NavItemDef[] = [
|
||||
{ id: 'home', label: 'Bosh sahifa', icon: Home, href: '/' },
|
||||
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
||||
{ id: 'plagiat', label: 'Plagiat', icon: FileSearch },
|
||||
{ id: 'si', label: 'SI detektor', icon: BrainCircuit },
|
||||
{ id: 'payments', label: "To'lovlar tarixi", icon: CreditCard },
|
||||
{ id: 'profile', label: 'Profil', icon: User },
|
||||
];
|
||||
|
||||
// ─── Props ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface SidebarProps {
|
||||
@@ -45,95 +31,118 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
userName,
|
||||
}) => (
|
||||
<>
|
||||
{/* Mobile backdrop */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-30 bg-black/30 backdrop-blur-sm lg:hidden"
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
}) => {
|
||||
const t = useTranslations('Cabinet');
|
||||
|
||||
<aside
|
||||
className={`
|
||||
fixed top-0 left-0 z-40 h-full w-60 bg-white border-r border-slate-100
|
||||
flex flex-col shadow-xl
|
||||
transition-transform duration-300 ease-out
|
||||
lg:sticky lg:top-0 lg:h-screen lg:translate-x-0 lg:shadow-none lg:z-auto
|
||||
${isOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||
`}
|
||||
>
|
||||
{/* Brand */}
|
||||
<div className="lg:hidden flex items-center justify-end px-5 py-4 border-b border-slate-100">
|
||||
<button
|
||||
const NAV_ITEMS = [
|
||||
{ id: 'home' as const, label: t('home'), icon: Home, href: '/' },
|
||||
{
|
||||
id: 'dashboard' as CabinetSection,
|
||||
label: t('dashboard'),
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{ id: 'plagiat' as CabinetSection, label: t('plagiat'), icon: FileSearch },
|
||||
{ id: 'si' as CabinetSection, label: t('siNav'), icon: BrainCircuit },
|
||||
{
|
||||
id: 'payments' as CabinetSection,
|
||||
label: t('payments'),
|
||||
icon: CreditCard,
|
||||
},
|
||||
{ id: 'profile' as CabinetSection, label: t('profile'), icon: User },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile backdrop */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-30 bg-black/30 backdrop-blur-sm lg:hidden"
|
||||
onClick={onClose}
|
||||
className=" p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
|
||||
aria-label="Yopish"
|
||||
>
|
||||
<X size={15} />
|
||||
</button>
|
||||
</div>
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* User pill */}
|
||||
<div className="px-3 pt-4 pb-2">
|
||||
<div className="flex items-center gap-3 px-3 py-2.5 rounded-xl bg-slate-50 border border-slate-100">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center shrink-0">
|
||||
<span className="text-blue-600 text-xs font-semibold">
|
||||
{userName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-slate-800 truncate">
|
||||
{userName}
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-400">Shaxsiy kabinet</p>
|
||||
<aside
|
||||
className={`
|
||||
fixed top-0 left-0 z-40 h-full w-60 bg-white border-r border-slate-100
|
||||
flex flex-col shadow-xl
|
||||
transition-transform duration-300 ease-out
|
||||
lg:sticky lg:top-0 lg:h-screen lg:translate-x-0 lg:shadow-none lg:z-auto
|
||||
${isOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||
`}
|
||||
>
|
||||
{/* Brand */}
|
||||
<div className="lg:hidden flex items-center justify-end px-5 py-4 border-b border-slate-100">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className=" p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
|
||||
aria-label={t('close')}
|
||||
>
|
||||
<X size={15} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* User pill */}
|
||||
<div className="px-3 pt-4 pb-2">
|
||||
<div className="flex items-center gap-3 px-3 py-2.5 rounded-xl bg-slate-50 border border-slate-100">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center shrink-0">
|
||||
<span className="text-blue-600 text-xs font-semibold">
|
||||
{userName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-slate-800 truncate">
|
||||
{userName}
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-400">
|
||||
{t('personalCabinet')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-3 py-2 space-y-0.5 overflow-y-auto">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const Icon = item.icon;
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-3 py-2 space-y-0.5 overflow-y-auto">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
if (item.id === 'home') {
|
||||
if (item.id === 'home') {
|
||||
return (
|
||||
<Link
|
||||
key="home"
|
||||
href={item.href as string}
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 transition-all duration-150"
|
||||
>
|
||||
<Icon size={17} />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const isActive = item.id === active;
|
||||
return (
|
||||
<Link
|
||||
key="home"
|
||||
href={item.href}
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 transition-all duration-150"
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => onNavigate(item.id as CabinetSection)}
|
||||
className={`
|
||||
w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium
|
||||
transition-all duration-150
|
||||
${
|
||||
isActive
|
||||
? 'bg-blue-50 text-blue-600'
|
||||
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-50'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Icon size={17} />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
{isActive && (
|
||||
<span className="ml-auto w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const isActive = item.id === active;
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => onNavigate(item.id as CabinetSection)}
|
||||
className={`
|
||||
w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium
|
||||
transition-all duration-150
|
||||
${
|
||||
isActive
|
||||
? 'bg-blue-50 text-blue-600'
|
||||
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-50'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Icon size={17} />
|
||||
<span>{item.label}</span>
|
||||
{isActive && (
|
||||
<span className="ml-auto w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { FileSearch, ArrowRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { SiCTACard } from '@/features/modals/siModal/page';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const CtaCards = () => (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{/* Plagiat */}
|
||||
<Link
|
||||
href={'/plagat'}
|
||||
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-blue-500 to-blue-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
||||
>
|
||||
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
|
||||
<FileSearch size={72} className="text-white" />
|
||||
</div>
|
||||
<FileSearch size={26} className="text-white mb-4" />
|
||||
<h3 className="text-white font-semibold text-base mb-1">
|
||||
Plagiat tekshiruvi
|
||||
</h3>
|
||||
<p className="text-blue-100 text-sm mb-4 leading-relaxed">
|
||||
Hujjatingizni originallik uchun tekshiring
|
||||
</p>
|
||||
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
||||
Yuborish <ArrowRight size={12} />
|
||||
</span>
|
||||
</Link>
|
||||
export const CtaCards = () => {
|
||||
const t = useTranslations('Cabinet');
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{/* Plagiat */}
|
||||
<Link
|
||||
href={'/plagat'}
|
||||
className="group relative overflow-hidden rounded-2xl bg-linear-to-br from-blue-500 to-blue-600 p-6 text-left shadow-md hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0 active:shadow-md"
|
||||
>
|
||||
<div className="absolute right-3 top-3 opacity-10 pointer-events-none">
|
||||
<FileSearch size={72} className="text-white" />
|
||||
</div>
|
||||
<FileSearch size={26} className="text-white mb-4" />
|
||||
<h3 className="text-white font-semibold text-base mb-1">
|
||||
{t('plagiatCheck')}
|
||||
</h3>
|
||||
<p className="text-blue-100 text-sm mb-4 leading-relaxed">
|
||||
{t('checkDesc')}
|
||||
</p>
|
||||
<span className="inline-flex items-center gap-1.5 text-white text-xs font-medium bg-white/20 rounded-lg px-3 py-1.5 group-hover:bg-white/30 transition-colors">
|
||||
{t('submit')} <ArrowRight size={12} />
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* SI */}
|
||||
<SiCTACard />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
{/* SI */}
|
||||
<SiCTACard />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ const SiRow: React.FC<{ item: SiDocument; index: number }> = ({
|
||||
const pay = useMutation({
|
||||
mutationKey: ['si-payment', item.id],
|
||||
mutationFn: () =>
|
||||
apiRequest<{ payment_link: string }>('POST', links.si_payment(item.id)),
|
||||
apiRequest<{ payment_link: string }>('POST', links.demo_pay(item.id)),
|
||||
onSuccess: (res) => {
|
||||
window.open(res.data.payment_link, '_self');
|
||||
},
|
||||
|
||||
@@ -3,16 +3,35 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { apiRequest } from '@/shared/request/apiRequest';
|
||||
import { links } from '@/shared/request/links';
|
||||
import { Download, CloudDownload } from 'lucide-react';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type SiResult = {
|
||||
original: number;
|
||||
ai_possible: number;
|
||||
type SiResultRes = {
|
||||
ai: number;
|
||||
hash: string;
|
||||
text: string;
|
||||
citation: number;
|
||||
plagiarism: number;
|
||||
originality: number;
|
||||
};
|
||||
|
||||
type SiResultData = {
|
||||
ok: boolean;
|
||||
res: SiResultRes;
|
||||
error: string;
|
||||
success: string;
|
||||
text_res: string;
|
||||
analyze_text?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type SiResult = {
|
||||
id: number;
|
||||
document: number;
|
||||
result: SiResultData;
|
||||
};
|
||||
|
||||
type SiDetail = {
|
||||
@@ -134,6 +153,7 @@ function LoadingSkeleton() {
|
||||
// ── Main Page ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function SiDetailPage({ id }: { id: number }) {
|
||||
const t = useTranslations('SiDetail');
|
||||
const { locale } = useParams() as { locale: string };
|
||||
useEffect(() => {
|
||||
console.log(locale);
|
||||
@@ -172,9 +192,10 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
}
|
||||
|
||||
// Derive SI percentages
|
||||
const original = doc.result?.original ?? 100 - (doc.si_percantage ?? 0);
|
||||
const aiPossible = doc.result?.ai_possible ?? 0;
|
||||
const ai = doc.result?.ai ?? doc.si_percantage ?? 0;
|
||||
const res = doc.result?.result?.res;
|
||||
const original = res?.originality ?? 100 - (doc.si_percantage ?? 0);
|
||||
const aiPossible = res?.plagiarism ?? 0;
|
||||
const ai = res?.ai ?? doc.si_percantage ?? 0;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 font-sans">
|
||||
@@ -200,7 +221,7 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
</svg>
|
||||
</button>
|
||||
<h1 className="text-base font-bold text-slate-800 truncate">
|
||||
{doc.title || 'SI tekshiruv'}
|
||||
{doc.title || t('siCheck')}
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
@@ -211,7 +232,7 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
{/* Section header */}
|
||||
<div className="bg-slate-50 border-b border-slate-100 px-6 py-4 text-center">
|
||||
<h2 className="text-sm font-bold uppercase tracking-widest text-blue-600">
|
||||
Asosiy ma'lumotlar
|
||||
{t('basicInfo')}
|
||||
</h2>
|
||||
<div className="w-10 h-0.5 bg-blue-600 mx-auto mt-1.5" />
|
||||
</div>
|
||||
@@ -219,48 +240,42 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
<div className="px-6">
|
||||
{/* Sub-header */}
|
||||
<p className="text-[11px] font-bold uppercase tracking-widest text-slate-400 py-4 border-b border-slate-100">
|
||||
Hujjat haqida ma'lumotlar
|
||||
{t('documentInfo')}
|
||||
</p>
|
||||
|
||||
<InfoRow
|
||||
label="Hujjat nomi"
|
||||
label={t('documentName')}
|
||||
value={doc.title || fileName(doc.file)}
|
||||
/>
|
||||
{doc.user && (
|
||||
<InfoRow
|
||||
label="Tekshiruvchi"
|
||||
label={t('checker')}
|
||||
value={`${doc.user.name} ${doc.user.surname}`}
|
||||
/>
|
||||
)}
|
||||
<InfoRow
|
||||
label="Hujjat yuklangan vaqti"
|
||||
label={t('uploadedAt')}
|
||||
value={formatDate(doc.created_at)}
|
||||
/>
|
||||
<InfoRow label={t('originalFileName')} value={fileName(doc.file)} />
|
||||
<InfoRow
|
||||
label="Hujjat faylining original nomi"
|
||||
value={fileName(doc.file)}
|
||||
/>
|
||||
<InfoRow
|
||||
label="So'zlar soni"
|
||||
label={t('wordCount')}
|
||||
value={
|
||||
doc.total_words > 0
|
||||
? doc.total_words.toLocaleString('uz-UZ')
|
||||
: '—'
|
||||
}
|
||||
/>
|
||||
<InfoRow
|
||||
label="Hujjat fayli kenggaytmasi"
|
||||
value={fileExtension(doc.file)}
|
||||
/>
|
||||
<InfoRow label={t('fileExt')} value={fileExtension(doc.file)} />
|
||||
{doc.file_size !== undefined && (
|
||||
<InfoRow
|
||||
label="Hujjat fayli o'lchami"
|
||||
label={t('fileSize')}
|
||||
value={fileSizeMb(doc.file_size)}
|
||||
/>
|
||||
)}
|
||||
{doc.total_price !== undefined && (
|
||||
<InfoRow
|
||||
label="Yechilgan summa"
|
||||
label={t('amountCharged')}
|
||||
value={`${Number(doc.total_price).toLocaleString('uz-UZ')} so'm`}
|
||||
/>
|
||||
)}
|
||||
@@ -275,7 +290,7 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-cyan-500 text-cyan-600 text-sm font-semibold hover:bg-cyan-50 transition-colors"
|
||||
>
|
||||
<Download size={15} />
|
||||
Original hujjatni yuklab olish
|
||||
{t('downloadOriginal')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
@@ -308,21 +323,15 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-black text-slate-800 leading-snug">
|
||||
Hujjatning SI detektori natijalari
|
||||
{t('siResultsTitle')}
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500 mt-1.5 leading-relaxed max-w-lg">
|
||||
Ushbu oynada foydalanuvchi tomonidan yuklangan matn
|
||||
sun'iy intellekt (SI) yordamida yozilgan bo'lish
|
||||
ehtimoli bo'yicha tahlil natijalari aks etirilgan.
|
||||
Detektor matnning stilistik, grammatik va semantik
|
||||
xususiyatlarini baholab, uning qanchalik darajada sun'iy
|
||||
intellekt tomonidan generatsiya qilingan bo'lishi
|
||||
mumkinligini foizlik ko'rinishida ko'rsatadi.
|
||||
{t('siResultsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{doc.file && (
|
||||
{/* {doc.file && (
|
||||
<a
|
||||
href={doc.file}
|
||||
target="_blank"
|
||||
@@ -330,24 +339,24 @@ export default function SiDetailPage({ id }: { id: number }) {
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-slate-800 text-white text-sm font-semibold hover:bg-slate-700 transition-colors shrink-0"
|
||||
>
|
||||
<CloudDownload size={16} />
|
||||
Yuklab olish
|
||||
{t('download')}
|
||||
</a>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
{/* Bars */}
|
||||
<div className="px-6 py-6 space-y-5">
|
||||
<SiBar
|
||||
label="Original matn"
|
||||
label={t('originalText')}
|
||||
value={original}
|
||||
color="bg-emerald-400"
|
||||
/>
|
||||
<SiBar
|
||||
label="Ehtimoliy Sun'iy Intellekt"
|
||||
label={t('possibleAi')}
|
||||
value={aiPossible}
|
||||
color="bg-amber-400"
|
||||
/>
|
||||
<SiBar label="Sun'iy Intellekt" value={ai} color="bg-red-500" />
|
||||
<SiBar label={t('aiContent')} value={ai} color="bg-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -10,7 +10,7 @@ import PaymentStatus from './paidStatus';
|
||||
import Sertifikat from '@/features/modals/sertificateModal/sertifikat';
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const baseUrl = 'https://dev-api.anti-plagiat.uz/api/v1';
|
||||
interface AnalyzeText {
|
||||
[key: string]: number | string;
|
||||
}
|
||||
@@ -397,10 +397,10 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<PaymentStatus status={doc.state} />
|
||||
{doc.certificate && <Sertifikat document_id={Number(id)} />}
|
||||
<Sertifikat document_id={Number(id)} />
|
||||
{doc.file && (
|
||||
<a
|
||||
href={doc.file}
|
||||
href={`${baseUrl}${doc.file}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-900 text-white text-xs font-semibold hover:bg-slate-700 transition-colors"
|
||||
@@ -486,21 +486,26 @@ export default function DocumentDetailPage({ id }: { id: number }) {
|
||||
<div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-8">
|
||||
<div className="flex flex-wrap justify-around gap-8">
|
||||
<ScoreRing
|
||||
value={res.originality}
|
||||
label="Originality"
|
||||
value={res.ai}
|
||||
label={t('scoreOriginality')}
|
||||
color="#10b981"
|
||||
/>
|
||||
|
||||
<ScoreRing
|
||||
value={res.plagiarism}
|
||||
label="Plagiarism"
|
||||
label={t('scorePlagiarism')}
|
||||
color="#f59e0b"
|
||||
/>
|
||||
<ScoreRing
|
||||
value={res.citation}
|
||||
label="Citation"
|
||||
label={t('scoreCitation')}
|
||||
color="#6366f1"
|
||||
/>
|
||||
<ScoreRing value={res.ai} label="AI Content" color="#ef4444" />
|
||||
<ScoreRing
|
||||
value={res.originality}
|
||||
label={t('scoreAiContent')}
|
||||
color="#ef4444"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { blue } from '../../lib/constant';
|
||||
import { PlagiatData } from '../../lib/types';
|
||||
import BarRow from './BarRow';
|
||||
@@ -14,27 +16,28 @@ export default function GaugeWithBars({
|
||||
originalityPercent,
|
||||
citationPercent,
|
||||
}: Props) {
|
||||
const t = useTranslations('PlagiatResult');
|
||||
return (
|
||||
<div className="flex items-center gap-5 mb-5">
|
||||
<CircleGauge value={plagiarismPercent || 0} />
|
||||
<div className="flex-1">
|
||||
<BarRow
|
||||
label="Plagiat"
|
||||
label={t('plagiat')}
|
||||
value={plagiarismPercent || 0}
|
||||
color={blue[900]}
|
||||
/>
|
||||
<BarRow
|
||||
label="AI generatsiya"
|
||||
label={t('aiGeneration')}
|
||||
value={aiPercent || 0}
|
||||
color={blue[600]}
|
||||
/>
|
||||
<BarRow
|
||||
label="Original"
|
||||
label={t('original')}
|
||||
value={originalityPercent || 0}
|
||||
color={blue[400]}
|
||||
/>
|
||||
<BarRow
|
||||
label="Iqtibos"
|
||||
label={t('citation')}
|
||||
value={citationPercent || 0}
|
||||
color={blue[200]}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { blue } from '../../lib/constant';
|
||||
import { PlagiatData } from '../../lib/types';
|
||||
|
||||
@@ -21,6 +23,7 @@ export default function Header({
|
||||
location,
|
||||
checkedAt,
|
||||
}: Props) {
|
||||
const t = useTranslations('PlagiatResult');
|
||||
return (
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div
|
||||
@@ -41,7 +44,7 @@ export default function Header({
|
||||
className="text-xs px-2.5 py-0.5 rounded-md font-medium"
|
||||
style={{ background: blue[100], color: blue[800] }}
|
||||
>
|
||||
{plagiarismPercent}% plagiat
|
||||
{plagiarismPercent}% {t('plagiat').toLowerCase()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[13px] mt-0.5 truncate" style={{ color: blue[400] }}>
|
||||
@@ -50,7 +53,7 @@ export default function Header({
|
||||
</div>
|
||||
<div className="text-right shrink-0">
|
||||
<p className="text-[11px]" style={{ color: blue[400] }}>
|
||||
Tekshirilgan
|
||||
{t('checked')}
|
||||
</p>
|
||||
<p className="text-[12px] mt-0.5" style={{ color: blue[600] }}>
|
||||
{checkedAt}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { PlagiatData } from '../../lib/types';
|
||||
import MetricCard from './Metriccard';
|
||||
|
||||
@@ -12,12 +14,16 @@ export default function TopMetrics({
|
||||
originalityPercent,
|
||||
citationPercent,
|
||||
}: Props) {
|
||||
const t = useTranslations('PlagiatResult');
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-2.5 mb-5">
|
||||
<MetricCard label="Plagiat darajasi" value={`${plagiarismPercent}%`} />
|
||||
<MetricCard label="AI yozgan" value={`${aiPercent}%`} />
|
||||
<MetricCard label="Originallik" value={`${originalityPercent}%`} />
|
||||
<MetricCard label="Iqtibos" value={`${citationPercent}%`} />
|
||||
<MetricCard
|
||||
label={t('plagiarismLevel')}
|
||||
value={`${plagiarismPercent}%`}
|
||||
/>
|
||||
<MetricCard label={t('aiWritten')} value={`${aiPercent}%`} />
|
||||
<MetricCard label={t('originality')} value={`${originalityPercent}%`} />
|
||||
<MetricCard label={t('citation')} value={`${citationPercent}%`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,26 +145,6 @@ function Skeleton() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Error state ──────────────────────────────────────────────────────────────
|
||||
|
||||
function ErrorState() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-6 bg-slate-50">
|
||||
<div
|
||||
className="rounded-xl p-8 text-center"
|
||||
style={{ background: '#fff', border: `0.5px solid ${blue[100]}` }}
|
||||
>
|
||||
<p className="text-sm font-medium mb-1" style={{ color: blue[900] }}>
|
||||
Ma'lumot topilmadi
|
||||
</p>
|
||||
<p className="text-xs" style={{ color: blue[400] }}>
|
||||
Ushbu tekshiruv mavjud emas yoki o'chirilgan
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function PlagiatResult({ id }: { id: number }) {
|
||||
@@ -186,7 +166,22 @@ export default function PlagiatResult({ id }: { id: number }) {
|
||||
});
|
||||
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (isError || !rawData) return <ErrorState />;
|
||||
if (isError || !rawData)
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-6 bg-slate-50">
|
||||
<div
|
||||
className="rounded-xl p-8 text-center"
|
||||
style={{ background: '#fff', border: `0.5px solid ${blue[100]}` }}
|
||||
>
|
||||
<p className="text-sm font-medium mb-1" style={{ color: blue[900] }}>
|
||||
Ma'lumot topilmadi
|
||||
</p>
|
||||
<p className="text-xs" style={{ color: blue[400] }}>
|
||||
Ushbu tekshiruv mavjud emas yoki o'chirilgan
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const data: PlagiatData = transformResponse(rawData);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const PageHeader: React.FC = () => {
|
||||
>
|
||||
<Plus size={15} className="text-white" />
|
||||
<h3 className="text-white font-semibold text-base">
|
||||
Plagiat tekshiruvi
|
||||
{t('plagiatCheck')}
|
||||
</h3>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
'use client';
|
||||
import { useEffect } from 'react';
|
||||
import Hero from './components/Hero';
|
||||
import InfoSection from './components/InfoSection';
|
||||
import StepsSection from './components/StepsSection';
|
||||
import Ticker from './components/Ticker';
|
||||
import { useRouter } from '@/shared/config/i18n/navigation';
|
||||
|
||||
const PlagiarismLanding = () => {
|
||||
const route = useRouter();
|
||||
useEffect(() => {
|
||||
const data = localStorage.getItem('user');
|
||||
|
||||
if (data) {
|
||||
route.push('/plagat');
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface PlagiarismFormState {
|
||||
file: File | null;
|
||||
certificate: boolean;
|
||||
text?: string;
|
||||
type: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
export type PlagiarismFormErrors = Partial<
|
||||
|
||||
@@ -21,7 +21,7 @@ const INITIAL_FORM: PlagiarismFormState = {
|
||||
file: null,
|
||||
certificate: true,
|
||||
text: '',
|
||||
type: 'boshqa',
|
||||
type: 0,
|
||||
};
|
||||
|
||||
const PRICE: PriceCalculate = {
|
||||
@@ -71,6 +71,7 @@ export function usePlagiarismForm() {
|
||||
const priceInfo: PriceCalculate = {
|
||||
total_price: resdata?.total_price || 0,
|
||||
discount: resdata?.discount || 0,
|
||||
certificate: resdata?.certificate || 0,
|
||||
service_fee: resdata?.service_fee || 0,
|
||||
};
|
||||
setPrices(priceInfo);
|
||||
@@ -90,7 +91,7 @@ export function usePlagiarismForm() {
|
||||
const payment = useMutation({
|
||||
mutationKey: ['payload'],
|
||||
mutationFn: ({ order_id }: { order_id: number }) =>
|
||||
apiRequest<{ payment_link: string }>('POST', links.payment(order_id)),
|
||||
apiRequest<{ payment_link: string }>('POST', links.demo_pay(order_id)),
|
||||
onSuccess: (res) => {
|
||||
console.log('payment res: ', res);
|
||||
window.open(res.data.payment_link, '_self');
|
||||
@@ -116,9 +117,9 @@ export function usePlagiarismForm() {
|
||||
setErrors((prev) => ({ ...prev, file: undefined }));
|
||||
}, []);
|
||||
|
||||
const setOption = useCallback((option: string) => {
|
||||
setForm((prev) => ({ ...prev, document_type: option }));
|
||||
setErrors((prev) => ({ ...prev, document_type: undefined }));
|
||||
const setOption = useCallback((option: number) => {
|
||||
setForm((prev) => ({ ...prev, type: option }));
|
||||
setErrors((prev) => ({ ...prev, type: undefined }));
|
||||
}, []);
|
||||
|
||||
const toggleCertificate = useCallback(() => {
|
||||
@@ -150,7 +151,7 @@ export function usePlagiarismForm() {
|
||||
fd.append('text', form.text || '');
|
||||
fd.append('file', form.file!);
|
||||
fd.append('certificate', String(form.certificate));
|
||||
fd.append('type', form.type);
|
||||
fd.append('type', String(form.type));
|
||||
console.log('sended data: ', fd);
|
||||
checkdocumentRequest.mutate(fd);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { FieldWrapper } from './Plagiraismui';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiRequest } from '@/shared/request/apiRequest';
|
||||
@@ -12,8 +13,8 @@ type DocumentType = {
|
||||
};
|
||||
|
||||
interface DocumentsTypesProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -22,6 +23,7 @@ export default function DocumentsTypes({
|
||||
onChange,
|
||||
disabled,
|
||||
}: DocumentsTypesProps) {
|
||||
const t = useTranslations('DocumentTypes');
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['document_types'],
|
||||
queryFn: (): Promise<DocumentType[]> =>
|
||||
@@ -30,20 +32,24 @@ export default function DocumentsTypes({
|
||||
),
|
||||
});
|
||||
|
||||
const selected = data?.find((item) => item.id === value);
|
||||
|
||||
return (
|
||||
<FieldWrapper htmlFor="document_type" label="Hujjat turi">
|
||||
<FieldWrapper htmlFor="document_type" label={t('label')}>
|
||||
<select
|
||||
id="document_type"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={selected?.name}
|
||||
onChange={(e) => {
|
||||
onChange(Number(e.target.value));
|
||||
}}
|
||||
disabled={isLoading || disabled}
|
||||
className={`${inputCls} cursor-pointer`}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{isLoading ? 'Yuklanmoqda...' : 'Hujjat turini tanlang...'}
|
||||
{isLoading ? t('loading') : t('placeholder')}
|
||||
</option>
|
||||
{data?.map((type) => (
|
||||
<option key={type.id} value={String(type.id)}>
|
||||
<option key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user