From 80343c9ca880a7ab6d8136bb57978a65bd84b9ce Mon Sep 17 00:00:00 2001
From: "nabijonovdavronbek619@gmail.com"
Date: Wed, 1 Apr 2026 01:22:31 +0500
Subject: [PATCH 1/3] landing page added
---
package-lock.json | 55 ++++--
package.json | 5 +-
src/app/[locale]/layout.tsx | 2 +
src/app/[locale]/page.tsx | 6 +-
src/app/[locale]/plagat/page.tsx | 11 ++
src/app/globals.css | 4 +
src/features/auth/login/lib/useLoginForm.ts | 26 +++
.../auth/register/lib/useRegisterForm.ts | 50 +++--
.../auth/register/lib/validateRegister.ts | 4 +-
src/features/auth/register/ui/form.tsx | 9 +-
src/shared/request/apiRequest.ts | 29 +++
src/shared/request/getLanguage.ts | 30 +++
src/shared/request/links.ts | 4 +
src/{ => shared}/request/plagiarismapi.ts | 0
src/widgets/detail/index.tsx | 6 +-
src/widgets/fileUpload/lib/usePlagiraism.ts | 2 +-
src/widgets/home/animations.ts | 15 ++
src/widgets/home/components/Badge.tsx | 43 +++++
.../home/components/DocIllustration.tsx | 137 ++++++++++++++
src/widgets/home/components/Hero.tsx | 177 ++++++++++++++++++
src/widgets/home/components/InfoCard.tsx | 87 +++++++++
src/widgets/home/components/InfoSection.tsx | 55 ++++++
src/widgets/home/components/Section.tsx | 16 ++
src/widgets/home/components/StartButton.tsx | 43 +++++
src/widgets/home/components/Stat.tsx | 32 ++++
src/widgets/home/components/StepCard.tsx | 108 +++++++++++
src/widgets/home/components/StepsSection.tsx | 129 +++++++++++++
src/widgets/home/components/Ticker.tsx | 51 +++++
src/widgets/home/constants.ts | 86 +++++++++
src/widgets/home/hooks/useIsMobile.ts | 16 ++
src/widgets/home/index.tsx | 29 +++
src/widgets/home/tokens.ts | 20 ++
src/widgets/home/types.ts | 20 ++
src/widgets/navbar/ui/index.tsx | 4 +-
34 files changed, 1262 insertions(+), 49 deletions(-)
create mode 100644 src/app/[locale]/plagat/page.tsx
create mode 100644 src/shared/request/apiRequest.ts
create mode 100644 src/shared/request/getLanguage.ts
create mode 100644 src/shared/request/links.ts
rename src/{ => shared}/request/plagiarismapi.ts (100%)
create mode 100644 src/widgets/home/animations.ts
create mode 100644 src/widgets/home/components/Badge.tsx
create mode 100644 src/widgets/home/components/DocIllustration.tsx
create mode 100644 src/widgets/home/components/Hero.tsx
create mode 100644 src/widgets/home/components/InfoCard.tsx
create mode 100644 src/widgets/home/components/InfoSection.tsx
create mode 100644 src/widgets/home/components/Section.tsx
create mode 100644 src/widgets/home/components/StartButton.tsx
create mode 100644 src/widgets/home/components/Stat.tsx
create mode 100644 src/widgets/home/components/StepCard.tsx
create mode 100644 src/widgets/home/components/StepsSection.tsx
create mode 100644 src/widgets/home/components/Ticker.tsx
create mode 100644 src/widgets/home/constants.ts
create mode 100644 src/widgets/home/hooks/useIsMobile.ts
create mode 100644 src/widgets/home/index.tsx
create mode 100644 src/widgets/home/tokens.ts
create mode 100644 src/widgets/home/types.ts
diff --git a/package-lock.json b/package-lock.json
index 1ff4093..49ef2d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,9 +27,9 @@
"@radix-ui/react-toggle-group": "^1.1.7",
"@radix-ui/react-tooltip": "^1.2.4",
"@tabler/icons-react": "^3.31.0",
- "@tanstack/react-query": "^5.76.0",
+ "@tanstack/react-query": "^5.96.0",
"@tanstack/react-table": "^8.21.3",
- "axios": "^1.12.2",
+ "axios": "^1.14.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
@@ -40,6 +40,7 @@
"next-themes": "^0.4.6",
"react": "^19.1.2",
"react-dom": "^19.1.2",
+ "react-toastify": "^11.0.5",
"recharts": "^2.15.3",
"sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
@@ -3208,9 +3209,9 @@
}
},
"node_modules/@tanstack/query-core": {
- "version": "5.90.16",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz",
- "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==",
+ "version": "5.96.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.0.tgz",
+ "integrity": "sha512-sfO3uQeol1BU7cRP6NYY7nAiX3GiNY20lI/dtSbKLwcIkYw/X+w/tEsQAkc544AfIhBX/IvH/QYtPHrPhyAKGw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -3218,12 +3219,12 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.90.16",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz",
- "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==",
+ "version": "5.96.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.96.0.tgz",
+ "integrity": "sha512-6qbjdm1K5kizVKv9TNqhIN3doq2anRhdF2XaFMFSn4m8L22S69RV+FilvlyVT4RoJyMxtPU5rs4RpdFa/PEC7A==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.90.16"
+ "@tanstack/query-core": "5.96.0"
},
"funding": {
"type": "github",
@@ -4240,14 +4241,14 @@
}
},
"node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
+ "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
}
},
"node_modules/axobject-query": {
@@ -7829,10 +7830,13 @@
}
},
"node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
},
"node_modules/punycode": {
"version": "2.3.1",
@@ -7976,6 +7980,19 @@
}
}
},
+ "node_modules/react-toastify": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
+ "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
diff --git a/package.json b/package.json
index ea0c581..cd5bc6d 100644
--- a/package.json
+++ b/package.json
@@ -30,9 +30,9 @@
"@radix-ui/react-toggle-group": "^1.1.7",
"@radix-ui/react-tooltip": "^1.2.4",
"@tabler/icons-react": "^3.31.0",
- "@tanstack/react-query": "^5.76.0",
+ "@tanstack/react-query": "^5.96.0",
"@tanstack/react-table": "^8.21.3",
- "axios": "^1.12.2",
+ "axios": "^1.14.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
@@ -43,6 +43,7 @@
"next-themes": "^0.4.6",
"react": "^19.1.2",
"react-dom": "^19.1.2",
+ "react-toastify": "^11.0.5",
"recharts": "^2.15.3",
"sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index 229166b..298e695 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -11,6 +11,7 @@ import { setRequestLocale } from 'next-intl/server';
import QueryProvider from '@/shared/config/react-query/QueryProvider';
import Script from 'next/script';
import Provider from '@/features/providers/provider';
+import { ToastContainer } from 'react-toastify';
type Props = {
children: ReactNode;
@@ -45,6 +46,7 @@ export default async function RootLayout({ children, params }: Props) {
{children}
+
diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx
index 09d434c..330b1c1 100644
--- a/src/app/[locale]/page.tsx
+++ b/src/app/[locale]/page.tsx
@@ -1,11 +1,9 @@
-import { PlagiarismCheckForm } from '@/widgets/fileUpload/ui/Plagiraismcheckform';
-import { HistoryPage } from '@/widgets/history';
+import PlagiarismLanding from '@/widgets/home';
export default function Home() {
return (
);
}
diff --git a/src/app/[locale]/plagat/page.tsx b/src/app/[locale]/plagat/page.tsx
new file mode 100644
index 0000000..130c2f5
--- /dev/null
+++ b/src/app/[locale]/plagat/page.tsx
@@ -0,0 +1,11 @@
+import { PlagiarismCheckForm } from '@/widgets/fileUpload/ui/Plagiraismcheckform';
+import { HistoryPage } from '@/widgets/history';
+
+export default function Page() {
+ return (
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index 70fb346..f7a5f68 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,6 +1,10 @@
+@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,600;0,700;1,400&family=DM+Mono:wght@400;500&family=DM+Sans:wght@400;500&display=swap');
@import 'tailwindcss';
@import 'tw-animate-css';
+*, *::before, *::after { box-sizing: border-box;}
+html { scroll-behavior: smooth; }
+
@custom-variant dark (&:is(.dark *));
@theme inline {
diff --git a/src/features/auth/login/lib/useLoginForm.ts b/src/features/auth/login/lib/useLoginForm.ts
index f7f26b1..5e1b5c6 100644
--- a/src/features/auth/login/lib/useLoginForm.ts
+++ b/src/features/auth/login/lib/useLoginForm.ts
@@ -2,10 +2,35 @@
import React, { useState } from 'react';
import { useTranslations } from 'next-intl';
+import { useMutation } from '@tanstack/react-query';
+import { apiRequest } from '@/shared/request/apiRequest';
+import { links } from '@/shared/request/links';
+import { useLoginModal } from '@/shared/zustand/auth';
+import { toast } from 'react-toastify';
+
+interface LoginData {
+ phone: string;
+}
export function useLoginForm() {
const [phone, setPhone] = useState('');
const [error, setError] = useState('');
+ const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
+ const loginReqest = useMutation({
+ mutationKey: ['login'],
+ mutationFn: (data: LoginData) => apiRequest('POST', links.login, data),
+ onSuccess: (data) => {
+ console.log('Login successful:', data);
+ toggleLoginModal();
+ toast.success('Kirish muvaffaqiyatli!');
+ },
+ onError: (err) => {
+ setError(err instanceof Error ? err.message : 'Unknown error');
+ // toggleLoginModal();
+ console.error('Login failed:', err);
+ toast.error(err instanceof Error ? err.message : 'Unknown error');
+ },
+ });
const t = useTranslations();
@@ -18,6 +43,7 @@ export function useLoginForm() {
return;
}
+ loginReqest.mutate({ phone: `+998${phone}` });
sessionStorage.setItem('prev_page', 'login');
};
diff --git a/src/features/auth/register/lib/useRegisterForm.ts b/src/features/auth/register/lib/useRegisterForm.ts
index 5121d52..07026fb 100644
--- a/src/features/auth/register/lib/useRegisterForm.ts
+++ b/src/features/auth/register/lib/useRegisterForm.ts
@@ -3,13 +3,43 @@
import { useCallback, useState } from 'react';
import { useRegisterZustand } from './registerZustand';
import { validateRegister, RegisterErrors } from './validateRegister';
+import { useRegisterModal } from '@/shared/zustand/auth';
+import { useMutation } from '@tanstack/react-query';
+import { apiRequest } from '@/shared/request/apiRequest';
+import { links } from '@/shared/request/links';
+import { toast } from 'react-toastify';
+
+interface RegisterData {
+ name: string;
+ surname: string;
+ phone: string;
+}
export function useRegisterForm() {
const { registerData, setItem, setOferta, clearRegisterData } =
useRegisterZustand();
const [errors, setErrors] = useState({});
- const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
+ const toggleRegisterModal = useRegisterModal(
+ (state) => state.toggleRegisterModal,
+ );
+
+ const registerRequest = useMutation({
+ mutationKey: ['register'],
+ mutationFn: (data: RegisterData) =>
+ apiRequest('POST', links.register, data),
+ onSuccess: (data) => {
+ console.log('Register successful:', data);
+ toggleRegisterModal();
+ setSuccess(true);
+ toast.success("Ro'yxatdan o'tish muvaffaqiyatli!");
+ },
+ onError: (err) => {
+ // toggleLoginModal();
+ console.error('Register failed:', err);
+ toast.error(err instanceof Error ? err.message : 'Unknown error');
+ },
+ });
const handleChange = useCallback(
(e: React.ChangeEvent) => {
@@ -33,23 +63,12 @@ export function useRegisterForm() {
async (e: React.FormEvent) => {
e.preventDefault();
const validationErrors = validateRegister(registerData);
+ console.log('register data on submit:', registerData);
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
-
- setLoading(true);
- try {
- // Replace with your real API call:
- // await api.post('/register', registerData);
- await new Promise((r) => setTimeout(r, 1200)); // simulated delay
- setSuccess(true);
- clearRegisterData();
- } catch {
- setErrors({ name: 'Something went wrong. Please try again.' });
- } finally {
- setLoading(false);
- }
+ registerRequest.mutate(registerData);
},
[registerData, clearRegisterData],
);
@@ -57,10 +76,11 @@ export function useRegisterForm() {
return {
registerData,
errors,
- loading,
+ loading: registerRequest.isPending,
success,
handleChange,
handleOferta,
handleSubmit,
+ setItem,
};
}
diff --git a/src/features/auth/register/lib/validateRegister.ts b/src/features/auth/register/lib/validateRegister.ts
index 898b29a..8063b42 100644
--- a/src/features/auth/register/lib/validateRegister.ts
+++ b/src/features/auth/register/lib/validateRegister.ts
@@ -28,8 +28,8 @@ export function validateRegister(data: {
const digits = data.phone.replace(/\D/g, '');
if (!digits) {
errors.phone = 'Phone is required';
- } else if (digits.length !== 9) {
- errors.phone = 'Enter a valid 9-digit phone number';
+ } else if (digits.length !== 9 && digits.length !== 12) {
+ errors.phone = 'Enter a valid 9-digit or 13-digit phone number';
}
if (!data.oferta) {
diff --git a/src/features/auth/register/ui/form.tsx b/src/features/auth/register/ui/form.tsx
index 9db4a5f..0269f5f 100644
--- a/src/features/auth/register/ui/form.tsx
+++ b/src/features/auth/register/ui/form.tsx
@@ -63,6 +63,7 @@ function Field({
}
export function RegisterFormUI() {
+ const [phone, setPhone] = React.useState('');
const {
registerData,
errors,
@@ -71,16 +72,22 @@ export function RegisterFormUI() {
handleChange,
handleOferta,
handleSubmit,
+ setItem,
} = useRegisterForm();
- const [phone, setPhone] = React.useState(registerData.phone || '');
const [isFocused, setIsFocused] = React.useState(false);
const handlePhoneChange = useCallback(
(e: React.ChangeEvent) => {
setPhone(normalizeDigits(e.target.value));
+ if (normalizeDigits(e.target.value).length === 9)
+ setItem('phone', `998${phone}`);
},
[setPhone],
);
+ if (errors) {
+ console.log('Validation errors:', errors);
+ }
+
// ── Success state ──────────────────────────────────────
if (success) {
return (
diff --git a/src/shared/request/apiRequest.ts b/src/shared/request/apiRequest.ts
new file mode 100644
index 0000000..6e84250
--- /dev/null
+++ b/src/shared/request/apiRequest.ts
@@ -0,0 +1,29 @@
+import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
+
+import { getRouteLang } from './getLanguage';
+
+const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
+
+const api = axios.create({
+ baseURL: baseUrl,
+});
+
+export const apiRequest = async (
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE',
+ url: string,
+ data?: unknown,
+ config?: Omit,
+): Promise => {
+ const response: AxiosResponse = await api.request({
+ method,
+ url,
+ data,
+ ...config,
+ headers: {
+ 'Accept-Language': getRouteLang(), // evaluated per-request
+ ...config?.headers,
+ },
+ });
+
+ return response.data;
+};
diff --git a/src/shared/request/getLanguage.ts b/src/shared/request/getLanguage.ts
new file mode 100644
index 0000000..3e47b53
--- /dev/null
+++ b/src/shared/request/getLanguage.ts
@@ -0,0 +1,30 @@
+const defaultLocale = 'uz';
+const locales = ['uz', 'ru', 'en'] as const;
+
+export function getRouteLang(): string {
+ if (typeof window === 'undefined') return defaultLocale;
+
+ // 1)Fall back to the first non-empty pathname segment
+ const rawSegments = window.location.pathname.split('/'); // e.g. ['', 'uz', 'path', ...]
+ const segments = rawSegments.filter(Boolean); // removes empty strings
+ if (segments.length > 0) {
+ const candidate = segments[0]; // first segment after root
+ if (locales.includes(candidate as (typeof locales)[number]))
+ return candidate;
+ }
+
+ // 2) Try cookie NEXT_LOCALE first (language switcher sets it)
+ const cookieMatch = document.cookie
+ .split(';')
+ .map((c) => c.trim())
+ .find((c) => c.startsWith('NEXT_LOCALE='));
+
+ if (cookieMatch) {
+ const cookieLang = cookieMatch.split('=')[1];
+ if (locales.includes(cookieLang as (typeof locales)[number]))
+ return cookieLang;
+ }
+
+ // 3) final fallback
+ return defaultLocale;
+}
diff --git a/src/shared/request/links.ts b/src/shared/request/links.ts
new file mode 100644
index 0000000..a1859f4
--- /dev/null
+++ b/src/shared/request/links.ts
@@ -0,0 +1,4 @@
+export const links = {
+ login: '/users/login/',
+ register: '/users/register/',
+};
diff --git a/src/request/plagiarismapi.ts b/src/shared/request/plagiarismapi.ts
similarity index 100%
rename from src/request/plagiarismapi.ts
rename to src/shared/request/plagiarismapi.ts
diff --git a/src/widgets/detail/index.tsx b/src/widgets/detail/index.tsx
index 690c30b..d830519 100644
--- a/src/widgets/detail/index.tsx
+++ b/src/widgets/detail/index.tsx
@@ -15,7 +15,7 @@ interface InfoRowProps {
export const InfoRow: React.FC = ({ label, value, icon }) => (
-
+
{icon && {icon} }
{label}
@@ -150,7 +150,7 @@ export const SimilarityMeter: React.FC = ({
{/* Track */}
{/* Threshold markers */}
@@ -211,7 +211,7 @@ export const Avatar: React.FC
= ({
return (
{initials}
diff --git a/src/widgets/fileUpload/lib/usePlagiraism.ts b/src/widgets/fileUpload/lib/usePlagiraism.ts
index 6956277..f488db8 100644
--- a/src/widgets/fileUpload/lib/usePlagiraism.ts
+++ b/src/widgets/fileUpload/lib/usePlagiraism.ts
@@ -7,7 +7,7 @@ import {
} from './types';
import { selectFullName, useUserStore } from './userStore';
import { isFormValid, validatePlagiarismForm } from './validation';
-import { submitPlagiarismCheck } from '@/request/plagiarismapi';
+import { submitPlagiarismCheck } from '@/shared/request/plagiarismapi';
// ─── Initial States ──────────────────────────────────────────────────────────
diff --git a/src/widgets/home/animations.ts b/src/widgets/home/animations.ts
new file mode 100644
index 0000000..9470812
--- /dev/null
+++ b/src/widgets/home/animations.ts
@@ -0,0 +1,15 @@
+import type { Variants } from 'framer-motion';
+
+export const fadeUp = (delay = 0): Variants => ({
+ hidden: { opacity: 0, y: 28 },
+ visible: {
+ opacity: 1,
+ y: 0,
+ transition: { duration: 0.65, delay, ease: [0.22, 1, 0.36, 1] },
+ },
+});
+
+export const stagger: Variants = {
+ hidden: {},
+ visible: { transition: { staggerChildren: 0.1 } },
+};
diff --git a/src/widgets/home/components/Badge.tsx b/src/widgets/home/components/Badge.tsx
new file mode 100644
index 0000000..9985a40
--- /dev/null
+++ b/src/widgets/home/components/Badge.tsx
@@ -0,0 +1,43 @@
+import type { FC, ReactNode } from 'react';
+import { motion } from 'framer-motion';
+import { C } from '../tokens';
+
+interface BadgeProps {
+ children: ReactNode;
+}
+
+const Badge: FC = ({ children }) => (
+
+
+ {children}
+
+);
+
+export default Badge;
diff --git a/src/widgets/home/components/DocIllustration.tsx b/src/widgets/home/components/DocIllustration.tsx
new file mode 100644
index 0000000..a72358e
--- /dev/null
+++ b/src/widgets/home/components/DocIllustration.tsx
@@ -0,0 +1,137 @@
+import type { FC } from 'react';
+import { motion } from 'framer-motion';
+import { C } from '../tokens';
+import { DOC_LINE_WIDTHS } from '../constants';
+
+const DocIllustration: FC = () => (
+
+ {/* Shadow card behind */}
+
+
+ {/* Main document card */}
+
+ {/* Card header */}
+
+
+ {/* Skeleton lines */}
+ {DOC_LINE_WIDTHS.map((w, i) => (
+
+ ))}
+
+ {/* Similarity warning */}
+
+ ⚠ 12% similarity found
+
+
+ {/* Verified badge */}
+
+ ✓ VERIFIED
+
+
+
+);
+
+export default DocIllustration;
diff --git a/src/widgets/home/components/Hero.tsx b/src/widgets/home/components/Hero.tsx
new file mode 100644
index 0000000..3b88a6a
--- /dev/null
+++ b/src/widgets/home/components/Hero.tsx
@@ -0,0 +1,177 @@
+import type { FC } from 'react';
+import { motion, type MotionStyle, type MotionValue } from 'framer-motion';
+import { fadeUp, stagger } from '../animations';
+import { C } from '../tokens';
+import { STATS } from '../constants';
+import { useIsMobile } from '../hooks/useIsMobile';
+import Badge from './Badge';
+import DocIllustration from './DocIllustration';
+import Section from './Section';
+import Stat from './Stat';
+import StartButton from './StartButton';
+
+interface HeroProps {
+ onStart: () => void;
+ blobY: MotionValue;
+}
+
+const Hero: FC = ({ onStart, blobY }) => {
+ const isMobile = useIsMobile();
+
+ return (
+
+ {/* Dot grid background */}
+
+
+ {/* Ambient glow blob */}
+
+
+
+ {/* Main row */}
+
+ {/* Text block */}
+
+ Academic Integrity Platform
+
+
+
+ Is Your Work
+
+
+ Truly Original?
+
+
+
+
+ Plagiarism is presenting someone else‘s ideas or words as
+ your own. In academia and professional life, it carries serious
+ consequences. Our platform detects it in seconds — so you can
+ submit with full confidence.
+
+
+
+
+
+ Certificate issued within 24h
+
+
+
+
+ {/* Illustration (hidden on mobile) */}
+ {!isMobile &&
}
+
+
+ {/* Stats row */}
+
+ {STATS.map((s) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default Hero;
diff --git a/src/widgets/home/components/InfoCard.tsx b/src/widgets/home/components/InfoCard.tsx
new file mode 100644
index 0000000..9454283
--- /dev/null
+++ b/src/widgets/home/components/InfoCard.tsx
@@ -0,0 +1,87 @@
+import { useRef, type FC } from 'react';
+import { motion, useInView } from 'framer-motion';
+import { fadeUp } from '../animations';
+import { C } from '../tokens';
+import type { InfoCardData } from '../types';
+
+const InfoCard: FC = ({
+ title,
+ text,
+ icon,
+ accent,
+ bg,
+ delay,
+}) => {
+ const ref = useRef(null);
+ const inView = useInView(ref, { once: true, margin: '-40px' });
+
+ return (
+
+ {/* Top accent bar */}
+
+
+ {/* Icon */}
+
+ {icon}
+
+
+
+ {title}
+
+
+
+ {text}
+
+
+ );
+};
+
+export default InfoCard;
diff --git a/src/widgets/home/components/InfoSection.tsx b/src/widgets/home/components/InfoSection.tsx
new file mode 100644
index 0000000..35ca690
--- /dev/null
+++ b/src/widgets/home/components/InfoSection.tsx
@@ -0,0 +1,55 @@
+import type { FC } from 'react';
+import { motion } from 'framer-motion';
+import { fadeUp } from '../animations';
+import { C } from '../tokens';
+import { INFO_CARDS } from '../constants';
+import InfoCard from './InfoCard';
+import Section from './Section';
+
+const InfoSection: FC = () => (
+
+
+ {/* Heading */}
+
+
+ Why It Matters
+
+
+ Understanding Plagiarism
+
+
+
+ {/* Cards */}
+
+ {INFO_CARDS.map((card) => (
+
+ ))}
+
+
+
+);
+
+export default InfoSection;
diff --git a/src/widgets/home/components/Section.tsx b/src/widgets/home/components/Section.tsx
new file mode 100644
index 0000000..a6bb7ed
--- /dev/null
+++ b/src/widgets/home/components/Section.tsx
@@ -0,0 +1,16 @@
+import type { CSSProperties, FC, ReactNode } from 'react';
+
+interface SectionProps {
+ children: ReactNode;
+ style?: CSSProperties;
+}
+
+const Section: FC = ({ children, style = {} }) => (
+
+);
+
+export default Section;
diff --git a/src/widgets/home/components/StartButton.tsx b/src/widgets/home/components/StartButton.tsx
new file mode 100644
index 0000000..4d2bde4
--- /dev/null
+++ b/src/widgets/home/components/StartButton.tsx
@@ -0,0 +1,43 @@
+'use client';
+import { useState, type FC } from 'react';
+import { motion } from 'framer-motion';
+import { C } from '../tokens';
+
+interface StartButtonProps {
+ onClick: () => void;
+ small?: boolean;
+}
+
+const StartButton: FC = ({ onClick, small = false }) => {
+ const [hovered, setHovered] = useState(false);
+
+ return (
+ setHovered(true)}
+ onHoverEnd={() => setHovered(false)}
+ onClick={onClick}
+ style={{
+ padding: small ? '10px 28px' : '15px 44px',
+ borderRadius: 6,
+ border: 'none',
+ background: hovered ? C.accentHover : C.accent,
+ color: '#fff',
+ fontFamily: "'DM Mono', monospace",
+ fontSize: small ? 11 : 13,
+ fontWeight: 500,
+ letterSpacing: '0.07em',
+ textTransform: 'uppercase',
+ cursor: 'pointer',
+ transition: 'background 0.2s, box-shadow 0.25s',
+ boxShadow: hovered
+ ? `0 8px 28px ${C.accent}44, 0 2px 8px ${C.accent}22`
+ : `0 2px 8px ${C.accent}22`,
+ }}
+ >
+ Start Checking →
+
+ );
+};
+
+export default StartButton;
diff --git a/src/widgets/home/components/Stat.tsx b/src/widgets/home/components/Stat.tsx
new file mode 100644
index 0000000..9adfe52
--- /dev/null
+++ b/src/widgets/home/components/Stat.tsx
@@ -0,0 +1,32 @@
+import type { FC } from 'react';
+import { C } from '../tokens';
+import type { StatItem } from '../types';
+
+const Stat: FC = ({ value, label }) => (
+
+
+ {value}
+
+
+ {label}
+
+
+);
+
+export default Stat;
diff --git a/src/widgets/home/components/StepCard.tsx b/src/widgets/home/components/StepCard.tsx
new file mode 100644
index 0000000..2fc7b39
--- /dev/null
+++ b/src/widgets/home/components/StepCard.tsx
@@ -0,0 +1,108 @@
+import { useRef, type FC } from 'react';
+import { motion, useInView } from 'framer-motion';
+import { fadeUp } from '../animations';
+import { C } from '../tokens';
+import { STEPS } from '../constants';
+import type { Step } from '../types';
+
+interface StepCardProps {
+ step: Step;
+ index: number;
+}
+
+const StepCard: FC = ({ step, index }) => {
+ const ref = useRef(null);
+ const inView = useInView(ref, { once: true, margin: '-50px' });
+ const isLast = index === STEPS.length - 1;
+
+ return (
+
+ {/* Vertical connector line */}
+ {!isLast && (
+
+ )}
+
+ {/* Step number badge */}
+
+ {step.num}
+
+
+ {/* Step content */}
+
+
+ {step.icon}
+
+ {step.title}
+
+
+
+ {step.desc}
+
+
+
+ );
+};
+
+export default StepCard;
diff --git a/src/widgets/home/components/StepsSection.tsx b/src/widgets/home/components/StepsSection.tsx
new file mode 100644
index 0000000..f203ff7
--- /dev/null
+++ b/src/widgets/home/components/StepsSection.tsx
@@ -0,0 +1,129 @@
+import type { FC } from 'react';
+import { motion } from 'framer-motion';
+import { fadeUp } from '../animations';
+import { C } from '../tokens';
+import { STEPS } from '../constants';
+import { useIsMobile } from '../hooks/useIsMobile';
+import Section from './Section';
+import StartButton from './StartButton';
+import StepCard from './StepCard';
+
+interface StepsSectionProps {
+ stepsRef: React.RefObject;
+ onScrollTop: () => void;
+}
+
+const StepsSection: FC = ({ stepsRef, onScrollTop }) => {
+ const isMobile = useIsMobile();
+
+ return (
+
+
+
+ {/* Heading */}
+
+
+ Process
+
+
+ How It Works
+
+
+ Six simple steps from upload to certified report.
+
+
+
+ {/* Steps grid */}
+
+ {STEPS.map((step, i) => (
+
+ ))}
+
+
+ {/* CTA banner */}
+
+
+
+ Ready to verify your document?
+
+
+ Get your originality certificate in under 24 hours.
+
+
+
+
+
+
+
+ );
+};
+
+export default StepsSection;
diff --git a/src/widgets/home/components/Ticker.tsx b/src/widgets/home/components/Ticker.tsx
new file mode 100644
index 0000000..688396b
--- /dev/null
+++ b/src/widgets/home/components/Ticker.tsx
@@ -0,0 +1,51 @@
+import type { FC } from 'react';
+import { motion } from 'framer-motion';
+import { C } from '../tokens';
+import { TICKER_ITEMS } from '../constants';
+
+const Ticker: FC = () => {
+ const doubled = [...TICKER_ITEMS, ...TICKER_ITEMS];
+
+ return (
+
+
+ {doubled.map((item, i) => (
+
+ {item}
+
+ ✦
+
+
+ ))}
+
+
+ );
+};
+
+export default Ticker;
diff --git a/src/widgets/home/constants.ts b/src/widgets/home/constants.ts
new file mode 100644
index 0000000..764ff44
--- /dev/null
+++ b/src/widgets/home/constants.ts
@@ -0,0 +1,86 @@
+import { C } from './tokens';
+import type { InfoCardData, StatItem, Step } from './types';
+
+export const STEPS: Step[] = [
+ {
+ num: '01',
+ icon: '▶',
+ title: 'Click Start',
+ desc: 'Hit the Start button on the homepage to kick off the process.',
+ },
+ {
+ num: '02',
+ icon: '🔐',
+ title: 'Register or Log In',
+ desc: 'Create a free account or sign in to access the checking platform.',
+ },
+ {
+ num: '03',
+ icon: '📄',
+ title: 'Upload Document',
+ desc: "Upload your file and enter the document's topic or theme.",
+ },
+ {
+ num: '04',
+ icon: '📤',
+ title: 'Send for Check',
+ desc: 'Submit your document for deep plagiarism analysis.',
+ },
+ {
+ num: '05',
+ icon: '💳',
+ title: 'Complete Payment',
+ desc: 'Pay securely for the plagiarism checking service.',
+ },
+ {
+ num: '06',
+ icon: '📜',
+ title: 'Get Your Report',
+ desc: 'Receive your official certificate and full plagiarism report.',
+ },
+];
+
+export const STATS: StatItem[] = [
+ { value: '98.7%', label: 'Detection accuracy' },
+ { value: '50K+', label: 'Documents checked' },
+ { value: '12+', label: 'Supported formats' },
+ { value: '24h', label: 'Report turnaround' },
+];
+
+export const INFO_CARDS: InfoCardData[] = [
+ {
+ delay: 0,
+ accent: C.accent,
+ bg: C.accentLight,
+ icon: '📖',
+ title: 'What is Plagiarism?',
+ text: "Plagiarism is presenting another person's ideas, words, or creative work without proper attribution. It ranges from direct copy-paste to subtle paraphrasing — both are equally problematic in academic or professional writing.",
+ },
+ {
+ delay: 0.1,
+ accent: C.red,
+ bg: C.redLight,
+ icon: '🛡️',
+ title: 'Why Check Your Document?',
+ text: 'Unintentional plagiarism is more common than you think. Checking before submission protects your reputation, ensures proper citation, meets institutional requirements, and gives you peace of mind.',
+ },
+ {
+ delay: 0.2,
+ accent: C.green,
+ bg: C.greenLight,
+ icon: '📜',
+ title: 'What You Get',
+ text: 'A comprehensive originality report with matched sources, similarity percentages, and an official signed certificate — accepted by universities, publishers, and institutions worldwide.',
+ },
+];
+
+export const TICKER_ITEMS: string[] = [
+ 'Originality Verified',
+ 'Academic Integrity',
+ 'Trusted Reports',
+ 'Deep Analysis',
+ 'Fast Results',
+ 'Official Certificate',
+];
+
+export const DOC_LINE_WIDTHS: number[] = [100, 88, 95, 72, 90, 60];
diff --git a/src/widgets/home/hooks/useIsMobile.ts b/src/widgets/home/hooks/useIsMobile.ts
new file mode 100644
index 0000000..43597f6
--- /dev/null
+++ b/src/widgets/home/hooks/useIsMobile.ts
@@ -0,0 +1,16 @@
+'use client';
+import { useEffect, useState } from 'react';
+
+export function useIsMobile(breakpoint = 768): boolean {
+ const [isMobile, setIsMobile] = useState(
+ typeof window !== 'undefined' ? window.innerWidth <= breakpoint : false,
+ );
+
+ useEffect(() => {
+ const handler = () => setIsMobile(window.innerWidth <= breakpoint);
+ window.addEventListener('resize', handler);
+ return () => window.removeEventListener('resize', handler);
+ }, [breakpoint]);
+
+ return isMobile;
+}
diff --git a/src/widgets/home/index.tsx b/src/widgets/home/index.tsx
new file mode 100644
index 0000000..51c310a
--- /dev/null
+++ b/src/widgets/home/index.tsx
@@ -0,0 +1,29 @@
+'use client';
+import { useRef, type FC } from 'react';
+import { useScroll, useTransform } from 'framer-motion';
+import Hero from './components/Hero';
+import InfoSection from './components/InfoSection';
+import StepsSection from './components/StepsSection';
+import Ticker from './components/Ticker';
+
+const PlagiarismLanding: FC = () => {
+ const stepsRef = useRef(null);
+ const { scrollY } = useScroll();
+ const blobY = useTransform(scrollY, [0, 600], [0, 80]);
+
+ const scrollToSteps = () =>
+ stepsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+ const scrollToTop = () => window.scrollTo({ top: 0, behavior: 'smooth' });
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default PlagiarismLanding;
diff --git a/src/widgets/home/tokens.ts b/src/widgets/home/tokens.ts
new file mode 100644
index 0000000..c824688
--- /dev/null
+++ b/src/widgets/home/tokens.ts
@@ -0,0 +1,20 @@
+export const C = {
+ bg: '#F7F5F0',
+ surface: '#FFFFFF',
+ surfaceWarm: '#F0EDE6',
+ border: '#E2DDD6',
+ borderDark: '#C8C2B8',
+ accent: '#1A4FD6',
+ accentLight: '#EEF2FD',
+ accentHover: '#1240B8',
+ text: '#1A1814',
+ textMid: '#4A4640',
+ textMuted: '#8A8680',
+ green: '#1A7A4A',
+ greenLight: '#E8F5EE',
+ red: '#C0392B',
+ redLight: '#FDF0EE',
+ gold: '#B8860B',
+ shadow: 'rgba(26,22,16,0.07)',
+ shadowMd: 'rgba(26,22,16,0.13)',
+} as const;
diff --git a/src/widgets/home/types.ts b/src/widgets/home/types.ts
new file mode 100644
index 0000000..36369fc
--- /dev/null
+++ b/src/widgets/home/types.ts
@@ -0,0 +1,20 @@
+export interface Step {
+ num: string;
+ icon: string;
+ title: string;
+ desc: string;
+}
+
+export interface StatItem {
+ value: string;
+ label: string;
+}
+
+export interface InfoCardData {
+ delay: number;
+ accent: string;
+ bg: string;
+ icon: string;
+ title: string;
+ text: string;
+}
diff --git a/src/widgets/navbar/ui/index.tsx b/src/widgets/navbar/ui/index.tsx
index 6f1abb8..ec0da6f 100644
--- a/src/widgets/navbar/ui/index.tsx
+++ b/src/widgets/navbar/ui/index.tsx
@@ -20,8 +20,8 @@ const Navbar = () => {
const menu = getMenu(t);
return (
-
-
+
+
{/* Desktop Menu */}
From 6041e6a719a4b044085561f1412cf2d862a32ecd Mon Sep 17 00:00:00 2001
From: "nabijonovdavronbek619@gmail.com"
Date: Wed, 1 Apr 2026 10:53:42 +0500
Subject: [PATCH 2/3] translation added
---
src/features/auth/login/ui/form.tsx | 30 ++---
src/features/auth/register/ui/form.tsx | 52 +++-----
src/shared/config/i18n/messages/en.json | 103 ++++++++++++++++
src/shared/config/i18n/messages/ru.json | 103 ++++++++++++++++
src/shared/config/i18n/messages/uz.d.json.ts | 105 +++++++++++++++-
src/shared/config/i18n/messages/uz.json | 105 +++++++++++++++-
src/widgets/home/components/Hero.tsx | 43 +++++--
src/widgets/home/components/InfoSection.tsx | 119 ++++++++++++-------
src/widgets/home/components/StartButton.tsx | 4 +-
src/widgets/home/components/StepCard.tsx | 44 ++++++-
src/widgets/home/components/StepsSection.tsx | 12 +-
src/widgets/home/components/Ticker.tsx | 24 +++-
src/widgets/paymentModal/ui/Paymebutton.tsx | 6 +-
src/widgets/paymentModal/ui/Paymentmodal.tsx | 22 ++--
src/widgets/paymentModal/ui/Pricesummary.tsx | 8 +-
15 files changed, 650 insertions(+), 130 deletions(-)
diff --git a/src/features/auth/login/ui/form.tsx b/src/features/auth/login/ui/form.tsx
index ab00bd1..67d567a 100644
--- a/src/features/auth/login/ui/form.tsx
+++ b/src/features/auth/login/ui/form.tsx
@@ -11,9 +11,12 @@ import { MotionWrapper } from '../../../../shared/ui/motion';
import { useLoginForm } from '../lib/useLoginForm';
import { useLoginModal } from '@/shared/zustand/auth';
import Link from 'next/link';
+import { useTranslations } from 'next-intl';
export function LoginForm() {
const [isFocused, setIsFocused] = useState(false);
+ const t = useTranslations('Auth.Login');
+ const tCommon = useTranslations('Common');
const { phone, setPhone, submit, error, loading } = useLoginForm();
const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
@@ -49,14 +52,12 @@ export function LoginForm() {
{/* Header */}
- Xush kelibsiz
+ {t('welcome')}
- Kirish
+ {t('title')}
-
- Telefon raqamingizni kiriting.
-
+ {t('description')}
);
@@ -112,14 +113,12 @@ export function RegisterFormUI() {
{/* Header */}
- Create account
+ {t('headerLabel')}
- Register
+ {t('title')}
-
- Fill in your details to get started.
-
+
{t('description')}
diff --git a/src/shared/config/i18n/messages/en.json b/src/shared/config/i18n/messages/en.json
index 49410a8..86f1d54 100644
--- a/src/shared/config/i18n/messages/en.json
+++ b/src/shared/config/i18n/messages/en.json
@@ -100,5 +100,108 @@
"downloadCertificate": "Download Certificate",
"unknownError": "Unknown error",
"words": "words"
+ },
+ "Hero": {
+ "badge": "Academic Integrity Platform",
+ "mainHeading": "Is Your Work",
+ "mainHeadingItalic": "Truly Original?",
+ "description": "Plagiarism is presenting someone else's ideas or words as your own. In academia and professional life, it carries serious consequences. Our platform detects it in seconds — so you can submit with full confidence.",
+ "certificateNote": "Certificate issued within 24h"
+ },
+ "Common": {
+ "startButton": "Start Checking →",
+ "complete": "✓ Complete",
+ "or": "or",
+ "submitting": "Submitting…",
+ "autoFilled": "Auto-filled",
+ "removeFile": "Remove file"
+ },
+ "InfoSection": {
+ "label": "Why It Matters",
+ "heading": "Understanding Plagiarism"
+ },
+ "StepsSection": {
+ "label": "Process",
+ "heading": "How It Works",
+ "description": "Six simple steps from upload to certified report.",
+ "ctaHeading": "Ready to verify your document?",
+ "ctaDescription": "Get your originality certificate in under 24 hours."
+ },
+ "Steps": {
+ "step1Title": "Click Start",
+ "step1Desc": "Begin your plagiarism check journey",
+ "step2Title": "Register or Log In",
+ "step2Desc": "Create an account or sign in to your existing one",
+ "step3Title": "Upload Document",
+ "step3Desc": "Upload your PDF, DOC, DOCX, or TXT file (max 20MB)",
+ "step4Title": "Send for Check",
+ "step4Desc": "Submit your document for comprehensive analysis",
+ "step5Title": "Complete Payment",
+ "step5Desc": "Pay securely for your plagiarism check service",
+ "step6Title": "Get Your Report",
+ "step6Desc": "Receive detailed results and certificate within 24 hours"
+ },
+ "Stats": {
+ "accuracy": "Detection accuracy",
+ "documents": "Documents checked",
+ "formats": "Supported formats",
+ "turnaround": "Report turnaround"
+ },
+ "InfoCards": {
+ "card1Title": "What is Plagiarism?",
+ "card1Desc": "Plagiarism occurs when someone uses another person's ideas, words, or work without proper credit. It can be intentional or unintentional.",
+ "card2Title": "Why Check Your Document?",
+ "card2Desc": "Ensure academic integrity, avoid penalties, and maintain your reputation. Our service provides comprehensive plagiarism detection.",
+ "card3Title": "What You Get",
+ "card3Desc": "Detailed plagiarism report, similarity percentage, matched sources, and an official certificate of originality."
+ },
+ "Ticker": {
+ "item1": "Originality Verified",
+ "item2": "Academic Integrity",
+ "item3": "Trusted Reports",
+ "item4": "Deep Analysis",
+ "item5": "Fast Results",
+ "item6": "Official Certificate"
+ },
+ "Auth": {
+ "Login": {
+ "welcome": "Welcome back",
+ "title": "Login",
+ "description": "Enter your phone number to continue.",
+ "phoneLabel": "Phone number",
+ "phonePlaceholder": "90 123 45 67",
+ "digitsEntered": "{count} digits entered",
+ "sending": "Sending…",
+ "sendCode": "Send code",
+ "registerPrompt": "Don't have an account?",
+ "registerLink": "Register"
+ },
+ "Register": {
+ "successTitle": "You're registered",
+ "successMessage": "We'll be in touch shortly.",
+ "headerLabel": "Create account",
+ "title": "Register",
+ "description": "Fill in your details to get started.",
+ "phoneLabel": "Phone number",
+ "phonePlaceholder": "90 123 45 67",
+ "digitsEntered": "{count} digits entered",
+ "namePlaceholder": "Ali",
+ "surnamePlaceholder": "Karimov",
+ "terms": "I agree to the Terms of Service and Privacy Policy",
+ "submitButton": "Create account"
+ }
+ },
+ "Payment": {
+ "title": "Payment",
+ "description": "Review your order and pay securely",
+ "orderSummary": "Order Summary",
+ "certificateIncluded": "Certificate of completion included",
+ "paymentMethod": "Payment Method",
+ "security": "Secured by Payme · SSL encrypted",
+ "serviceFee": "Service fee",
+ "certificateLabel": "Certificate",
+ "total": "Total",
+ "connecting": "Connecting to Payme…",
+ "payButton": "Pay with Payme"
}
}
diff --git a/src/shared/config/i18n/messages/ru.json b/src/shared/config/i18n/messages/ru.json
index 13ae46a..ee3c5ce 100644
--- a/src/shared/config/i18n/messages/ru.json
+++ b/src/shared/config/i18n/messages/ru.json
@@ -100,5 +100,108 @@
"downloadCertificate": "Скачать сертификат",
"unknownError": "Неизвестная ошибка",
"words": "слов"
+ },
+ "Hero": {
+ "badge": "Платформа академической честности",
+ "mainHeading": "Ваша работа",
+ "mainHeadingItalic": "Действительно оригинальна?",
+ "description": "Плагиат - это представление идей или слов другого человека как своих собственных. В академической и профессиональной жизни это несет серьезные последствия. Наша платформа обнаруживает его за секунды — чтобы вы могли отправлять с полной уверенностью.",
+ "certificateNote": "Сертификат выдается в течение 24 часов"
+ },
+ "Common": {
+ "startButton": "Начать проверку →",
+ "complete": "✓ Завершено",
+ "or": "или",
+ "submitting": "Отправка…",
+ "autoFilled": "Автозаполнено",
+ "removeFile": "Удалить файл"
+ },
+ "InfoSection": {
+ "label": "Почему это важно",
+ "heading": "Понимание плагиата"
+ },
+ "StepsSection": {
+ "label": "Процесс",
+ "heading": "Как это работает",
+ "description": "Шесть простых шагов от загрузки до сертифицированного отчета.",
+ "ctaHeading": "Готовы проверить ваш документ?",
+ "ctaDescription": "Получите сертификат оригинальности менее чем за 24 часа."
+ },
+ "Steps": {
+ "step1Title": "Нажмите Начать",
+ "step1Desc": "Начните ваше путешествие по проверке на плагиат",
+ "step2Title": "Зарегистрируйтесь или войдите",
+ "step2Desc": "Создайте аккаунт или войдите в существующий",
+ "step3Title": "Загрузите документ",
+ "step3Desc": "Загрузите ваш PDF, DOC, DOCX или TXT файл (макс 20МБ)",
+ "step4Title": "Отправьте на проверку",
+ "step4Desc": "Отправьте ваш документ на комплексный анализ",
+ "step5Title": "Завершите оплату",
+ "step5Desc": "Оплатите безопасно услугу проверки на плагиат",
+ "step6Title": "Получите ваш отчет",
+ "step6Desc": "Получите подробные результаты и сертификат в течение 24 часов"
+ },
+ "Stats": {
+ "accuracy": "Точность обнаружения",
+ "documents": "Проверено документов",
+ "formats": "Поддерживаемые форматы",
+ "turnaround": "Время выполнения отчета"
+ },
+ "InfoCards": {
+ "card1Title": "Что такое плагиат?",
+ "card1Desc": "Плагиат происходит, когда кто-то использует идеи, слова или работу другого человека без надлежащего указания. Это может быть преднамеренным или непреднамеренным.",
+ "card2Title": "Почему проверять ваш документ?",
+ "card2Desc": "Обеспечьте академическую честность, избегайте штрафов и поддерживайте вашу репутацию. Наш сервис предоставляет комплексное обнаружение плагиата.",
+ "card3Title": "Что вы получаете",
+ "card3Desc": "Подробный отчет о плагиате, процент схожести, найденные источники и официальный сертификат оригинальности."
+ },
+ "Ticker": {
+ "item1": "Оригинальность подтверждена",
+ "item2": "Академическая честность",
+ "item3": "Надежные отчеты",
+ "item4": "Глубокий анализ",
+ "item5": "Быстрые результаты",
+ "item6": "Официальный сертификат"
+ },
+ "Auth": {
+ "Login": {
+ "welcome": "Добро пожаловать обратно",
+ "title": "Вход",
+ "description": "Введите ваш номер телефона для продолжения.",
+ "phoneLabel": "Номер телефона",
+ "phonePlaceholder": "90 123 45 67",
+ "digitsEntered": "Введено {count} цифр",
+ "sending": "Отправка…",
+ "sendCode": "Отправить код",
+ "registerPrompt": "Нет аккаунта?",
+ "registerLink": "Зарегистрироваться"
+ },
+ "Register": {
+ "successTitle": "Вы зарегистрированы",
+ "successMessage": "Мы свяжемся с вами в ближайшее время.",
+ "headerLabel": "Создать аккаунт",
+ "title": "Регистрация",
+ "description": "Заполните ваши данные, чтобы начать.",
+ "phoneLabel": "Номер телефона",
+ "phonePlaceholder": "90 123 45 67",
+ "digitsEntered": "Введено {count} цифр",
+ "namePlaceholder": "Али",
+ "surnamePlaceholder": "Каримов",
+ "terms": "Я согласен с Условиями обслуживания и Политикой конфиденциальности",
+ "submitButton": "Создать аккаунт"
+ }
+ },
+ "Payment": {
+ "title": "Оплата",
+ "description": "Просмотрите ваш заказ и оплатите безопасно",
+ "orderSummary": "Сводка заказа",
+ "certificateIncluded": "Включен сертификат завершения",
+ "paymentMethod": "Способ оплаты",
+ "security": "Защищено Payme · SSL шифрование",
+ "serviceFee": "Стоимость услуги",
+ "certificateLabel": "Сертификат",
+ "total": "Итого",
+ "connecting": "Подключение к Payme…",
+ "payButton": "Оплатить через Payme"
}
}
diff --git a/src/shared/config/i18n/messages/uz.d.json.ts b/src/shared/config/i18n/messages/uz.d.json.ts
index 7b0caa8..709d3f1 100644
--- a/src/shared/config/i18n/messages/uz.d.json.ts
+++ b/src/shared/config/i18n/messages/uz.d.json.ts
@@ -95,7 +95,7 @@ declare const messages: {
processedAt: 'Qayta ishlangan';
certificate: 'Sertifikat';
noCertificate: 'Bu tekshiruv uchun sertifikat berilmagan.';
- noCertificateHighSimilarity: "Yuoqori o'xshashlik natijasida sertifikatlar berilmaydi.";
+ noCertificateHighSimilarity: "Yuqori o'xshashlik natijasida sertifikatlar berilmaydi.";
issued: 'Berilgan';
expires: 'Muddati tugaydi';
issuer: 'Beruvchi';
@@ -104,5 +104,108 @@ declare const messages: {
unknownError: "Noma'lum xato";
words: "so'z";
};
+ Hero: {
+ badge: 'Akademik halollik platformasi';
+ mainHeading: 'Sizning ishingiz';
+ mainHeadingItalic: 'Haqiqatan ham originalmi?';
+ description: "Plagiat - bu boshqa birovning g'oyalarini yoki so'zlarini o'z g'oyalar yoki so'zlari sifatida taqdim etish. Akademik va professional hayotda bu jiddiy oqibatlarga olib keladi. Bizning platformamiz buni soniyalar ichida aniqlaydi — shuning uchun siz to'liq ishonch bilan yuborishingiz mumkin.";
+ certificateNote: 'Sertifikat 24 soat ichida beriladi';
+ };
+ Common: {
+ startButton: 'Tekshirishni boshlash →';
+ complete: "✓ To'liq";
+ or: 'yoki';
+ submitting: 'Yuborilmoqda…';
+ autoFilled: "Avto-to'ldirilgan";
+ removeFile: 'Faylni olib tashlash';
+ };
+ InfoSection: {
+ label: 'Nima uchun bu muhim';
+ heading: 'Plagiatni tushunish';
+ };
+ StepsSection: {
+ label: 'Jarayon';
+ heading: 'Bu qanday ishlaydi';
+ description: 'Yuklashdan sertifikatlangan hisobotgacha oltita oddiy qadam.';
+ ctaHeading: 'Hujjatingizni tekshirishga tayyormisiz?';
+ ctaDescription: '24 soat ichida orijinallik sertifikatini oling.';
+ };
+ Steps: {
+ step1Title: 'Boshlash tugmasini bosing';
+ step1Desc: 'Plagiat tekshiruvi sayohatini boshlang';
+ step2Title: "Ro'yxatdan o'ting yoki kiring";
+ step2Desc: 'Hisob yarating yoki mavjud hisobingizga kiring';
+ step3Title: 'Hujjatni yuklang';
+ step3Desc: 'PDF, DOC, DOCX yoki TXT faylingizni yuklang (maks 20MB)';
+ step4Title: 'Tekshirish uchun yuboring';
+ step4Desc: 'Hujjatingizni kompleks tahlil uchun yuboring';
+ step5Title: "To'lovni amalga oshiring";
+ step5Desc: "Plagiat tekshiruvi xizmatini xavfsiz to'lang";
+ step6Title: 'Hisobotingizni oling';
+ step6Desc: '24 soat ichida batafsil natijalar va sertifikatni oling';
+ };
+ Stats: {
+ accuracy: 'Aniqlash aniqligi';
+ documents: 'Tekshirilgan hujjatlar';
+ formats: "Qo'llab-quvvatlangan formatlar";
+ turnaround: 'Hisobot tayyorlanish vaqti';
+ };
+ InfoCards: {
+ card1Title: 'Plagiat nima?';
+ card1Desc: "Plagiat - bu kimdir boshqa birovning g'oyalarini, so'zlarini yoki ishini tegishli kredit bermay ishlatganda yuzaga keladi. Bu qasddan yoki qasdsiz bo'lishi mumkin.";
+ card2Title: 'Nima uchun hujjatingizni tekshirish kerak?';
+ card2Desc: "Akademik halollikni ta'minlang, jarimalardan qoching va obro'ingizni saqlang. Bizning xizmatimiz kompleks plagiat aniqlashni taqdim etadi.";
+ card3Title: 'Siz nima olasiz';
+ card3Desc: "Batafsil plagiat hisoboti, o'xshashlik foizi, topilgan manbalar va orijinallikning rasmiy sertifikati.";
+ };
+ Ticker: {
+ item1: 'Orijinallik tasdiqlangan';
+ item2: 'Akademik halollik';
+ item3: 'Ishonchli hisobotlar';
+ item4: 'Chuqur tahlil';
+ item5: 'Tez natijalar';
+ item6: 'Rasmiy sertifikat';
+ };
+ Auth: {
+ Login: {
+ welcome: 'Xush kelibsiz';
+ title: 'Kirish';
+ description: 'Davomini davom ettirish uchun telefon raqamingizni kiriting.';
+ phoneLabel: 'Telefon raqami';
+ phonePlaceholder: '90 123 45 67';
+ digitsEntered: '{count} ta raqam kiritildi';
+ sending: 'Yuborilmoqda…';
+ sendCode: 'Kodni yuborish';
+ registerPrompt: "Hisobingiz yo'qmi?";
+ registerLink: "Ro'yxatdan o'tish";
+ };
+ Register: {
+ successTitle: "Siz ro'yxatdan o'tdingiz";
+ successMessage: "Tez orada siz bilan bog'lanamiz.";
+ headerLabel: 'Hisob yaratish';
+ title: "Ro'yxatdan o'tish";
+ description: "Boshlash uchun ma'lumotlaringizni to'ldiring.";
+ phoneLabel: 'Telefon raqami';
+ phonePlaceholder: '90 123 45 67';
+ digitsEntered: '{count} ta raqam kiritildi';
+ namePlaceholder: 'Ali';
+ surnamePlaceholder: 'Karimov';
+ terms: "Men Xizmat ko'rsatish shartlari va Maxfiylik siyosatiga roziman";
+ submitButton: 'Hisob yaratish';
+ };
+ };
+ Payment: {
+ title: "To'lov";
+ description: "Buyurtmangizni ko'rib chiqing va xavfsiz to'lang";
+ orderSummary: "Buyurtma umumiy ko'rinishi";
+ certificateIncluded: 'Tugallanish sertifikati kiritilgan';
+ paymentMethod: "To'lov usuli";
+ security: 'Payme tomonidan himoyalangan · SSL shifrlash';
+ serviceFee: "Xizmat to'lovi";
+ certificateLabel: 'Sertifikat';
+ total: 'Jami';
+ connecting: 'Paymega ulanmoqda…';
+ payButton: "Payme orqali to'lash";
+ };
};
export default messages;
diff --git a/src/shared/config/i18n/messages/uz.json b/src/shared/config/i18n/messages/uz.json
index c6aecb0..bf1078e 100644
--- a/src/shared/config/i18n/messages/uz.json
+++ b/src/shared/config/i18n/messages/uz.json
@@ -92,7 +92,7 @@
"processedAt": "Qayta ishlangan",
"certificate": "Sertifikat",
"noCertificate": "Bu tekshiruv uchun sertifikat berilmagan.",
- "noCertificateHighSimilarity": "Yuoqori o'xshashlik natijasida sertifikatlar berilmaydi.",
+ "noCertificateHighSimilarity": "Yuqori o'xshashlik natijasida sertifikatlar berilmaydi.",
"issued": "Berilgan",
"expires": "Muddati tugaydi",
"issuer": "Beruvchi",
@@ -100,5 +100,108 @@
"downloadCertificate": "Sertifikatni yuklab olish",
"unknownError": "Noma'lum xato",
"words": "so'z"
+ },
+ "Hero": {
+ "badge": "Akademik halollik platformasi",
+ "mainHeading": "Sizning ishingiz",
+ "mainHeadingItalic": "Haqiqatan ham originalmi?",
+ "description": "Plagiat - bu boshqa birovning g'oyalarini yoki so'zlarini o'z g'oyalar yoki so'zlari sifatida taqdim etish. Akademik va professional hayotda bu jiddiy oqibatlarga olib keladi. Bizning platformamiz buni soniyalar ichida aniqlaydi — shuning uchun siz to'liq ishonch bilan yuborishingiz mumkin.",
+ "certificateNote": "Sertifikat 24 soat ichida beriladi"
+ },
+ "Common": {
+ "startButton": "Tekshirishni boshlash →",
+ "complete": "✓ To'liq",
+ "or": "yoki",
+ "submitting": "Yuborilmoqda…",
+ "autoFilled": "Avto-to'ldirilgan",
+ "removeFile": "Faylni olib tashlash"
+ },
+ "InfoSection": {
+ "label": "Nima uchun bu muhim",
+ "heading": "Plagiatni tushunish"
+ },
+ "StepsSection": {
+ "label": "Jarayon",
+ "heading": "Bu qanday ishlaydi",
+ "description": "Yuklashdan sertifikatlangan hisobotgacha oltita oddiy qadam.",
+ "ctaHeading": "Hujjatingizni tekshirishga tayyormisiz?",
+ "ctaDescription": "24 soat ichida orijinallik sertifikatini oling."
+ },
+ "Steps": {
+ "step1Title": "Boshlash tugmasini bosing",
+ "step1Desc": "Plagiat tekshiruvi sayohatini boshlang",
+ "step2Title": "Ro'yxatdan o'ting yoki kiring",
+ "step2Desc": "Hisob yarating yoki mavjud hisobingizga kiring",
+ "step3Title": "Hujjatni yuklang",
+ "step3Desc": "PDF, DOC, DOCX yoki TXT faylingizni yuklang (maks 20MB)",
+ "step4Title": "Tekshirish uchun yuboring",
+ "step4Desc": "Hujjatingizni kompleks tahlil uchun yuboring",
+ "step5Title": "To'lovni amalga oshiring",
+ "step5Desc": "Plagiat tekshiruvi xizmatini xavfsiz to'lang",
+ "step6Title": "Hisobotingizni oling",
+ "step6Desc": "24 soat ichida batafsil natijalar va sertifikatni oling"
+ },
+ "Stats": {
+ "accuracy": "Aniqlash aniqligi",
+ "documents": "Tekshirilgan hujjatlar",
+ "formats": "Qo'llab-quvvatlangan formatlar",
+ "turnaround": "Hisobot tayyorlanish vaqti"
+ },
+ "InfoCards": {
+ "card1Title": "Plagiat nima?",
+ "card1Desc": "Plagiat - bu kimdir boshqa birovning g'oyalarini, so'zlarini yoki ishini tegishli kredit bermay ishlatganda yuzaga keladi. Bu qasddan yoki qasdsiz bo'lishi mumkin.",
+ "card2Title": "Nima uchun hujjatingizni tekshirish kerak?",
+ "card2Desc": "Akademik halollikni ta'minlang, jarimalardan qoching va obro'ingizni saqlang. Bizning xizmatimiz kompleks plagiat aniqlashni taqdim etadi.",
+ "card3Title": "Siz nima olasiz",
+ "card3Desc": "Batafsil plagiat hisoboti, o'xshashlik foizi, topilgan manbalar va orijinallikning rasmiy sertifikati."
+ },
+ "Ticker": {
+ "item1": "Orijinallik tasdiqlangan",
+ "item2": "Akademik halollik",
+ "item3": "Ishonchli hisobotlar",
+ "item4": "Chuqur tahlil",
+ "item5": "Tez natijalar",
+ "item6": "Rasmiy sertifikat"
+ },
+ "Auth": {
+ "Login": {
+ "welcome": "Xush kelibsiz",
+ "title": "Kirish",
+ "description": "Davomini davom ettirish uchun telefon raqamingizni kiriting.",
+ "phoneLabel": "Telefon raqami",
+ "phonePlaceholder": "90 123 45 67",
+ "digitsEntered": "{count} ta raqam kiritildi",
+ "sending": "Yuborilmoqda…",
+ "sendCode": "Kodni yuborish",
+ "registerPrompt": "Hisobingiz yo'qmi?",
+ "registerLink": "Ro'yxatdan o'tish"
+ },
+ "Register": {
+ "successTitle": "Siz ro'yxatdan o'tdingiz",
+ "successMessage": "Tez orada siz bilan bog'lanamiz.",
+ "headerLabel": "Hisob yaratish",
+ "title": "Ro'yxatdan o'tish",
+ "description": "Boshlash uchun ma'lumotlaringizni to'ldiring.",
+ "phoneLabel": "Telefon raqami",
+ "phonePlaceholder": "90 123 45 67",
+ "digitsEntered": "{count} ta raqam kiritildi",
+ "namePlaceholder": "Ali",
+ "surnamePlaceholder": "Karimov",
+ "terms": "Men Xizmat ko'rsatish shartlari va Maxfiylik siyosatiga roziman",
+ "submitButton": "Hisob yaratish"
+ }
+ },
+ "Payment": {
+ "title": "To'lov",
+ "description": "Buyurtmangizni ko'rib chiqing va xavfsiz to'lang",
+ "orderSummary": "Buyurtma umumiy ko'rinishi",
+ "certificateIncluded": "Tugallanish sertifikati kiritilgan",
+ "paymentMethod": "To'lov usuli",
+ "security": "Payme tomonidan himoyalangan · SSL shifrlash",
+ "serviceFee": "Xizmat to'lovi",
+ "certificateLabel": "Sertifikat",
+ "total": "Jami",
+ "connecting": "Paymega ulanmoqda…",
+ "payButton": "Payme orqali to'lash"
}
}
diff --git a/src/widgets/home/components/Hero.tsx b/src/widgets/home/components/Hero.tsx
index 3b88a6a..612be62 100644
--- a/src/widgets/home/components/Hero.tsx
+++ b/src/widgets/home/components/Hero.tsx
@@ -9,6 +9,7 @@ import DocIllustration from './DocIllustration';
import Section from './Section';
import Stat from './Stat';
import StartButton from './StartButton';
+import { useTranslations } from 'next-intl';
interface HeroProps {
onStart: () => void;
@@ -17,6 +18,23 @@ interface HeroProps {
const Hero: FC
= ({ onStart, blobY }) => {
const isMobile = useIsMobile();
+ const t = useTranslations('Hero');
+ const tStats = useTranslations('Stats');
+
+ const getTranslatedLabel = (label: string) => {
+ switch (label) {
+ case 'Detection accuracy':
+ return tStats('accuracy');
+ case 'Documents checked':
+ return tStats('documents');
+ case 'Supported formats':
+ return tStats('formats');
+ case 'Report turnaround':
+ return tStats('turnaround');
+ default:
+ return label;
+ }
+ };
return (
@@ -71,7 +89,7 @@ const Hero: FC
= ({ onStart, blobY }) => {
>
{/* Text block */}
- Academic Integrity Platform
+ {t('badge')}
= ({ onStart, blobY }) => {
marginBottom: 4,
}}
>
- Is Your Work
+ {t('mainHeading')}
= ({ onStart, blobY }) => {
marginBottom: 28,
}}
>
- Truly Original?
+ {t('mainHeadingItalic')}
@@ -117,10 +135,7 @@ const Hero: FC = ({ onStart, blobY }) => {
marginBottom: 40,
}}
>
- Plagiarism is presenting someone else‘s ideas or words as
- your own. In academia and professional life, it carries serious
- consequences. Our platform detects it in seconds — so you can
- submit with full confidence.
+ {t('description')}
= ({ onStart, blobY }) => {
fontFamily: "'DM Mono', monospace",
}}
>
- Certificate issued within 24h
+ {t('certificateNote')}
@@ -165,9 +180,15 @@ const Hero: FC = ({ onStart, blobY }) => {
borderTop: `1px solid ${C.border}`,
}}
>
- {STATS.map((s) => (
-
- ))}
+ {STATS.map((s) => {
+ return (
+
+ );
+ })}
diff --git a/src/widgets/home/components/InfoSection.tsx b/src/widgets/home/components/InfoSection.tsx
index 35ca690..eaffb4a 100644
--- a/src/widgets/home/components/InfoSection.tsx
+++ b/src/widgets/home/components/InfoSection.tsx
@@ -5,51 +5,82 @@ import { C } from '../tokens';
import { INFO_CARDS } from '../constants';
import InfoCard from './InfoCard';
import Section from './Section';
+import { useTranslations } from 'next-intl';
-const InfoSection: FC = () => (
-
-
- {/* Heading */}
-
-
- Why It Matters
-
-
- Understanding Plagiarism
-
-
+const InfoSection: FC = () => {
+ const t = useTranslations('InfoSection');
+ const tCards = useTranslations('InfoCards');
- {/* Cards */}
-
- {INFO_CARDS.map((card) => (
-
- ))}
-
-
-
-);
+ const getTranslatedCard = (card: (typeof INFO_CARDS)[0]) => {
+ switch (card.title) {
+ case 'What is Plagiarism?':
+ return {
+ ...card,
+ title: tCards('card1Title'),
+ text: tCards('card1Desc'),
+ };
+ case 'Why Check Your Document?':
+ return {
+ ...card,
+ title: tCards('card2Title'),
+ text: tCards('card2Desc'),
+ };
+ case 'What You Get':
+ return {
+ ...card,
+ title: tCards('card3Title'),
+ text: tCards('card3Desc'),
+ };
+ default:
+ return card;
+ }
+ };
+
+ return (
+
+
+ {/* Heading */}
+
+
+ {t('label')}
+
+
+ {t('heading')}
+
+
+
+ {/* Cards */}
+
+ {INFO_CARDS.map((card) => (
+
+ ))}
+
+
+
+ );
+};
export default InfoSection;
diff --git a/src/widgets/home/components/StartButton.tsx b/src/widgets/home/components/StartButton.tsx
index 4d2bde4..8e796c3 100644
--- a/src/widgets/home/components/StartButton.tsx
+++ b/src/widgets/home/components/StartButton.tsx
@@ -2,6 +2,7 @@
import { useState, type FC } from 'react';
import { motion } from 'framer-motion';
import { C } from '../tokens';
+import { useTranslations } from 'next-intl';
interface StartButtonProps {
onClick: () => void;
@@ -10,6 +11,7 @@ interface StartButtonProps {
const StartButton: FC = ({ onClick, small = false }) => {
const [hovered, setHovered] = useState(false);
+ const t = useTranslations('Common');
return (
= ({ onClick, small = false }) => {
: `0 2px 8px ${C.accent}22`,
}}
>
- Start Checking →
+ {t('startButton')}
);
};
diff --git a/src/widgets/home/components/StepCard.tsx b/src/widgets/home/components/StepCard.tsx
index 2fc7b39..e874d34 100644
--- a/src/widgets/home/components/StepCard.tsx
+++ b/src/widgets/home/components/StepCard.tsx
@@ -4,6 +4,7 @@ import { fadeUp } from '../animations';
import { C } from '../tokens';
import { STEPS } from '../constants';
import type { Step } from '../types';
+import { useTranslations } from 'next-intl';
interface StepCardProps {
step: Step;
@@ -14,6 +15,45 @@ const StepCard: FC = ({ step, index }) => {
const ref = useRef(null);
const inView = useInView(ref, { once: true, margin: '-50px' });
const isLast = index === STEPS.length - 1;
+ const t = useTranslations('Steps');
+
+ const getStepTitle = (num: string) => {
+ switch (num) {
+ case '01':
+ return t('step1Title');
+ case '02':
+ return t('step2Title');
+ case '03':
+ return t('step3Title');
+ case '04':
+ return t('step4Title');
+ case '05':
+ return t('step5Title');
+ case '06':
+ return t('step6Title');
+ default:
+ return step.title;
+ }
+ };
+
+ const getStepDesc = (num: string) => {
+ switch (num) {
+ case '01':
+ return t('step1Desc');
+ case '02':
+ return t('step2Desc');
+ case '03':
+ return t('step3Desc');
+ case '04':
+ return t('step4Desc');
+ case '05':
+ return t('step5Desc');
+ case '06':
+ return t('step6Desc');
+ default:
+ return step.desc;
+ }
+ };
return (
= ({ step, index }) => {
fontWeight: 700,
}}
>
- {step.title}
+ {getStepTitle(step.num)}
= ({ step, index }) => {
margin: 0,
}}
>
- {step.desc}
+ {getStepDesc(step.num)}
diff --git a/src/widgets/home/components/StepsSection.tsx b/src/widgets/home/components/StepsSection.tsx
index f203ff7..6872a92 100644
--- a/src/widgets/home/components/StepsSection.tsx
+++ b/src/widgets/home/components/StepsSection.tsx
@@ -7,6 +7,7 @@ import { useIsMobile } from '../hooks/useIsMobile';
import Section from './Section';
import StartButton from './StartButton';
import StepCard from './StepCard';
+import { useTranslations } from 'next-intl';
interface StepsSectionProps {
stepsRef: React.RefObject;
@@ -15,6 +16,7 @@ interface StepsSectionProps {
const StepsSection: FC = ({ stepsRef, onScrollTop }) => {
const isMobile = useIsMobile();
+ const t = useTranslations('StepsSection');
return (
= ({ stepsRef, onScrollTop }) => {
marginBottom: 10,
}}
>
- Process
+ {t('label')}
= ({ stepsRef, onScrollTop }) => {
lineHeight: 1.2,
}}
>
- How It Works
+ {t('heading')}
= ({ stepsRef, onScrollTop }) => {
lineHeight: 1.75,
}}
>
- Six simple steps from upload to certified report.
+ {t('description')}
@@ -112,10 +114,10 @@ const StepsSection: FC
= ({ stepsRef, onScrollTop }) => {
fontWeight: 700,
}}
>
- Ready to verify your document?
+ {t('ctaHeading')}
- Get your originality certificate in under 24 hours.
+ {t('ctaDescription')}
diff --git a/src/widgets/home/components/Ticker.tsx b/src/widgets/home/components/Ticker.tsx
index 688396b..65d9a7b 100644
--- a/src/widgets/home/components/Ticker.tsx
+++ b/src/widgets/home/components/Ticker.tsx
@@ -2,10 +2,32 @@ import type { FC } from 'react';
import { motion } from 'framer-motion';
import { C } from '../tokens';
import { TICKER_ITEMS } from '../constants';
+import { useTranslations } from 'next-intl';
const Ticker: FC = () => {
+ const t = useTranslations('Ticker');
const doubled = [...TICKER_ITEMS, ...TICKER_ITEMS];
+ const getTranslatedItem = (item: string, index: number) => {
+ const itemIndex = index % TICKER_ITEMS.length;
+ switch (itemIndex) {
+ case 0:
+ return t('item1');
+ case 1:
+ return t('item2');
+ case 2:
+ return t('item3');
+ case 3:
+ return t('item4');
+ case 4:
+ return t('item5');
+ case 5:
+ return t('item6');
+ default:
+ return item;
+ }
+ };
+
return (
{
color: i % 3 === 0 ? C.textMid : C.textMuted,
}}
>
- {item}
+ {getTranslatedItem(item, i)}
✦
diff --git a/src/widgets/paymentModal/ui/Paymebutton.tsx b/src/widgets/paymentModal/ui/Paymebutton.tsx
index 2a0d18a..f22cccc 100644
--- a/src/widgets/paymentModal/ui/Paymebutton.tsx
+++ b/src/widgets/paymentModal/ui/Paymebutton.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { PaymentStatus } from '../lib/types';
+import { useTranslations } from 'next-intl';
// ─── Payme Logo SVG ────────────────────────────────────────────────────────────
@@ -64,13 +65,14 @@ export const PaymeButton: React.FC
= ({
status,
}) => {
const isLoading = status === 'loading';
+ const t = useTranslations('Payment');
return (
= ({
{isLoading ? (
<>
- Connecting to Payme…
+ {t('connecting')}
>
) : (
<>
diff --git a/src/widgets/paymentModal/ui/Paymentmodal.tsx b/src/widgets/paymentModal/ui/Paymentmodal.tsx
index ea0f212..ac01019 100644
--- a/src/widgets/paymentModal/ui/Paymentmodal.tsx
+++ b/src/widgets/paymentModal/ui/Paymentmodal.tsx
@@ -4,6 +4,7 @@ import { PaymentModalProps } from '../lib/types';
import { getPricing } from '../lib/utils';
import { PriceSummary } from './Pricesummary';
import { PaymeButton } from './Paymebutton';
+import { useTranslations } from 'next-intl';
// ─── Close Button ──────────────────────────────────────────────────────────────
@@ -68,12 +69,14 @@ const CloseButton: React.FC<{ onClick: () => void }> = ({ onClick }) => (
// ─── Security Badge ────────────────────────────────────────────────────────────
-const SecurityBadge: React.FC = () => (
+const SecurityBadge: React.FC<{ securityText: string }> = ({
+ securityText,
+}) => (
-
Secured by Payme · SSL encrypted
+
{securityText}
);
@@ -89,6 +92,7 @@ export const PaymentModal: React.FC = ({
const dialogRef = useRef(null);
const pricing = getPricing();
const status = isLoading ? 'loading' : 'idle';
+ const t = useTranslations('Payment');
// ── Close on Escape ──────────────────────────────────────────────────────────
useEffect(() => {
@@ -157,11 +161,9 @@ export const PaymentModal: React.FC = ({
id="payment-modal-title"
className="text-lg font-semibold text-slate-900"
>
- Payment
+ {t('title')}
-
- Review your order and pay securely
-
+ {t('description')}
@@ -170,7 +172,7 @@ export const PaymentModal: React.FC = ({
{/* Order details */}
- Order Summary
+ {t('orderSummary')}
@@ -187,20 +189,20 @@ export const PaymentModal: React.FC = ({
>
- Certificate of completion included
+ {t('certificateIncluded')}
)}
{/* Payment method label */}
- Payment Method
+ {t('paymentMethod')}
{/* Security note */}
-
+
diff --git a/src/widgets/paymentModal/ui/Pricesummary.tsx b/src/widgets/paymentModal/ui/Pricesummary.tsx
index 975404d..9ac44e5 100644
--- a/src/widgets/paymentModal/ui/Pricesummary.tsx
+++ b/src/widgets/paymentModal/ui/Pricesummary.tsx
@@ -2,6 +2,7 @@
import React from 'react';
import { formatPrice } from '../lib/utils';
import { PriceSummaryProps } from '../lib/types';
+import { useTranslations } from 'next-intl';
// ─── Price Row ─────────────────────────────────────────────────────────────────
@@ -53,25 +54,26 @@ export const PriceSummary: React.FC = ({
const total = hasCertificate
? pricing.serviceFee + pricing.certificateFee
: pricing.serviceFee;
+ const t = useTranslations('Payment');
return (
{hasCertificate && (
)}
Date: Wed, 1 Apr 2026 12:12:35 +0500
Subject: [PATCH 3/3] modal location updated
---
src/features/auth/login/ui/form.tsx | 19 ++++++++-----
src/features/auth/register/ui/form.tsx | 29 ++++++++++++++++++++
src/shared/config/i18n/messages/en.json | 3 +-
src/shared/config/i18n/messages/ru.json | 3 +-
src/shared/config/i18n/messages/uz.d.json.ts | 1 +
src/shared/config/i18n/messages/uz.json | 3 +-
src/widgets/home/components/Hero.tsx | 22 +++++++++------
src/widgets/home/components/StepsSection.tsx | 15 +++++-----
src/widgets/home/index.tsx | 17 ++----------
9 files changed, 71 insertions(+), 41 deletions(-)
diff --git a/src/features/auth/login/ui/form.tsx b/src/features/auth/login/ui/form.tsx
index 67d567a..c7becde 100644
--- a/src/features/auth/login/ui/form.tsx
+++ b/src/features/auth/login/ui/form.tsx
@@ -9,8 +9,7 @@ import {
import PhonePrefix from '../../../../shared/ui/phonePrefix';
import { MotionWrapper } from '../../../../shared/ui/motion';
import { useLoginForm } from '../lib/useLoginForm';
-import { useLoginModal } from '@/shared/zustand/auth';
-import Link from 'next/link';
+import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth';
import { useTranslations } from 'next-intl';
export function LoginForm() {
@@ -20,6 +19,9 @@ export function LoginForm() {
const { phone, setPhone, submit, error, loading } = useLoginForm();
const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
+ const toggleRegisterModal = useRegisterModal(
+ (state) => state.toggleRegisterModal,
+ );
const handlePhoneChange = useCallback(
(e: React.ChangeEvent) => {
@@ -149,14 +151,17 @@ export function LoginForm() {
{/* Register hint */}
-
+
{t('registerPrompt')}
- {
+ toggleLoginModal();
+ toggleRegisterModal();
+ }}
+ className="text-stone-800 hover:cursor-pointer underline underline-offset-2 hover:text-stone-600 transition-colors"
>
{t('registerLink')}
-
+
diff --git a/src/features/auth/register/ui/form.tsx b/src/features/auth/register/ui/form.tsx
index 61a3b7d..6e6f89c 100644
--- a/src/features/auth/register/ui/form.tsx
+++ b/src/features/auth/register/ui/form.tsx
@@ -5,6 +5,7 @@ import { useRegisterForm } from '../lib/useRegisterForm';
import { formatPhone, normalizeDigits } from '@/shared/lib/formatPhone';
import PhonePrefix from '@/shared/ui/phonePrefix';
import { useTranslations } from 'next-intl';
+import { useLoginModal, useRegisterModal } from '@/shared/zustand/auth';
function Field({
id,
@@ -66,6 +67,7 @@ function Field({
export function RegisterFormUI() {
const [phone, setPhone] = React.useState('');
const t = useTranslations('Auth.Register');
+ const t_login = useTranslations('Auth.Login');
const tCommon = useTranslations('Common');
const {
registerData,
@@ -77,6 +79,10 @@ export function RegisterFormUI() {
handleSubmit,
setItem,
} = useRegisterForm();
+ const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
+ const toggleRegisterModal = useRegisterModal(
+ (state) => state.toggleRegisterModal,
+ );
const [isFocused, setIsFocused] = React.useState(false);
const handlePhoneChange = useCallback(
(e: React.ChangeEvent) => {
@@ -233,6 +239,29 @@ export function RegisterFormUI() {
)}
+
+ {/* Divider */}
+
+
+
+ {tCommon('or')}
+
+
+
+
+ {/* Register hint */}
+
+ {t('loginPrompt')}
+
{
+ toggleLoginModal();
+ toggleRegisterModal();
+ }}
+ className="text-stone-800 hover:cursor-pointer underline underline-offset-2 hover:text-stone-600 transition-colors"
+ >
+ {t_login('title')}
+
+
);
}
diff --git a/src/shared/config/i18n/messages/en.json b/src/shared/config/i18n/messages/en.json
index 86f1d54..3ed7a48 100644
--- a/src/shared/config/i18n/messages/en.json
+++ b/src/shared/config/i18n/messages/en.json
@@ -188,7 +188,8 @@
"namePlaceholder": "Ali",
"surnamePlaceholder": "Karimov",
"terms": "I agree to the Terms of Service and Privacy Policy",
- "submitButton": "Create account"
+ "submitButton": "Create account",
+ "loginPrompt": "Already have an account?"
}
},
"Payment": {
diff --git a/src/shared/config/i18n/messages/ru.json b/src/shared/config/i18n/messages/ru.json
index ee3c5ce..5073b39 100644
--- a/src/shared/config/i18n/messages/ru.json
+++ b/src/shared/config/i18n/messages/ru.json
@@ -188,7 +188,8 @@
"namePlaceholder": "Али",
"surnamePlaceholder": "Каримов",
"terms": "Я согласен с Условиями обслуживания и Политикой конфиденциальности",
- "submitButton": "Создать аккаунт"
+ "submitButton": "Создать аккаунт",
+ "loginPrompt": "Уже есть аккаунт?"
}
},
"Payment": {
diff --git a/src/shared/config/i18n/messages/uz.d.json.ts b/src/shared/config/i18n/messages/uz.d.json.ts
index 709d3f1..95a0d10 100644
--- a/src/shared/config/i18n/messages/uz.d.json.ts
+++ b/src/shared/config/i18n/messages/uz.d.json.ts
@@ -192,6 +192,7 @@ declare const messages: {
surnamePlaceholder: 'Karimov';
terms: "Men Xizmat ko'rsatish shartlari va Maxfiylik siyosatiga roziman";
submitButton: 'Hisob yaratish';
+ loginPrompt: 'Hisobingiz bormi?';
};
};
Payment: {
diff --git a/src/shared/config/i18n/messages/uz.json b/src/shared/config/i18n/messages/uz.json
index bf1078e..7b2462d 100644
--- a/src/shared/config/i18n/messages/uz.json
+++ b/src/shared/config/i18n/messages/uz.json
@@ -188,7 +188,8 @@
"namePlaceholder": "Ali",
"surnamePlaceholder": "Karimov",
"terms": "Men Xizmat ko'rsatish shartlari va Maxfiylik siyosatiga roziman",
- "submitButton": "Hisob yaratish"
+ "submitButton": "Hisob yaratish",
+ "loginPrompt": "Hisobingiz bormi?"
}
},
"Payment": {
diff --git a/src/widgets/home/components/Hero.tsx b/src/widgets/home/components/Hero.tsx
index 612be62..31ad2d1 100644
--- a/src/widgets/home/components/Hero.tsx
+++ b/src/widgets/home/components/Hero.tsx
@@ -1,5 +1,10 @@
-import type { FC } from 'react';
-import { motion, type MotionStyle, type MotionValue } from 'framer-motion';
+'use client';
+import {
+ motion,
+ useScroll,
+ useTransform,
+ type MotionStyle,
+} from 'framer-motion';
import { fadeUp, stagger } from '../animations';
import { C } from '../tokens';
import { STATS } from '../constants';
@@ -10,16 +15,15 @@ import Section from './Section';
import Stat from './Stat';
import StartButton from './StartButton';
import { useTranslations } from 'next-intl';
+import { useLoginModal } from '@/shared/zustand/auth';
-interface HeroProps {
- onStart: () => void;
- blobY: MotionValue;
-}
-
-const Hero: FC = ({ onStart, blobY }) => {
+const Hero = () => {
const isMobile = useIsMobile();
const t = useTranslations('Hero');
const tStats = useTranslations('Stats');
+ const { scrollY } = useScroll();
+ const blobY = useTransform(scrollY, [0, 600], [0, 80]);
+ const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
const getTranslatedLabel = (label: string) => {
switch (label) {
@@ -149,7 +153,7 @@ const Hero: FC = ({ onStart, blobY }) => {
flexWrap: 'wrap',
}}
>
-
+ toggleLoginModal()} />
;
- onScrollTop: () => void;
-}
-
-const StepsSection: FC = ({ stepsRef, onScrollTop }) => {
+const StepsSection = () => {
const isMobile = useIsMobile();
const t = useTranslations('StepsSection');
+ const stepsRef = useRef(null);
+ const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
return (
= ({ stepsRef, onScrollTop }) => {
{t('ctaDescription')}
-
+ toggleLoginModal()} />
diff --git a/src/widgets/home/index.tsx b/src/widgets/home/index.tsx
index 51c310a..dc2bc1a 100644
--- a/src/widgets/home/index.tsx
+++ b/src/widgets/home/index.tsx
@@ -1,27 +1,16 @@
'use client';
-import { useRef, type FC } from 'react';
-import { useScroll, useTransform } from 'framer-motion';
import Hero from './components/Hero';
import InfoSection from './components/InfoSection';
import StepsSection from './components/StepsSection';
import Ticker from './components/Ticker';
-const PlagiarismLanding: FC = () => {
- const stepsRef = useRef(null);
- const { scrollY } = useScroll();
- const blobY = useTransform(scrollY, [0, 600], [0, 80]);
-
- const scrollToSteps = () =>
- stepsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
-
- const scrollToTop = () => window.scrollTo({ top: 0, behavior: 'smooth' });
-
+const PlagiarismLanding = () => {
return (
<>
-
+
-
+
>
);
};