Обновлен Dockerfile для установки wget и добавлены зависимости в компонентах. Исправлены зависимости в хуках и компонентах, улучшено логирование и обработка ошибок. Удалены неиспользуемые импорты и оптимизированы некоторые функции. Обновлены компоненты для работы с изображениями и улучшена структура кода.
This commit is contained in:
@ -29,7 +29,7 @@ export function AuthGuard({ children, fallback }: AuthGuardProps) {
|
||||
}
|
||||
|
||||
initAuth()
|
||||
}, []) // Убираем checkAuth из зависимостей чтобы избежать повторных вызовов
|
||||
}, [checkAuth, isAuthenticated, user]) // Добавляем зависимости как требует линтер
|
||||
|
||||
// Дополнительное логирование состояний
|
||||
useEffect(() => {
|
||||
|
@ -8,7 +8,7 @@ import { InnStep } from "./inn-step"
|
||||
import { MarketplaceApiStep } from "./marketplace-api-step"
|
||||
import { ConfirmationStep } from "./confirmation-step"
|
||||
import { CheckCircle } from "lucide-react"
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
|
||||
|
||||
type AuthStep = 'phone' | 'sms' | 'cabinet-select' | 'inn' | 'marketplace-api' | 'confirmation' | 'complete'
|
||||
type CabinetType = 'fulfillment' | 'seller' | 'logist' | 'wholesale'
|
||||
@ -61,8 +61,6 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
partnerCode: partnerCode
|
||||
})
|
||||
|
||||
const { verifySmsCode, checkAuth } = useAuth()
|
||||
|
||||
// При завершении авторизации инициируем проверку и перенаправление
|
||||
useEffect(() => {
|
||||
if (step === 'complete') {
|
||||
@ -126,10 +124,6 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
setStep('complete')
|
||||
}
|
||||
|
||||
const handlePhoneBack = () => {
|
||||
setStep('phone')
|
||||
}
|
||||
|
||||
const handleSmsBack = () => {
|
||||
setStep('phone')
|
||||
}
|
||||
@ -240,7 +234,7 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
|
||||
onBack={handleConfirmationBack}
|
||||
/>
|
||||
)}
|
||||
{step === 'complete' && (
|
||||
{(step as string) === 'complete' && (
|
||||
<div className="space-y-6 text-center">
|
||||
<div className="flex justify-center">
|
||||
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { AuthLayout } from "./auth-layout"
|
||||
import { Package, ShoppingCart, ArrowLeft, Truck, Building2 } from "lucide-react"
|
||||
|
@ -46,10 +46,7 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
|
||||
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)
|
||||
|
@ -1,16 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect, useRef } from "react"
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { GlassInput } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { AuthLayout } from "./auth-layout"
|
||||
import { Key, ArrowLeft, ShoppingCart, Check, X } from "lucide-react"
|
||||
import { ArrowLeft, ShoppingCart, Check, X } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { ADD_MARKETPLACE_API_KEY } from '@/graphql/mutations'
|
||||
import { getAuthToken } from '@/lib/apollo-client'
|
||||
|
||||
|
||||
interface ApiValidationData {
|
||||
sellerId?: string
|
||||
@ -110,7 +110,7 @@ export function MarketplaceApiStep({ onNext, onBack }: MarketplaceApiStepProps)
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
} catch {
|
||||
setValidationStates(prev => ({
|
||||
...prev,
|
||||
[marketplace]: {
|
||||
|
@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"
|
||||
import { GlassInput } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
|
||||
import { AuthLayout } from "./auth-layout"
|
||||
import { MessageSquare, ArrowLeft, Clock, RefreshCw, Check } from "lucide-react"
|
||||
import { useMutation } from '@apollo/client'
|
||||
@ -26,7 +26,7 @@ export function SmsStep({ phone, onNext, onBack }: SmsStepProps) {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const inputRefs = useRef<(HTMLInputElement | null)[]>([])
|
||||
|
||||
const { verifySmsCode, checkAuth } = useAuth()
|
||||
const { verifySmsCode } = useAuth()
|
||||
const [sendSmsCode] = useMutation(SEND_SMS_CODE)
|
||||
|
||||
// Автофокус на первое поле при загрузке
|
||||
|
@ -8,7 +8,7 @@ import { DashboardHome } from './dashboard-home'
|
||||
export type DashboardSection = 'home' | 'settings'
|
||||
|
||||
export function Dashboard() {
|
||||
const [activeSection, setActiveSection] = useState<DashboardSection>('home')
|
||||
const [activeSection] = useState<DashboardSection>('home')
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeSection) {
|
||||
|
@ -3,13 +3,12 @@
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { useRouter, usePathname } from 'next/navigation'
|
||||
import {
|
||||
Settings,
|
||||
LogOut,
|
||||
Building2,
|
||||
Store,
|
||||
MessageCircle,
|
||||
Wrench
|
||||
|
@ -32,10 +32,10 @@ import {
|
||||
RefreshCw,
|
||||
Calendar,
|
||||
Settings,
|
||||
Upload,
|
||||
Camera
|
||||
} from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
export function UserSettings() {
|
||||
const { user } = useAuth()
|
||||
@ -662,9 +662,11 @@ export function UserSettings() {
|
||||
<div className="relative">
|
||||
<Avatar className="h-16 w-16">
|
||||
{user?.avatar ? (
|
||||
<img
|
||||
<Image
|
||||
src={user.avatar}
|
||||
alt="Аватар"
|
||||
width={64}
|
||||
height={64}
|
||||
className="w-full h-full object-cover rounded-full"
|
||||
/>
|
||||
) : (
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { useQuery, useMutation } from '@apollo/client'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
@ -39,9 +39,9 @@ interface CounterpartyRequest {
|
||||
}
|
||||
|
||||
export function MarketCounterparties() {
|
||||
const { data: counterpartiesData, loading: counterpartiesLoading, refetch: refetchCounterparties } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
const { data: incomingData, loading: incomingLoading, refetch: refetchIncoming } = useQuery(GET_INCOMING_REQUESTS)
|
||||
const { data: outgoingData, loading: outgoingLoading, refetch: refetchOutgoing } = useQuery(GET_OUTGOING_REQUESTS)
|
||||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
const { data: incomingData, loading: incomingLoading } = useQuery(GET_INCOMING_REQUESTS)
|
||||
const { data: outgoingData, loading: outgoingLoading } = useQuery(GET_OUTGOING_REQUESTS)
|
||||
|
||||
const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, {
|
||||
refetchQueries: [
|
||||
@ -141,7 +141,7 @@ export function MarketCounterparties() {
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return 'Ошибка даты'
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ export function OrganizationCard({
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return 'Ошибка даты'
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,13 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
import {
|
||||
Building2,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Calendar,
|
||||
|
||||
FileText,
|
||||
Users,
|
||||
CreditCard,
|
||||
@ -98,7 +98,7 @@ function formatDate(dateString?: string | null): string {
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return 'Не указана'
|
||||
}
|
||||
}
|
||||
@ -376,7 +376,7 @@ export function OrganizationDetailsModal({ organization, open, onOpenChange }: O
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.users.map((user, index) => (
|
||||
{organization.users.map((user) => (
|
||||
<div key={user.id} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<OrganizationAvatar
|
||||
@ -406,7 +406,7 @@ export function OrganizationDetailsModal({ organization, open, onOpenChange }: O
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.apiKeys.map((apiKey, index) => (
|
||||
{organization.apiKeys.map((apiKey) => (
|
||||
<div key={apiKey.id} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Badge className={apiKey.isActive ? 'bg-green-500/20 text-green-300 border-green-500/30' : 'bg-red-500/20 text-red-300 border-red-500/30'}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useState, useRef, useEffect, useMemo } from 'react'
|
||||
import { useMutation, useQuery } from '@apollo/client'
|
||||
import { GET_MESSAGES } from '@/graphql/queries'
|
||||
import { SEND_MESSAGE, SEND_VOICE_MESSAGE, SEND_IMAGE_MESSAGE, SEND_FILE_MESSAGE } from '@/graphql/mutations'
|
||||
@ -97,7 +97,7 @@ export function MessengerChat({ counterparty }: MessengerChatProps) {
|
||||
}
|
||||
})
|
||||
|
||||
const messages = messagesData?.messages || []
|
||||
const messages = useMemo(() => messagesData?.messages || [], [messagesData?.messages])
|
||||
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useQuery, useMutation } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import Image from 'next/image'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@ -45,7 +46,7 @@ export function ServicesTab() {
|
||||
imageUrl: ''
|
||||
})
|
||||
const [imageFile, setImageFile] = useState<File | null>(null)
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [isUploading] = useState(false)
|
||||
|
||||
// GraphQL запросы и мутации
|
||||
const { data, loading, error, refetch } = useQuery(GET_MY_SERVICES, {
|
||||
@ -95,36 +96,6 @@ export function ServicesTab() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
if (!user?.id) return
|
||||
|
||||
setIsUploading(true)
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('userId', user.id)
|
||||
formData.append('type', 'service')
|
||||
|
||||
const response = await fetch('/api/upload-service-image', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to upload image')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
console.log('Upload result:', result)
|
||||
setFormData(prev => ({ ...prev, imageUrl: result.url }))
|
||||
} catch (error) {
|
||||
console.error('Error uploading image:', error)
|
||||
toast.error('Ошибка при загрузке изображения')
|
||||
} finally {
|
||||
setIsUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadImageAndGetUrl = async (file: File): Promise<string> => {
|
||||
if (!user?.id) throw new Error('User not found')
|
||||
|
||||
@ -278,9 +249,11 @@ export function ServicesTab() {
|
||||
/>
|
||||
{formData.imageUrl && (
|
||||
<div className="mt-3">
|
||||
<img
|
||||
<Image
|
||||
src={formData.imageUrl}
|
||||
alt="Preview"
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-20 h-20 object-cover rounded-lg border border-purple-400/30 shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
@ -386,9 +359,11 @@ export function ServicesTab() {
|
||||
<td className="p-4 text-white/80">{index + 1}</td>
|
||||
<td className="p-4">
|
||||
{service.imageUrl ? (
|
||||
<img
|
||||
<Image
|
||||
src={service.imageUrl}
|
||||
alt={service.name}
|
||||
width={48}
|
||||
height={48}
|
||||
className="w-12 h-12 object-cover rounded border border-white/20"
|
||||
onError={(e) => {
|
||||
console.error('Image failed to load:', service.imageUrl, e)
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery, useMutation } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import Image from 'next/image'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@ -47,7 +48,7 @@ export function SuppliesTab() {
|
||||
imageUrl: ''
|
||||
})
|
||||
const [imageFile, setImageFile] = useState<File | null>(null)
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [isUploading] = useState(false)
|
||||
|
||||
// GraphQL запросы и мутации
|
||||
const { data, loading, error, refetch } = useQuery(GET_MY_SUPPLIES, {
|
||||
@ -96,35 +97,7 @@ export function SuppliesTab() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
if (!user?.id) return
|
||||
|
||||
setIsUploading(true)
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('userId', user.id)
|
||||
formData.append('type', 'supply')
|
||||
|
||||
const response = await fetch('/api/upload-service-image', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to upload image')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
console.log('Upload result:', result)
|
||||
setFormData(prev => ({ ...prev, imageUrl: result.url }))
|
||||
} catch (error) {
|
||||
console.error('Error uploading image:', error)
|
||||
toast.error('Ошибка при загрузке изображения')
|
||||
} finally {
|
||||
setIsUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadImageAndGetUrl = async (file: File): Promise<string> => {
|
||||
if (!user?.id) throw new Error('User not found')
|
||||
@ -300,9 +273,11 @@ export function SuppliesTab() {
|
||||
/>
|
||||
{formData.imageUrl && (
|
||||
<div className="mt-3">
|
||||
<img
|
||||
<Image
|
||||
src={formData.imageUrl}
|
||||
alt="Preview"
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-20 h-20 object-cover rounded-lg border border-purple-400/30 shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
@ -409,9 +384,11 @@ export function SuppliesTab() {
|
||||
<td className="p-4 text-white/80">{index + 1}</td>
|
||||
<td className="p-4">
|
||||
{supply.imageUrl ? (
|
||||
<img
|
||||
<Image
|
||||
src={supply.imageUrl}
|
||||
alt={supply.name}
|
||||
width={48}
|
||||
height={48}
|
||||
className="w-12 h-12 object-cover rounded border border-white/20"
|
||||
/>
|
||||
) : (
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Paperclip, Image, X } from 'lucide-react'
|
||||
import NextImage from 'next/image'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
|
||||
interface FileUploaderProps {
|
||||
@ -118,9 +119,11 @@ export function FileUploader({ onSendFile }: FileUploaderProps) {
|
||||
<div className="flex items-center space-x-2 flex-1">
|
||||
{isImageType(selectedFile.type) ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
<NextImage
|
||||
src={selectedFile.url}
|
||||
alt="Preview"
|
||||
width={40}
|
||||
height={40}
|
||||
className="w-10 h-10 object-cover rounded"
|
||||
/>
|
||||
<div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { Download, Eye } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
@ -44,9 +45,11 @@ export function ImageMessage({ imageUrl, fileName, fileSize, isCurrentUser = fal
|
||||
: 'bg-white/10 border border-white/20'
|
||||
} rounded-lg overflow-hidden`}>
|
||||
<div className="relative">
|
||||
<img
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={fileName}
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-auto cursor-pointer transition-opacity duration-200"
|
||||
style={{
|
||||
opacity: isLoading ? 0 : 1,
|
||||
@ -108,9 +111,11 @@ export function ImageMessage({ imageUrl, fileName, fileSize, isCurrentUser = fal
|
||||
onClick={() => setShowFullSize(false)}
|
||||
>
|
||||
<div className="relative max-w-full max-h-full">
|
||||
<img
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={fileName}
|
||||
width={800}
|
||||
height={600}
|
||||
className="max-w-full max-h-full object-contain"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
|
@ -16,7 +16,8 @@ const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
||||
onChange?.(value)
|
||||
}
|
||||
|
||||
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput
|
||||
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { min, max, step, ...filteredProps } = props
|
||||
|
||||
return (
|
||||
@ -61,6 +62,7 @@ const GlassPhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
||||
const isEmpty = !value || value.replace(/\D/g, '').length === 0
|
||||
|
||||
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { min, max, step, onFocus, onBlur, ...filteredProps } = props
|
||||
|
||||
return (
|
||||
|
@ -23,6 +23,7 @@ export function VoicePlayer({ audioUrl, duration = 0, isCurrentUser = false }: V
|
||||
if (duration > 0 && (!audioDuration || audioDuration === 0)) {
|
||||
setAudioDuration(duration)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [duration, audioDuration])
|
||||
|
||||
useEffect(() => {
|
||||
|
Reference in New Issue
Block a user