@@ -1,6 +1,6 @@ import React, { FC, useState } from 'react' import { useForm } from 'react-hook-form' -import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler } from 'react-hook-form' import { Box, TextField, Typography } from '@mui/material' import { useSession } from 'next-auth/react' @@ -1,6 +1,6 @@ import React, { FC, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' -import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler } from 'react-hook-form' import { Box, TextField, Typography } from '@mui/material' import axios from 'axios' import { useSession } from 'next-auth/react' @@ -13,7 +13,7 @@ export function useCreateMediaMessage( modelType: 'video' | 'image' | 'audio' | 'voice', device: Device, setMessages: Dispatch>, - mobileScrollContainer: RefObject + mobileScrollContainer: RefObject ) { const [isComplete, setIsComplete] = useState(false) const [createLoading, setCreateLoading] = useState(false) @@ -1,6 +1,6 @@ import React, { FC, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' -import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler } from 'react-hook-form' import { Box, TextField, Typography } from '@mui/material' import { useSession } from 'next-auth/react' @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react' -import { debounce } from 'lodash' +import debounce from 'lodash.debounce' import { predictPrice } from '#/shared/api/models/predict-price' @@ -1,4 +1,4 @@ -import { RegisterOptions } from 'react-hook-form/dist/types/validator' +import { RegisterOptions } from 'react-hook-form' type ContactFields = { name: string @@ -1,6 +1,6 @@ import React, { useMemo } from 'react' import { Controller, FormProvider, useForm } from 'react-hook-form' -import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler } from 'react-hook-form' import { Box, TextField, Typography } from '@mui/material' import { useRouter } from 'next/router' @@ -55,36 +55,42 @@ width: 100%; margin-top: 30px; - & :global .MuiSvgIcon-root { + :global(.MuiSvgIcon-root) { width: 40px; height: 40px; cursor: pointer; - .MuiStepIcon-text { + + :global(.MuiStepIcon-text) { font-size: 12px; } } - & :global .MuiSvgIcon-root.Mui-active { + + :global(.MuiSvgIcon-root.Mui-active) { width: 40px; height: 40px; color: var(--text-color-purple); - .MuiStepIcon-text { + + :global(.MuiStepIcon-text) { font-size: 12px; } } - & :global .MuiSvgIcon-root.Mui-completed { + + :global(.MuiSvgIcon-root.Mui-completed) { width: 40px; height: 40px; color: var(--text-color-purple); - .MuiStepIcon-text { + + :global(.MuiStepIcon-text) { font-size: 12px; } } - & :global .MuiStepLabel-labelContainer { - span.Mui-active { + :global(.MuiStepLabel-labelContainer) { + :global(span.Mui-active) { color: var(--text-color-purple); } - span { + + :global(span) { color: var(--text-color-main); } } @@ -1,4 +1,4 @@ -import { RegisterOptions } from 'react-hook-form/dist/types/validator' +import { RegisterOptions } from 'react-hook-form' export const PasswordOptions: RegisterOptions = { required: 'Поле пароль обязательно к заполнению!', @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { useCookies } from 'react-cookie' import { useForm } from 'react-hook-form' -import { SubmitErrorHandler, SubmitHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler, SubmitHandler } from 'react-hook-form' import { Button, InputAdornment, TextField } from '@mui/material' import Box from '@mui/material/Box' import CircularProgress from '@mui/material/CircularProgress' @@ -4,8 +4,8 @@ import YandexProvider from 'next-auth/providers/yandex' import { getAll } from '#/entities/user-account/model/user-type-slice' -import { AuthConstants, ERROR_MAPPING, ERROR_YANDEX_MAPPING } from './constants' -import { AuthorizationProxy } from './proxy' +import { AuthConstants, ERROR_MAPPING, ERROR_YANDEX_MAPPING } from '#/shared/lib/auth/constants' +import { AuthorizationProxy } from '#/shared/lib/auth/authorization-proxy' const AuthProxy = new AuthorizationProxy() @@ -1,6 +1,7 @@ -import dynamic from 'next/dynamic'; +import dynamic from 'next/dynamic' +import type { ReactElement } from 'react' -type ClientOnlyProps = { children: JSX.Element }; +type ClientOnlyProps = { children: ReactElement } const ClientOnly = (props: ClientOnlyProps) => { const { children } = props; @@ -1,10 +1,9 @@ import axios from 'axios' import { JWT } from 'next-auth/jwt' +import { AuthConstants } from '#/shared/lib/auth/constants' import { getApiUrl } from '#/shared/lib/constants' -import { AuthConstants } from './constants' - export class AuthorizationProxy { public readonly loginByEmail = async (email: string, password: string) => { return await fetch(getApiUrl() + '/auth/login', { @@ -17,7 +17,7 @@ export abstract class AuthConstants { public static readonly sessionTime = Number(env.NEXT_SESSION_TIME ?? env.NEXT_PUBLIC_SESSION_TIME) * 1000 - + private static readonly expiresTime = this.sessionTime public static readonly getExpiresDate = () => Date.now() + this.expiresTime @@ -32,10 +32,10 @@ export const ERROR_MAPPING: Record = { export const ERROR_YANDEX_MAPPING: Record = { "Your credentials aren't allowed": 'yandex_credentials_not_allowed', - 'Invalid client_id parameter value.': 'yandex_client_id_not_correct', + 'Invalid client_id parameter value.': 'yandex_client_id_not_correct', } export const ERRROR_YANDEX_TRANSLATE_MAPPING: Record = { - 'yandex_credentials_not_allowed': 'Неверные учетные данные', - 'yandex_client_id_not_correct': 'Ошибка сервера (скоро поправим)', + yandex_credentials_not_allowed: 'Неверные учетные данные', + yandex_client_id_not_correct: 'Ошибка сервера (скоро поправим)', } @@ -1,4 +1,4 @@ -import { RegisterOptions } from 'react-hook-form/dist/types/validator' +import { RegisterOptions } from 'react-hook-form' const EMAIL_REGEXP = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu @@ -1,7 +1,7 @@ import React from 'react' export const useAutoScroll = (depend: any) => { - const refScroll = React.useRef() as any + const refScroll = React.useRef(null) React.useEffect(() => { const block = refScroll.current @@ -1,5 +1,5 @@ import React from 'react' -import { RegisterOptions } from 'react-hook-form/dist/types/validator' +import { RegisterOptions } from 'react-hook-form' import { TextField } from '@mui/material' import { TextFieldProps } from '@mui/material/TextField/TextField' @@ -2,7 +2,7 @@ import * as React from 'react' import { useEffect } from 'react' import { useCookies } from 'react-cookie' import { useForm } from 'react-hook-form' -import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler } from 'react-hook-form' import { Box, Button, InputAdornment, Link, Stack, TextField, Typography } from '@mui/material' import CircularProgress from '@mui/material/CircularProgress' import Image from 'next/image' @@ -16,7 +16,7 @@ import OpenedEyeSvg from '#/assets/svg/opened-eye.svg?react' import { PasswordOptions } from '#/features/register-by-email/lib/constants' import { YandexAuthButton } from '#/features/yandex-auth-button' import { NextPageWithLayout } from '#/pages/_app' -import { ERROR_MAPPING, ERRROR_YANDEX_TRANSLATE_MAPPING } from '#/pages/api/auth/constants' +import { ERROR_MAPPING, ERRROR_YANDEX_TRANSLATE_MAPPING } from '#/shared/lib/auth/constants' import { emailOptions } from '#/shared' import { getDeviceType } from '#/shared/lib/helpers' import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' @@ -95,7 +95,7 @@ color: var(--text-color-additional-two); } } - .titleModalLimit:global { + .titleModalLimit { border: 1px solid red; margin-bottom: 10px; } @@ -107,7 +107,7 @@ color: var(--text-color-additional-two); } } - .titleModalLimit:global { + .titleModalLimit { border: 1px solid red; margin-bottom: 10px; } @@ -1,6 +1,6 @@ import React, { FC, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' -import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { SubmitErrorHandler } from 'react-hook-form' import { Box, TextField, Typography } from '@mui/material' import Paper from '@mui/material/Paper' import Table from '@mui/material/Table' @@ -11,7 +11,7 @@ interface CodeProps { node: any inline: boolean | undefined className: string | undefined - children: React.ReactNode & React.ReactNode[] + children: React.ReactNode language: string | undefined } @@ -9,7 +9,7 @@ export const getDefaultLayout = function (layoutProps: Omit JSX.Element, + Element: (...props: any) => ReactElement, layoutProps: Omit = {} ) { return (page: ReactElement) => {page} @@ -12,19 +12,20 @@ export const Markdown = ({ content, id = 'markdown'}: IProps) => { const LazyCode = dynamic(() => import('#/widgets/chat-gpt-field/ui/code')) return ( +
{children} @@ -41,5 +42,6 @@ export const Markdown = ({ content, id = 'markdown'}: IProps) => { > {content} +
) } @@ -29,7 +29,7 @@ interface IMessagesList { export const ChatMessagesList: React.FC = memo( ({ messageResponse, onLoadImage, setResendValue, modelTitle, device, modelType, botParams, getMessagesPagination, deleteMessage, loading }) => { - const paginationScroll = React.useRef() + const paginationScroll = React.useRef(null) const [isPaginating, setIsPaginating] = React.useState(false) const [chatScrollHeight, setChatScrollHeight] = React.useState(0) const [scrollBottom, setScrollBottom] = React.useState(0) @@ -1,4 +1,4 @@ -.drawer :global .MuiPaper-root { +.drawer :global(.MuiPaper-root) { } .Image { @@ -28,7 +28,7 @@ export async function middleware(req: NextRequest) { export const config = { matcher: [ - '/((?!api|_next/static|healthz|_next/image|favicon.ico|sitemap.xml|manifest.json|svg|robots.txt|login|confirm|register|auth|change-password|reset|.*\\.[svg|json|png|jpg|jpeg|gif|css|js]).*)', + '/((?!api|_next/static|healthz|_next/image|favicon.ico|sitemap.xml|manifest.json|monitoring|svg|robots.txt|login|confirm|register|auth|change-password|reset|.*\\.[svg|json|png|jpg|jpeg|gif|css|js]).*)', '/', ], } @@ -1,32 +0,0 @@ -{ - "plugins": ["prettier", "simple-import-sort"], - "extends": ["next/core-web-vitals"], - "rules": { - "no-console": "warn", - "quotes": ["warn", "single"], - "indent": "off", - "jsx-quotes": ["warn", "prefer-single"], - "react/display-name": "off", - "simple-import-sort/imports": [ - "warn", - { - "groups": [ - // Packages `react` related packages come first. - ["^react", "^@?\\w"], - // Internal packages. - ["^(@|#|components)(/.*|$)"], - // Side effect imports. - ["^\\u0000"], - // Parent imports. Put `..` last. - ["^\\.\\.(?!/?$)", "^\\.\\./?$"], - // Other relative imports. Put same-folder imports and `.` last. - ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"], - // Style imports. - ["^.+\\.?(css)$"] - ] - } - ], - "simple-import-sort/exports": "off", - "react-hooks/rules-of-hooks": "off" - } -} @@ -0,0 +1 @@ +legacy-peer-deps=true @@ -0,0 +1,42 @@ +import nextVitals from 'eslint-config-next/core-web-vitals' +import prettierPlugin from 'eslint-plugin-prettier' +import simpleImportSort from 'eslint-plugin-simple-import-sort' + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + ...nextVitals, + { + plugins: { + prettier: prettierPlugin, + 'simple-import-sort': simpleImportSort, + }, + rules: { + 'no-console': 'warn', + quotes: ['warn', 'single'], + indent: 'off', + 'jsx-quotes': ['warn', 'prefer-single'], + 'react/display-name': 'off', + 'simple-import-sort/imports': [ + 'warn', + { + groups: [ + ['^react', '^@?\\w'], + ['^(@|#|components)(/.*|$)'], + ['^\\u0000'], + ['^\\.\\.(?!/?$)', '^\\.\\./?$'], + ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], + ['^.+\\.?(css)$'], + ], + }, + ], + 'simple-import-sort/exports': 'off', + 'react-hooks/rules-of-hooks': 'off', + 'react-hooks/set-state-in-effect': 'off', + 'react-hooks/preserve-manual-memoization': 'off', + 'react-hooks/refs': 'off', + 'react-hooks/use-memo': 'off', + 'react-hooks/immutability': 'off', + 'react-hooks/incompatible-library': 'off', + }, + }, +] @@ -3,39 +3,41 @@ const { i18n } = require('./next-i18next.config') const nextConfig = { i18n, - webpack(config, { isServer, dev }) { - config.experiments = { - asyncWebAssembly: true, - layers: true, - } - const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg')) - - config.module.rules.push( - { - test: /\.svg$/i, - issuer: fileLoaderRule.issuer, - resourceQuery: /react/, - use: ['@svgr/webpack'], - }, - { - ...fileLoaderRule, - test: /\.svg$/i, - resourceQuery: /url/, - } - ) - fileLoaderRule.exclude = /\.svg$/i - - return config - }, reactStrictMode: false, productionBrowserSourceMaps: true, + turbopack: { + rules: { + '*.svg': { + condition: { query: /react/ }, + loaders: ['@svgr/webpack'], + as: '*.js', + }, + }, + }, images: { - domains: [ - 'app.air.fail', - 'storage.yandexcloud.net', - 's3-alpha-sig.figma.com', - 'localhost', + remotePatterns: [ + { + protocol: 'https', + hostname: 'app.air.fail', + }, + { + protocol: 'https', + hostname: 'storage.yandexcloud.net', + }, + { + protocol: 'https', + hostname: 's3-alpha-sig.figma.com', + }, + { + protocol: 'http', + hostname: 'localhost', + }, + { + protocol: 'https', + hostname: 'localhost', + }, ], + dangerouslyAllowLocalIP: true, }, } @@ -45,11 +47,11 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ const analyzerConfig = withBundleAnalyzer(nextConfig) -const { withSentryConfig } = require("@sentry/nextjs"); +const { withSentryConfig } = require('@sentry/nextjs') module.exports = withSentryConfig(analyzerConfig, { silent: !process.env.CI, widenClientFileUpload: true, - tunnelRoute: process.env.SENTRY_TUNNEL_PATH ?? '/monitoring', - debug: process.env.SENTRY_DEBUG ?? false -}); + tunnelRoute: process.env.SENTRY_TUNNEL_PATH ?? '/monitoring', + debug: process.env.SENTRY_DEBUG ?? false, +}) @@ -9,10 +9,10 @@ "start": "next start", "build": "next build", "dev": "next dev", - "lint": "next lint" + "lint": "eslint ." }, "engines": { - "node": ">=12.0.0" + "node": ">=20.9.0" }, "keywords": [], "license": "MIT", @@ -32,18 +32,17 @@ "@mui/material": "^5.11.12", "@mui/styled-engine-sc": "^5.11.11", "@mui/x-date-pickers": "^6.6.0", - "@next/bundle-analyzer": "^13.4.3", + "@next/bundle-analyzer": "^16.0.0", "@reduxjs/toolkit": "^1.9.5", "@sentry/cli": "^2.58.4", - "@sentry/nextjs": "^10.32.1", - "@sentry/webpack-plugin": "^5.1.1", + "@sentry/nextjs": "^10.54.0", "@testing-library/user-event": "^14.6.1", "@types/cookie": "^0.5.1", "@types/intro.js": "^5.1.1", "@types/lodash": "^4.14.195", "@types/node": "18.15.2", - "@types/react": "18.0.28", - "@types/react-dom": "18.0.11", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "@types/react-syntax-highlighter": "^15.5.7", "@unleash/proxy-client-react": "^5.0.1", "@vercel/otel": "^1.13.0", @@ -55,36 +54,38 @@ "cross-env": "^7.0.3", "dayjs": "^1.11.8", "draft-js": "^0.11.7", - "eslint": "^8.7.0", - "eslint-config-next": "13.2.4", + "eslint-config-next": "^16.0.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-simple-import-sort": "^10.0.0", + "framer-motion": "^12.23.12", "i18next": "^23.4.1", + "intro.js": "^7.2.0", "intro.js-react": "^1.0.0", "lint-staged": "^13.2.2", "lodash.debounce": "^4.0.8", - "next": "13.2.4", - "next-auth": "^4.20.1", + "motion": "^12.23.12", + "next": "^16.0.0", + "next-auth": "^4.24.11", "next-i18next": "^14.0.0", "nextstepjs": "^2.2.0", "prettier": "^2.8.8", - "react": "18.2.0", + "react": "^19.0.0", "react-chartjs-2": "^5.2.0", "react-cookie": "^4.1.1", - "react-dom": "18.2.0", + "react-dom": "^19.0.0", "react-draft-wysiwyg": "^1.15.0", "react-hook-form": "^7.54.2", "react-i18next": "^13.0.3", - "react-markdown": "^8.0.7", + "react-markdown": "^10.1.0", "react-redux": "^8.0.5", "react-syntax-highlighter": "^15.5.0", "react-transition-group": "^4.4.5", - "remark-gfm": "^3.0.1", + "remark-gfm": "^4.0.1", "sass": "^1.63.4", "sharp": "^0.34.1", "styled-components": "^5.3.9", "swiper": "^11.2.1", - "typescript": "5.1.3", + "unleash-proxy-client": "^3.7.8", "usehooks-ts": "^3.1.1" }, "devDependencies": { @@ -95,9 +96,11 @@ "@types/intro.js": "^5.1.1", "@types/jest": "^29.5.14", "@types/lodash": "^4.14.195", + "@types/lodash.debounce": "^4.0.9", "@types/react-draft-wysiwyg": "^1.13.8", "@types/react-syntax-highlighter": "^15.5.7", "cross-fetch": "^4.1.0", + "eslint": "^9.39.4", "eslint-config-prettier": "^8.8.0", "husky": "^9.1.7", "jest": "^29.7.0", @@ -105,13 +108,14 @@ "lint-staged": "^13.2.2", "prettier": "^2.8.8", "ts-node": "^10.9.2", - "typescript": "5.1.3" + "typescript": "^5.7.3" }, "resolutions": { "react-scripts/webpack-dev-server/yargs/yargs-parser": ">=18.1.2" }, "overrides": { - "es5-ext@^0.10.50": "0.10.53" + "es5-ext@^0.10.50": "0.10.53", + "@opentelemetry/semantic-conventions": "^1.41.0" }, "browserslist": { "production": [ @@ -1,26 +1,47 @@ { - "compilerOptions": { - "target": "ESNext", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "baseUrl": ".", - "noImplicitThis": false, - "paths": { - "#/*": ["./src/*"], - "#/components/*": ["components/*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "ESNext", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "baseUrl": ".", + "noImplicitThis": false, + "paths": { + "#/*": [ + "./src/*" + ], + "#/components/*": [ + "components/*" + ] + }, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }