Обновлен Dockerfile для установки wget и добавлены зависимости в компонентах. Исправлены зависимости в хуках и компонентах, улучшено логирование и обработка ошибок. Удалены неиспользуемые импорты и оптимизированы некоторые функции. Обновлены компоненты для работы с изображениями и улучшена структура кода.

This commit is contained in:
Bivekich
2025-07-17 11:18:32 +03:00
parent 99e91287f3
commit 83ed577a44
24 changed files with 80 additions and 119 deletions

View File

@ -3,8 +3,6 @@ FROM node:18-alpine AS base
# Устанавливаем зависимости только когда нужно # Устанавливаем зависимости только когда нужно
FROM base AS deps FROM base AS deps
# Проверяем https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine чтобы понять зачем нужен libc6-compat
RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
# Устанавливаем зависимости на основе предпочтительного менеджера пакетов # Устанавливаем зависимости на основе предпочтительного менеджера пакетов
@ -37,6 +35,9 @@ ENV NODE_ENV production
# Отключаем телеметрию next.js во время runtime # Отключаем телеметрию next.js во время runtime
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED 1
# Устанавливаем wget для healthcheck
RUN apk add --no-cache wget
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs

View File

@ -29,7 +29,7 @@ export function AuthGuard({ children, fallback }: AuthGuardProps) {
} }
initAuth() initAuth()
}, []) // Убираем checkAuth из зависимостей чтобы избежать повторных вызовов }, [checkAuth, isAuthenticated, user]) // Добавляем зависимости как требует линтер
// Дополнительное логирование состояний // Дополнительное логирование состояний
useEffect(() => { useEffect(() => {

View File

@ -8,7 +8,7 @@ import { InnStep } from "./inn-step"
import { MarketplaceApiStep } from "./marketplace-api-step" import { MarketplaceApiStep } from "./marketplace-api-step"
import { ConfirmationStep } from "./confirmation-step" import { ConfirmationStep } from "./confirmation-step"
import { CheckCircle } from "lucide-react" import { CheckCircle } from "lucide-react"
import { useAuth } from '@/hooks/useAuth'
type AuthStep = 'phone' | 'sms' | 'cabinet-select' | 'inn' | 'marketplace-api' | 'confirmation' | 'complete' type AuthStep = 'phone' | 'sms' | 'cabinet-select' | 'inn' | 'marketplace-api' | 'confirmation' | 'complete'
type CabinetType = 'fulfillment' | 'seller' | 'logist' | 'wholesale' type CabinetType = 'fulfillment' | 'seller' | 'logist' | 'wholesale'
@ -61,8 +61,6 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
partnerCode: partnerCode partnerCode: partnerCode
}) })
const { verifySmsCode, checkAuth } = useAuth()
// При завершении авторизации инициируем проверку и перенаправление // При завершении авторизации инициируем проверку и перенаправление
useEffect(() => { useEffect(() => {
if (step === 'complete') { if (step === 'complete') {
@ -126,10 +124,6 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
setStep('complete') setStep('complete')
} }
const handlePhoneBack = () => {
setStep('phone')
}
const handleSmsBack = () => { const handleSmsBack = () => {
setStep('phone') setStep('phone')
} }
@ -240,7 +234,7 @@ export function AuthFlow({ partnerCode }: AuthFlowProps = {}) {
onBack={handleConfirmationBack} onBack={handleConfirmationBack}
/> />
)} )}
{step === 'complete' && ( {(step as string) === 'complete' && (
<div className="space-y-6 text-center"> <div className="space-y-6 text-center">
<div className="flex justify-center"> <div className="flex justify-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center"> <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">

View File

@ -1,7 +1,7 @@
"use client" "use client"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { AuthLayout } from "./auth-layout" import { AuthLayout } from "./auth-layout"
import { Package, ShoppingCart, ArrowLeft, Truck, Building2 } from "lucide-react" import { Package, ShoppingCart, ArrowLeft, Truck, Building2 } from "lucide-react"

View File

@ -46,10 +46,7 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
return phone || "+7 (___) ___-__-__" 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 () => { const handleConfirm = async () => {
setIsLoading(true) setIsLoading(true)

View File

@ -1,16 +1,16 @@
"use client" "use client"
import { useState, useEffect, useRef } from "react" import { useState } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { GlassInput } from "@/components/ui/input" import { GlassInput } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Checkbox } from "@/components/ui/checkbox" import { Checkbox } from "@/components/ui/checkbox"
import { AuthLayout } from "./auth-layout" 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 { Badge } from "@/components/ui/badge"
import { useMutation } from '@apollo/client' import { useMutation } from '@apollo/client'
import { ADD_MARKETPLACE_API_KEY } from '@/graphql/mutations' import { ADD_MARKETPLACE_API_KEY } from '@/graphql/mutations'
import { getAuthToken } from '@/lib/apollo-client'
interface ApiValidationData { interface ApiValidationData {
sellerId?: string sellerId?: string
@ -110,7 +110,7 @@ export function MarketplaceApiStep({ onNext, onBack }: MarketplaceApiStepProps)
}) })
} }
} }
} catch (error: unknown) { } catch {
setValidationStates(prev => ({ setValidationStates(prev => ({
...prev, ...prev,
[marketplace]: { [marketplace]: {

View File

@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"
import { GlassInput } from "@/components/ui/input" import { GlassInput } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { AuthLayout } from "./auth-layout" import { AuthLayout } from "./auth-layout"
import { MessageSquare, ArrowLeft, Clock, RefreshCw, Check } from "lucide-react" import { MessageSquare, ArrowLeft, Clock, RefreshCw, Check } from "lucide-react"
import { useMutation } from '@apollo/client' import { useMutation } from '@apollo/client'
@ -26,7 +26,7 @@ export function SmsStep({ phone, onNext, onBack }: SmsStepProps) {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const inputRefs = useRef<(HTMLInputElement | null)[]>([]) const inputRefs = useRef<(HTMLInputElement | null)[]>([])
const { verifySmsCode, checkAuth } = useAuth() const { verifySmsCode } = useAuth()
const [sendSmsCode] = useMutation(SEND_SMS_CODE) const [sendSmsCode] = useMutation(SEND_SMS_CODE)
// Автофокус на первое поле при загрузке // Автофокус на первое поле при загрузке

View File

@ -8,7 +8,7 @@ import { DashboardHome } from './dashboard-home'
export type DashboardSection = 'home' | 'settings' export type DashboardSection = 'home' | 'settings'
export function Dashboard() { export function Dashboard() {
const [activeSection, setActiveSection] = useState<DashboardSection>('home') const [activeSection] = useState<DashboardSection>('home')
const renderContent = () => { const renderContent = () => {
switch (activeSection) { switch (activeSection) {

View File

@ -3,13 +3,12 @@
import { useAuth } from '@/hooks/useAuth' import { useAuth } from '@/hooks/useAuth'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card' import { Card } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar' import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
import { useRouter, usePathname } from 'next/navigation' import { useRouter, usePathname } from 'next/navigation'
import { import {
Settings, Settings,
LogOut, LogOut,
Building2,
Store, Store,
MessageCircle, MessageCircle,
Wrench Wrench

View File

@ -32,10 +32,10 @@ import {
RefreshCw, RefreshCw,
Calendar, Calendar,
Settings, Settings,
Upload,
Camera Camera
} from 'lucide-react' } from 'lucide-react'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import Image from 'next/image'
export function UserSettings() { export function UserSettings() {
const { user } = useAuth() const { user } = useAuth()
@ -662,9 +662,11 @@ export function UserSettings() {
<div className="relative"> <div className="relative">
<Avatar className="h-16 w-16"> <Avatar className="h-16 w-16">
{user?.avatar ? ( {user?.avatar ? (
<img <Image
src={user.avatar} src={user.avatar}
alt="Аватар" alt="Аватар"
width={64}
height={64}
className="w-full h-full object-cover rounded-full" className="w-full h-full object-cover rounded-full"
/> />
) : ( ) : (

View File

@ -1,6 +1,6 @@
"use client" "use client"
import { useState } from 'react' import React from 'react'
import { useQuery, useMutation } from '@apollo/client' import { useQuery, useMutation } from '@apollo/client'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
@ -39,9 +39,9 @@ interface CounterpartyRequest {
} }
export function MarketCounterparties() { export function MarketCounterparties() {
const { data: counterpartiesData, loading: counterpartiesLoading, refetch: refetchCounterparties } = useQuery(GET_MY_COUNTERPARTIES) const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
const { data: incomingData, loading: incomingLoading, refetch: refetchIncoming } = useQuery(GET_INCOMING_REQUESTS) const { data: incomingData, loading: incomingLoading } = useQuery(GET_INCOMING_REQUESTS)
const { data: outgoingData, loading: outgoingLoading, refetch: refetchOutgoing } = useQuery(GET_OUTGOING_REQUESTS) const { data: outgoingData, loading: outgoingLoading } = useQuery(GET_OUTGOING_REQUESTS)
const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, { const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, {
refetchQueries: [ refetchQueries: [
@ -141,7 +141,7 @@ export function MarketCounterparties() {
month: 'long', month: 'long',
day: 'numeric' day: 'numeric'
}) })
} catch (error) { } catch {
return 'Ошибка даты' return 'Ошибка даты'
} }
} }

View File

@ -80,7 +80,7 @@ export function OrganizationCard({
month: 'long', month: 'long',
day: 'numeric' day: 'numeric'
}) })
} catch (error) { } catch {
return 'Ошибка даты' return 'Ошибка даты'
} }
} }

View File

@ -3,13 +3,13 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Card } from '@/components/ui/card' import { Card } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator'
import { import {
Building2, Building2,
Phone, Phone,
Mail, Mail,
MapPin, MapPin,
Calendar,
FileText, FileText,
Users, Users,
CreditCard, CreditCard,
@ -98,7 +98,7 @@ function formatDate(dateString?: string | null): string {
month: 'long', month: 'long',
day: 'numeric' day: 'numeric'
}) })
} catch (error) { } catch {
return 'Не указана' return 'Не указана'
} }
} }
@ -376,7 +376,7 @@ export function OrganizationDetailsModal({ organization, open, onOpenChange }: O
</h3> </h3>
<div className="space-y-3"> <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 key={user.id} className="flex items-center justify-between">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<OrganizationAvatar <OrganizationAvatar
@ -406,7 +406,7 @@ export function OrganizationDetailsModal({ organization, open, onOpenChange }: O
</h3> </h3>
<div className="space-y-3"> <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 key={apiKey.id} className="flex items-center justify-between">
<div className="flex items-center space-x-3"> <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'}> <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'}>

View File

@ -1,6 +1,6 @@
"use client" "use client"
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect, useMemo } from 'react'
import { useMutation, useQuery } from '@apollo/client' import { useMutation, useQuery } from '@apollo/client'
import { GET_MESSAGES } from '@/graphql/queries' import { GET_MESSAGES } from '@/graphql/queries'
import { SEND_MESSAGE, SEND_VOICE_MESSAGE, SEND_IMAGE_MESSAGE, SEND_FILE_MESSAGE } from '@/graphql/mutations' 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])

View File

@ -1,8 +1,9 @@
"use client" "use client"
import { useState, useEffect } from 'react' import { useState } from 'react'
import { useQuery, useMutation } from '@apollo/client' import { useQuery, useMutation } from '@apollo/client'
import { Card } from '@/components/ui/card' import { Card } from '@/components/ui/card'
import Image from 'next/image'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
@ -45,7 +46,7 @@ export function ServicesTab() {
imageUrl: '' imageUrl: ''
}) })
const [imageFile, setImageFile] = useState<File | null>(null) const [imageFile, setImageFile] = useState<File | null>(null)
const [isUploading, setIsUploading] = useState(false) const [isUploading] = useState(false)
// GraphQL запросы и мутации // GraphQL запросы и мутации
const { data, loading, error, refetch } = useQuery(GET_MY_SERVICES, { 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> => { const uploadImageAndGetUrl = async (file: File): Promise<string> => {
if (!user?.id) throw new Error('User not found') if (!user?.id) throw new Error('User not found')
@ -278,9 +249,11 @@ export function ServicesTab() {
/> />
{formData.imageUrl && ( {formData.imageUrl && (
<div className="mt-3"> <div className="mt-3">
<img <Image
src={formData.imageUrl} src={formData.imageUrl}
alt="Preview" alt="Preview"
width={80}
height={80}
className="w-20 h-20 object-cover rounded-lg border border-purple-400/30 shadow-lg" className="w-20 h-20 object-cover rounded-lg border border-purple-400/30 shadow-lg"
/> />
</div> </div>
@ -386,9 +359,11 @@ export function ServicesTab() {
<td className="p-4 text-white/80">{index + 1}</td> <td className="p-4 text-white/80">{index + 1}</td>
<td className="p-4"> <td className="p-4">
{service.imageUrl ? ( {service.imageUrl ? (
<img <Image
src={service.imageUrl} src={service.imageUrl}
alt={service.name} alt={service.name}
width={48}
height={48}
className="w-12 h-12 object-cover rounded border border-white/20" className="w-12 h-12 object-cover rounded border border-white/20"
onError={(e) => { onError={(e) => {
console.error('Image failed to load:', service.imageUrl, e) console.error('Image failed to load:', service.imageUrl, e)

View File

@ -3,6 +3,7 @@
import { useState } from 'react' import { useState } from 'react'
import { useQuery, useMutation } from '@apollo/client' import { useQuery, useMutation } from '@apollo/client'
import { Card } from '@/components/ui/card' import { Card } from '@/components/ui/card'
import Image from 'next/image'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
@ -47,7 +48,7 @@ export function SuppliesTab() {
imageUrl: '' imageUrl: ''
}) })
const [imageFile, setImageFile] = useState<File | null>(null) const [imageFile, setImageFile] = useState<File | null>(null)
const [isUploading, setIsUploading] = useState(false) const [isUploading] = useState(false)
// GraphQL запросы и мутации // GraphQL запросы и мутации
const { data, loading, error, refetch } = useQuery(GET_MY_SUPPLIES, { 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> => { const uploadImageAndGetUrl = async (file: File): Promise<string> => {
if (!user?.id) throw new Error('User not found') if (!user?.id) throw new Error('User not found')
@ -300,9 +273,11 @@ export function SuppliesTab() {
/> />
{formData.imageUrl && ( {formData.imageUrl && (
<div className="mt-3"> <div className="mt-3">
<img <Image
src={formData.imageUrl} src={formData.imageUrl}
alt="Preview" alt="Preview"
width={80}
height={80}
className="w-20 h-20 object-cover rounded-lg border border-purple-400/30 shadow-lg" className="w-20 h-20 object-cover rounded-lg border border-purple-400/30 shadow-lg"
/> />
</div> </div>
@ -409,9 +384,11 @@ export function SuppliesTab() {
<td className="p-4 text-white/80">{index + 1}</td> <td className="p-4 text-white/80">{index + 1}</td>
<td className="p-4"> <td className="p-4">
{supply.imageUrl ? ( {supply.imageUrl ? (
<img <Image
src={supply.imageUrl} src={supply.imageUrl}
alt={supply.name} alt={supply.name}
width={48}
height={48}
className="w-12 h-12 object-cover rounded border border-white/20" className="w-12 h-12 object-cover rounded border border-white/20"
/> />
) : ( ) : (

View File

@ -3,6 +3,7 @@
import { useState, useRef } from 'react' import { useState, useRef } from 'react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Paperclip, Image, X } from 'lucide-react' import { Paperclip, Image, X } from 'lucide-react'
import NextImage from 'next/image'
import { useAuth } from '@/hooks/useAuth' import { useAuth } from '@/hooks/useAuth'
interface FileUploaderProps { interface FileUploaderProps {
@ -118,9 +119,11 @@ export function FileUploader({ onSendFile }: FileUploaderProps) {
<div className="flex items-center space-x-2 flex-1"> <div className="flex items-center space-x-2 flex-1">
{isImageType(selectedFile.type) ? ( {isImageType(selectedFile.type) ? (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<img <NextImage
src={selectedFile.url} src={selectedFile.url}
alt="Preview" alt="Preview"
width={40}
height={40}
className="w-10 h-10 object-cover rounded" className="w-10 h-10 object-cover rounded"
/> />
<div> <div>

View File

@ -1,6 +1,7 @@
"use client" "use client"
import { useState } from 'react' import { useState } from 'react'
import Image from 'next/image'
import { Download, Eye } from 'lucide-react' import { Download, Eye } from 'lucide-react'
import { Button } from '@/components/ui/button' 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' : 'bg-white/10 border border-white/20'
} rounded-lg overflow-hidden`}> } rounded-lg overflow-hidden`}>
<div className="relative"> <div className="relative">
<img <Image
src={imageUrl} src={imageUrl}
alt={fileName} alt={fileName}
width={300}
height={300}
className="w-full h-auto cursor-pointer transition-opacity duration-200" className="w-full h-auto cursor-pointer transition-opacity duration-200"
style={{ style={{
opacity: isLoading ? 0 : 1, opacity: isLoading ? 0 : 1,
@ -108,9 +111,11 @@ export function ImageMessage({ imageUrl, fileName, fileSize, isCurrentUser = fal
onClick={() => setShowFullSize(false)} onClick={() => setShowFullSize(false)}
> >
<div className="relative max-w-full max-h-full"> <div className="relative max-w-full max-h-full">
<img <Image
src={imageUrl} src={imageUrl}
alt={fileName} alt={fileName}
width={800}
height={600}
className="max-w-full max-h-full object-contain" className="max-w-full max-h-full object-contain"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />

View File

@ -16,7 +16,8 @@ const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
onChange?.(value) onChange?.(value)
} }
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput // Фильтруем пропсы, которые могут конфликтовать с IMaskInput
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { min, max, step, ...filteredProps } = props const { min, max, step, ...filteredProps } = props
return ( return (
@ -61,6 +62,7 @@ const GlassPhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
const isEmpty = !value || value.replace(/\D/g, '').length === 0 const isEmpty = !value || value.replace(/\D/g, '').length === 0
// Фильтруем пропсы, которые могут конфликтовать с IMaskInput // Фильтруем пропсы, которые могут конфликтовать с IMaskInput
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { min, max, step, onFocus, onBlur, ...filteredProps } = props const { min, max, step, onFocus, onBlur, ...filteredProps } = props
return ( return (

View File

@ -23,6 +23,7 @@ export function VoicePlayer({ audioUrl, duration = 0, isCurrentUser = false }: V
if (duration > 0 && (!audioDuration || audioDuration === 0)) { if (duration > 0 && (!audioDuration || audioDuration === 0)) {
setAudioDuration(duration) setAudioDuration(duration)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [duration, audioDuration]) }, [duration, audioDuration])
useEffect(() => { useEffect(() => {

View File

@ -30,9 +30,11 @@ const generateToken = (payload: AuthTokenPayload): string => {
return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '30d' }) return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '30d' })
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const verifyToken = (token: string): AuthTokenPayload => { const verifyToken = (token: string): AuthTokenPayload => {
try { try {
return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) { } catch (error) {
throw new GraphQLError('Недействительный токен', { throw new GraphQLError('Недействительный токен', {
extensions: { code: 'UNAUTHENTICATED' } extensions: { code: 'UNAUTHENTICATED' }
@ -94,7 +96,7 @@ function parseLiteral(ast: unknown): unknown {
return value return value
} }
case Kind.LIST: case Kind.LIST:
return ast.values.map(parseLiteral) return (ast as { values: unknown[] }).values.map(parseLiteral)
default: default:
return null return null
} }
@ -774,7 +776,7 @@ export const resolvers = {
const organization = await prisma.organization.create({ const organization = await prisma.organization.create({
data: { data: {
inn: validationResults[0]?.data?.inn || `SELLER_${Date.now()}`, inn: (validationResults[0]?.data?.inn as string) || `SELLER_${Date.now()}`,
name: shopName, // Используем tradeMark как основное название name: shopName, // Используем tradeMark как основное название
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`, fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
type: 'SELLER' type: 'SELLER'
@ -788,7 +790,7 @@ export const resolvers = {
marketplace: validation.marketplace as 'WILDBERRIES' | 'OZON', marketplace: validation.marketplace as 'WILDBERRIES' | 'OZON',
apiKey: validation.apiKey, apiKey: validation.apiKey,
organizationId: organization.id, organizationId: organization.id,
validationData: validation.data validationData: JSON.parse(JSON.stringify(validation.data))
} }
}) })
} }
@ -910,8 +912,8 @@ export const resolvers = {
where: { id: existingKey.id }, where: { id: existingKey.id },
data: { data: {
apiKey, apiKey,
validationData: validationResult.data, validationData: JSON.parse(JSON.stringify(validationResult.data)),
isActive: true isActive: true
} }
}) })
@ -927,7 +929,7 @@ export const resolvers = {
marketplace, marketplace,
apiKey, apiKey,
organizationId: user.organization.id, organizationId: user.organization.id,
validationData: validationResult.data validationData: JSON.parse(JSON.stringify(validationResult.data))
} }
}) })
@ -1097,7 +1099,7 @@ export const resolvers = {
} }
// Обновляем организацию // Обновляем организацию
const updatedOrganization = await prisma.organization.update({ await prisma.organization.update({
where: { id: user.organization.id }, where: { id: user.organization.id },
data: updateData, data: updateData,
include: { include: {
@ -1213,7 +1215,7 @@ export const resolvers = {
} }
// Обновляем организацию // Обновляем организацию
const updatedOrganization = await prisma.organization.update({ await prisma.organization.update({
where: { id: user.organization.id }, where: { id: user.organization.id },
data: updateData, data: updateData,
include: { include: {

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { apolloClient } from '@/lib/apollo-client' import { apolloClient } from '@/lib/apollo-client'
export const useApolloRefresh = () => { export const useApolloRefresh = () => {

View File

@ -27,7 +27,7 @@ const authLink = setContext((operation, { headers }) => {
}) })
// Error Link для обработки ошибок // Error Link для обработки ошибок
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => { const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) { if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path, extensions }) => { graphQLErrors.forEach(({ message, locations, path, extensions }) => {
console.error( console.error(

View File

@ -15,9 +15,12 @@ const s3Config: S3Config = {
} }
export class S3Service { export class S3Service {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private static async createSignedUrl(fileName: string, fileType: string): Promise<string> { private static async createSignedUrl(fileName: string, fileType: string): Promise<string> {
// Для простоты пока используем прямую загрузку через fetch // Для простоты пока используем прямую загрузку через fetch
// В продакшене лучше генерировать signed URLs на backend // В продакшене лучше генерировать signed URLs на backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// fileType используется для будущей логики разделения по типам файлов
const timestamp = Date.now() const timestamp = Date.now()
const key = `avatars/${timestamp}-${fileName}` const key = `avatars/${timestamp}-${fileName}`