Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен 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,360 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { AuthLayout } from "./auth-layout"
import { Package, UserCheck, Phone, FileText, Key, ArrowLeft, Check, Zap, Truck, Building2 } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { useAuth } from '@/hooks/useAuth'
interface OrganizationData {
name?: string
fullName?: string
address?: string
isActive?: boolean
}
interface ApiKeyValidation {
sellerId?: string
sellerName?: string
isValid?: boolean
}
interface ConfirmationStepProps {
data: {
phone: string
cabinetType: 'fulfillment' | 'seller' | 'logist' | 'wholesale'
inn?: string
organizationData?: OrganizationData
wbApiKey?: string
wbApiValidation?: ApiKeyValidation
ozonApiKey?: string
ozonApiValidation?: ApiKeyValidation
}
onConfirm: () => void
onBack: () => void
}
export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepProps) {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const { registerFulfillmentOrganization, registerSellerOrganization } = useAuth()
const formatPhone = (phone: string) => {
return phone || "+7 (___) ___-__-__"
}
const formatApiKey = (key?: string) => {
if (!key) return ""
return key.substring(0, 4) + "•".repeat(key.length - 8) + key.substring(key.length - 4)
}
const handleConfirm = async () => {
setIsLoading(true)
setError(null)
try {
let result
if ((data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') && data.inn) {
result = await registerFulfillmentOrganization(
data.phone.replace(/\D/g, ''),
data.inn
)
} else if (data.cabinetType === 'seller') {
result = await registerSellerOrganization({
phone: data.phone.replace(/\D/g, ''),
wbApiKey: data.wbApiKey,
ozonApiKey: data.ozonApiKey
})
}
if (result?.success) {
onConfirm()
} else {
setError(result?.message || 'Ошибка при регистрации организации')
}
} catch (error: unknown) {
console.error('Registration error:', error)
setError('Произошла ошибка при регистрации. Попробуйте еще раз.')
} finally {
setIsLoading(false)
}
}
return (
<AuthLayout
title="Подтверждение данных"
description="Проверьте введенные данные перед завершением"
currentStep={5}
totalSteps={5}
stepName="Подтверждение"
>
<div className="space-y-4">
{/* Объединенная карточка с данными */}
<div className="glass-card p-4 space-y-3">
{/* Телефон */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Phone className="h-4 w-4 text-white" />
<span className="text-white text-sm">Телефон:</span>
</div>
<div className="flex items-center gap-2">
<span className="text-white/70 text-sm">{formatPhone(data.phone)}</span>
<Badge variant="outline" className="glass-secondary text-green-300 border-green-400/30 text-xs flex items-center gap-1">
<Check className="h-3 w-3" />
</Badge>
</div>
</div>
{/* Тип кабинета */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{data.cabinetType === 'fulfillment' ? (
<Package className="h-4 w-4 text-white" />
) : data.cabinetType === 'logist' ? (
<Truck className="h-4 w-4 text-white" />
) : data.cabinetType === 'wholesale' ? (
<Building2 className="h-4 w-4 text-white" />
) : (
<UserCheck className="h-4 w-4 text-white" />
)}
<span className="text-white text-sm">Кабинет:</span>
</div>
<div className="flex items-center gap-2">
<span className="text-white/70 text-sm">
{data.cabinetType === 'fulfillment' ? 'Фулфилмент' :
data.cabinetType === 'logist' ? 'Логистика' :
data.cabinetType === 'wholesale' ? 'Оптовик' :
'Селлер'}
</span>
<Badge
variant="secondary"
className={`glass-secondary text-xs flex items-center gap-1 ${
data.cabinetType === 'fulfillment'
? "text-blue-300 border-blue-400/30"
: data.cabinetType === 'logist'
? "text-green-300 border-green-400/30"
: data.cabinetType === 'wholesale'
? "text-orange-300 border-orange-400/30"
: "text-purple-300 border-purple-400/30"
}`}
>
{data.cabinetType === 'fulfillment' ? (
<Package className="h-3 w-3" />
) : data.cabinetType === 'logist' ? (
<Truck className="h-3 w-3" />
) : data.cabinetType === 'wholesale' ? (
<Building2 className="h-3 w-3" />
) : (
<UserCheck className="h-3 w-3" />
)}
</Badge>
</div>
</div>
{/* Данные организации */}
{(data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') && data.inn && (
<>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileText className="h-4 w-4 text-white" />
<span className="text-white text-sm">ИНН:</span>
</div>
<div className="flex items-center gap-2">
<span className="text-white/70 text-sm font-mono">{data.inn}</span>
<Badge variant="outline" className="glass-secondary text-green-300 border-green-400/30 text-xs flex items-center gap-1">
<Check className="h-3 w-3" />
</Badge>
</div>
</div>
{/* Данные организации из DaData */}
{data.organizationData && (
<>
{data.organizationData.name && (
<div className="flex items-center justify-between pl-6">
<span className="text-white/60 text-sm">Название:</span>
<span className="text-white/90 text-sm max-w-[240px] text-right truncate">
{data.organizationData.name}
</span>
</div>
)}
{data.organizationData.fullName && data.organizationData.fullName !== data.organizationData.name && (
<div className="flex items-center justify-between pl-6">
<span className="text-white/60 text-sm">Полное название:</span>
<span className="text-white/70 text-xs max-w-[200px] text-right truncate">
{data.organizationData.fullName}
</span>
</div>
)}
{data.organizationData.address && (
<div className="flex items-center justify-between pl-6">
<span className="text-white/60 text-sm">Адрес:</span>
<span className="text-white/70 text-xs max-w-[200px] text-right truncate">
{data.organizationData.address}
</span>
</div>
)}
<div className="flex items-center justify-between pl-6">
<span className="text-white/60 text-sm">Статус:</span>
<Badge
variant="outline"
className={`text-xs flex items-center gap-1 ${
data.organizationData.isActive
? "glass-secondary text-green-300 border-green-400/30"
: "glass-secondary text-red-300 border-red-400/30"
}`}
>
{data.organizationData.isActive ? (
<>
<Check className="h-3 w-3" />
Активна
</>
) : (
<>
<FileText className="h-3 w-3" />
Неактивна
</>
)}
</Badge>
</div>
</>
)}
</>
)}
{/* API ключи для селлера */}
{data.cabinetType === 'seller' && (data.wbApiKey || data.ozonApiKey) && (
<>
<div className="flex items-center gap-2 pt-1">
<Key className="h-4 w-4 text-white" />
<span className="text-white text-sm">API ключи:</span>
<Badge variant="outline" className="glass-secondary text-yellow-300 border-yellow-400/30 text-xs ml-auto flex items-center gap-1">
<Zap className="h-3 w-3" />
Активны
</Badge>
</div>
{data.wbApiKey && (
<div className="space-y-2 pl-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-white/60 text-sm">Wildberries</span>
<Badge variant="outline" className="glass-secondary text-purple-300 border-purple-400/30 text-xs">
WB
</Badge>
</div>
{data.wbApiValidation?.sellerName ? (
<span className="text-white/70 text-xs max-w-[120px] text-right truncate">
{data.wbApiValidation.sellerName}
</span>
) : (
<Badge variant="outline" className="glass-secondary text-green-300 border-green-400/30 text-xs flex items-center gap-1">
<Check className="h-3 w-3" />
Подключен
</Badge>
)}
</div>
{data.wbApiValidation && (
<>
{data.wbApiValidation.sellerName && (
<div className="flex items-center justify-between pl-4">
<span className="text-white/50 text-xs">Магазин:</span>
<span className="text-white/70 text-xs max-w-[160px] text-right truncate">
{data.wbApiValidation.sellerName}
</span>
</div>
)}
{data.wbApiValidation.sellerId && (
<div className="flex items-center justify-between pl-4">
<span className="text-white/50 text-xs">ID продавца:</span>
<span className="text-white/70 text-xs font-mono">
{data.wbApiValidation.sellerId}
</span>
</div>
)}
</>
)}
</div>
)}
{data.ozonApiKey && (
<div className="space-y-2 pl-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-white/60 text-sm">Ozon</span>
<Badge variant="outline" className="glass-secondary text-blue-300 border-blue-400/30 text-xs">
OZ
</Badge>
</div>
<Badge variant="outline" className="glass-secondary text-green-300 border-green-400/30 text-xs flex items-center gap-1">
<Check className="h-3 w-3" />
Подключен
</Badge>
</div>
{data.ozonApiValidation && (
<>
{data.ozonApiValidation.sellerName && (
<div className="flex items-center justify-between pl-4">
<span className="text-white/50 text-xs">Магазин:</span>
<span className="text-white/70 text-xs max-w-[160px] text-right truncate">
{data.ozonApiValidation.sellerName}
</span>
</div>
)}
{data.ozonApiValidation.sellerId && (
<div className="flex items-center justify-between pl-4">
<span className="text-white/50 text-xs">ID продавца:</span>
<span className="text-white/70 text-xs font-mono">
{data.ozonApiValidation.sellerId}
</span>
</div>
)}
</>
)}
</div>
)}
</>
)}
</div>
{error && (
<div className="glass-card p-3 border-red-400/30">
<p className="text-red-400 text-sm text-center">{error}</p>
</div>
)}
<div className="space-y-3">
<Button
onClick={handleConfirm}
variant="glass"
size="lg"
className="w-full h-12 flex items-center gap-2"
disabled={isLoading}
>
<Check className="h-4 w-4" />
{isLoading ? "Создание организации..." : "Подтвердить и завершить"}
</Button>
<Button
type="button"
variant="glass-secondary"
onClick={onBack}
className="w-full flex items-center gap-2"
disabled={isLoading}
>
<ArrowLeft className="h-4 w-4" />
Назад
</Button>
</div>
</div>
</AuthLayout>
)
}