Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.

This commit is contained in:
Bivekich
2025-07-16 18:00:41 +03:00
parent d260749bc9
commit 823ef9a28c
69 changed files with 15539 additions and 210 deletions

View File

@ -0,0 +1,219 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { GlassInput } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { AuthLayout } from "./auth-layout"
import { FileText, ArrowLeft, Building, Check, AlertTriangle } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { useMutation } from '@apollo/client'
import { VERIFY_INN } from '@/graphql/mutations'
interface InnStepProps {
onNext: (inn: string, organizationData?: OrganizationData) => void
onBack: () => void
}
interface OrganizationData {
name: string
address: string
isActive: boolean
}
export function InnStep({ onNext, onBack }: InnStepProps) {
const [inn, setInn] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [organizationData, setOrganizationData] = useState<OrganizationData | null>(null)
const [verifyInn] = useMutation(VERIFY_INN)
const formatInn = (value: string) => {
const numbers = value.replace(/\D/g, '')
return numbers.slice(0, 12) // Максимум 12 цифр для ИНН
}
const handleInnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const formatted = formatInn(e.target.value)
setInn(formatted)
setError(null)
setOrganizationData(null)
}
const isValidInn = (inn: string) => {
return inn.length === 10 || inn.length === 12
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!isValidInn(inn)) {
setError('ИНН должен содержать 10 или 12 цифр')
return
}
setIsLoading(true)
setError(null)
setOrganizationData(null)
try {
const { data } = await verifyInn({
variables: { inn }
})
if (data.verifyInn.success && data.verifyInn.organization) {
const org = data.verifyInn.organization
const newOrgData = {
name: org.name,
address: org.address,
isActive: org.isActive
}
setOrganizationData(newOrgData)
if (org.isActive) {
// Автоматически переходим дальше для активных организаций
setTimeout(() => {
onNext(inn, newOrgData)
}, 1500)
}
} else {
setError('Организация с таким ИНН не найдена')
}
} catch (error: unknown) {
console.error('INN verification error:', error)
setError('Ошибка проверки ИНН. Попробуйте позже.')
} finally {
setIsLoading(false)
}
}
const handleContinueInactive = () => {
if (organizationData && !organizationData.isActive) {
onNext(inn, organizationData)
}
}
return (
<AuthLayout
title="ИНН организации"
description="Укажите ИНН для проверки организации"
currentStep={4}
totalSteps={5}
stepName="ИНН"
>
<div className="space-y-4">
<Alert className="glass-secondary border-white/20">
<Building className="h-4 w-4 text-white" />
<AlertDescription className="text-white/80">
Фулфилмент кабинет - склады и логистика
</AlertDescription>
</Alert>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label htmlFor="inn" className="text-white text-sm font-medium flex items-center gap-2">
<FileText className="h-4 w-4" />
ИНН организации
</Label>
{organizationData && (
<Badge
variant="outline"
className={`glass-secondary flex items-center gap-1 ${
organizationData.isActive
? 'text-green-300 border-green-400/30'
: 'text-yellow-300 border-yellow-400/30'
}`}
>
{organizationData.isActive ? (
<>
<Check className="h-3 w-3" />
Активна
</>
) : (
<>
<AlertTriangle className="h-3 w-3" />
Неактивна
</>
)}
</Badge>
)}
</div>
<GlassInput
id="inn"
type="text"
inputMode="numeric"
placeholder="1234567890"
value={inn}
onChange={handleInnChange}
className={`h-12 text-center text-lg font-mono ${error ? 'border-red-400/50' : ''}`}
maxLength={12}
/>
{error && (
<p className="text-red-400 text-xs text-center">{error}</p>
)}
</div>
{organizationData && (
<div className="glass-card p-4 space-y-2">
<h4 className="text-white font-medium text-sm">{organizationData.name}</h4>
<p className="text-white/70 text-xs">{organizationData.address}</p>
{organizationData.isActive ? (
<div className="flex items-center gap-2 pt-2">
<Check className="h-4 w-4 text-green-300" />
<span className="text-green-300 text-sm">Организация активна</span>
</div>
) : (
<div className="flex items-center gap-2 pt-2">
<AlertTriangle className="h-4 w-4 text-yellow-300" />
<span className="text-yellow-300 text-sm">Организация неактивна</span>
</div>
)}
</div>
)}
<div className="space-y-3">
{!organizationData && (
<Button
type="submit"
variant="glass"
size="lg"
className="w-full h-12"
disabled={!isValidInn(inn) || isLoading}
>
{isLoading ? "Проверка ИНН..." : "Проверить ИНН"}
</Button>
)}
{organizationData && !organizationData.isActive && (
<Button
type="button"
onClick={handleContinueInactive}
variant="glass"
size="lg"
className="w-full h-12"
>
Продолжить с неактивной организацией
</Button>
)}
<Button
type="button"
variant="glass-secondary"
onClick={onBack}
className="w-full flex items-center gap-2"
>
<ArrowLeft className="h-4 w-4" />
Назад
</Button>
</div>
</form>
</div>
</AuthLayout>
)
}