Binary files /dev/null and b/public/images/dashboard.png differ @@ -1,3 +1,5 @@ - - + + \ No newline at end of file @@ -1,3 +1,4 @@ - - + + \ No newline at end of file @@ -2,9 +2,9 @@ import React from 'react' import { FormControlLabel, Typography } from '@mui/material' import Switch from '@mui/material/Switch' -import { setParams } from '@/src/main/store/model-parametres-store' -import { useAppDispatch } from '@/src/main/store/store' -import TooltipModelTypes from '@/src/widgets/filters-gpt/ui/tooltip-model-types' +import { setParams } from '#/app/store/model-parametres-store' +import { useAppDispatch } from '#/app/store/store' +import TooltipModelTypes from '#/widgets/filters-gpt/ui/tooltip-model-types' interface IProps { name: string @@ -4,11 +4,11 @@ import { TextField, Typography } from '@mui/material' import Box from '@mui/material/Box' import { TextFieldProps } from '@mui/material/TextField/TextField' -import { setParams } from '@/src/main/store/model-parametres-store' -import { useAppDispatch } from '@/src/main/store/store' -import { InputStyleDark, InputStyleLight } from '@/src/shared' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' -import TooltipModelTypes from '@/src/widgets/filters-gpt/ui/tooltip-model-types' +import { setParams } from '#/app/store/model-parametres-store' +import { useAppDispatch } from '#/app/store/store' +import { InputStyleDark, InputStyleLight } from '#/shared' +import { useThemeAndDevice } from '#/shared/lib/hooks' +import TooltipModelTypes from '#/widgets/filters-gpt/ui/tooltip-model-types' interface IProps { title: string @@ -2,10 +2,10 @@ import * as React from 'react' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import { MenuItem, Select, SelectChangeEvent, Stack, Typography } from '@mui/material' -import { setParams } from '@/src/main/store/model-parametres-store' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { baseColor } from '@/src/shared/lib/constants/colors' -import TooltipModelTypes from '@/src/widgets/filters-gpt/ui/tooltip-model-types' +import { setParams } from '#/app/store/model-parametres-store' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { baseColor } from '#/shared/lib/constants/colors' +import TooltipModelTypes from '#/widgets/filters-gpt/ui/tooltip-model-types' interface IProps { selects: { availables: string[]; default: any; end: number; start: number; step: number } @@ -1,9 +1,9 @@ import * as React from 'react' -import { setParams } from '@/src/main/store/model-parametres-store' -import { useAppDispatch } from '@/src/main/store/store' -import { Slider as Sl } from '@/src/shared' -import TooltipModelTypes from '@/src/widgets/filters-gpt/ui/tooltip-model-types' +import { setParams } from '#/app/store/model-parametres-store' +import { useAppDispatch } from '#/app/store/store' +import { Slider as Sl } from '#/shared' +import TooltipModelTypes from '#/widgets/filters-gpt/ui/tooltip-model-types' interface IProps { title: string @@ -2,8 +2,8 @@ import React, { useRef, useState } from 'react' import { Box, Typography } from '@mui/material' import Image from 'next/image' -import { useAppSelector } from '@/src/main/store/store' -import { TooltipCustom } from '@/src/shared' +import { useAppSelector } from '#/app/store/store' +import { TooltipCustom } from '#/shared' interface IProps { loading: boolean @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import CircularProgress from '@mui/material/CircularProgress' import Image from 'next/image' @@ -11,20 +11,9 @@ interface IProps { required: (string | null)[] } -export const SendBtn = ({ - loading, - unpinImage, - input, - sendMessage, - setInput, - required, -}: IProps) => { +export const SendBtn = ({ loading, unpinImage, input, sendMessage, setInput, required }: IProps) => { const send_icon = '/svg/chatgpt/send_message.svg' - useEffect(() => { - console.log(loading) - }, [loading]) - if (loading) { return ( (toShort: T, lang: LangParam): LangRet return arrLang.find(([_, value]) => lang === value)![0] as LangReturn } -const InfoBar: React.FC = ({ title, device }) => { +const InfoBar: React.FC = ({ device }) => { const theme = useAppSelector((state) => state.theme.theme) const balance = useAppSelector((state) => state.balance.balance) @@ -68,9 +66,7 @@ const InfoBar: React.FC = ({ title, device }) => { const { data } = useSession() - const { email, first_name, last_name, profile_picture_link, account_type } = useAppSelector( - (state) => state.user - ) + const { email, first_name, last_name, profile_picture_link, account_type } = useAppSelector((state) => state.user) const dispatch = useAppDispatch() @@ -147,21 +143,14 @@ const InfoBar: React.FC = ({ title, device }) => { > - Приносим извинения за отсутствие соединения с - платформой, мы работаем над стабильностью и благодарны, что - вы остаетесь с нами! - {/* Мы уже работаем над этой проблемой. Попробуйте перезайти в - аккаунт */} + Мы уже работаем над этой проблемой. Попробуйте перезайти в аккаунт - {/* */} + + dispatch(change(null))} @@ -182,10 +171,7 @@ const InfoBar: React.FC = ({ title, device }) => { {show_balance && ( - + {declineToken(balance.toString())} @@ -207,14 +193,13 @@ const InfoBar: React.FC = ({ title, device }) => { cursor: 'pointer', }} > - {email[0] || 'N'} + {email ? email[0] : 'N'} = ({ title, device }) => { - router.push('/account?scope=setting') - } + onClick={() => router.push('/account?scope=setting')} > = ({ title, device }) => { - router.push( - '/account?scope=business' - ) - } + onClick={() => router.push('/account?scope=business')} > {''} = ({ title, device }) => { - router.push( - '/account?scope=referral' - ) - } + onClick={() => router.push('/account?scope=referral')} > {''} = ({ title, device }) => { - router.push( - '/account?scope=subscribe' - ) - } + onClick={() => router.push('/account?scope=subscribe')} > = ({ title, device }) => { signOut()} + onClick={() => { + signOut() + }} sx={{ marginTop: '15px', cursor: 'pointer' }} > - {''} - + {''} + Выйти @@ -1,46 +1,41 @@ -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { Box } from '@mui/material' import { ThemeProvider } from '@mui/material/styles' import Head from 'next/head' import { useRouter } from 'next/router' import { useSession } from 'next-auth/react' -import { getUserBalance } from '@/src/entities/balance' -import { useTheme } from '@/src/entities/theme' -import { getAllInfo } from '@/src/entities/user-account' -import { isSettingExist } from '@/src/entities/user-account/lib/helpers/is-setting-exist' -import { getUserAccountSettings, setSettings } from '@/src/entities/user-account/model/settings' -import InfoBar from '@/src/main/layout/ui/info-bar' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { Loader } from '@/src/shared' -import { pingFangFont } from '@/src/shared/lib/constants/font/font' -import { Device } from '@/src/shared/lib/types/entities' -import { MainMenuMobile } from '@/src/widgets/main-menu' -import { SideMenu } from '@/src/widgets/side-menu' - -const freeRoutes = ['/login', '/register', '/reset', '/changePassword'] +import { getUserBalance } from '#/entities/balance' +import { useTheme } from '#/entities/theme' +import { getAllInfo } from '#/entities/user-account' +import { isSettingExist } from '#/entities/user-account/lib/helpers/is-setting-exist' +import { getUserAccountSettings, setSettings } from '#/entities/user-account/model/settings' +import InfoBar from '#/app/layout/ui/info-bar' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { Loader } from '#/shared' +import { pingFangFont } from '#/shared/lib/constants/font/font' +import { Device } from '#/shared/lib/types/entities' +import { MainMenuMobile } from '#/widgets/main-menu' +import { SideMenu } from '#/widgets/side-menu' +import { getDeviceType } from '#/shared/lib/helpers' + +const freeRoutes = ['/login', '/register', '/reset', '/change-password'] interface Props { children: React.ReactNode titlePage: string - device?: Device isAuthPage?: boolean title?: string | React.ReactNode | null isLoader?: boolean } -export const Layout: React.FC = ({ - children, - device, - isAuthPage = false, - titlePage, - title = titlePage, - isLoader, -}) => { +export const Layout: React.FC = ({ children, isAuthPage = false, titlePage, title = titlePage, isLoader }) => { const { data: sessionData } = useSession() const appState = useAppSelector((state) => state) const dispatch = useAppDispatch() + const device = getDeviceType() + const desktop = device === 'desktop' const [sidemenuDefaultOpen, setSidemenuDefaultOpen] = useState(true) @@ -52,15 +47,9 @@ export const Layout: React.FC = ({ const router = useRouter() React.useEffect(() => { - if (status === 'authenticated') { - dispatch(getUserBalance(data?.access)) - dispatch(getAllInfo(data?.access)) - } - - if (status === 'unauthenticated' && !freeRoutes.includes(router.route)) { - router.push('/login') - } - }, [status]) + dispatch(getUserBalance(data?.access)) + dispatch(getAllInfo(data?.access)) + }, [data]) React.useEffect(() => { if ( @@ -100,13 +89,7 @@ export const Layout: React.FC = ({ if (status === 'loading' || isLoader) { return ( - + ) @@ -126,9 +109,8 @@ export const Layout: React.FC = ({ id={'layout'} sx={{ width: '100%', - height: desktop ? '100%' : '100dvh', + height: desktop ? '100%' : '100vh', padding: isAuthPage ? '0px' : desktop ? '0px' : '10px', - overflow: 'visible', }} > = ({ {!isAuthPage ? ( desktop ? ( - + - + {children} @@ -0,0 +1 @@ +export { Layout } from '#/app/layout/ui/layout' @@ -12,7 +12,10 @@ export const paramsStore = createSlice({ name: 'paramsStore', initialState, reducers: { - setParams: (state, action: { payload: { [key: string]: string | number | number[] | boolean }; type: Object }) => { + setParams: ( + state, + action: { payload: { [key: string]: string | number | number[] | boolean }; type: Object } + ) => { if (Object.keys(action.payload).length === 0) { state.params = {} return @@ -1,12 +1,12 @@ -import { createSlice,PayloadAction } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' -const defaultDuration = 4000; +const defaultDuration = 4000 -type Notification = { - id: number, - text: string, - type: 'error' | 'success', - duration?: number +type Notification = { + id: number + text: string + type: 'error' | 'success' + duration?: number } export interface State { @@ -22,17 +22,16 @@ export const notificationSlice = createSlice({ initialState, reducers: { add: (state, action: PayloadAction) => { + const { duration, id } = action.payload - const { duration, id } = action.payload - - state.notifications = [...state.notifications, action.payload]; - setTimeout(() => { - state.notifications = state.notifications.filter(el => el.id !== id) - }, duration ?? defaultDuration); - } + state.notifications = [...state.notifications, action.payload] + setTimeout(() => { + state.notifications = state.notifications.filter((el) => el.id !== id) + }, duration ?? defaultDuration) + }, }, }) export const { add } = notificationSlice.actions -export default notificationSlice.reducer \ No newline at end of file +export default notificationSlice.reducer @@ -1,15 +1,16 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' import { configureStore } from '@reduxjs/toolkit' -import { balanceSlice } from '@/src/entities/balance' -import { themeSlice } from '@/src/entities/theme' -import { userSlice } from '@/src/entities/user-account' -import { settingsSlice } from '@/src/entities/user-account/model/settings' -import { stepperSlice } from '@/src/features/register-business' -import { copySlice } from '@/src/features/use-copy/copy-slice' -import { paramsStore } from '@/src/main/store/model-parametres-store' +import { balanceSlice } from '#/entities/balance' +import { themeSlice } from '#/entities/theme' +import { userSlice } from '#/entities/user-account' +import { settingsSlice } from '#/entities/user-account/model/settings' +import { stepperSlice } from '#/features/register-business' +import { copySlice } from '#/features/use-copy/copy-slice' +import { paramsStore } from '#/app/store/model-parametres-store' import { notificationSlice } from './notification-slice' +import { pendingSlice } from '#/features/pending' export const store = configureStore({ reducer: { @@ -21,6 +22,7 @@ export const store = configureStore({ params: paramsStore.reducer, settings: settingsSlice.reducer, copy: copySlice.reducer, + loading: pendingSlice.reducer, }, }) @@ -7,14 +7,14 @@ justify-content: space-between; margin-top: 8px; - .side_desk{ + .side_desk { margin-top: 50px; - @media(max-width: 1080px){ + @media (max-width: 1080px) { margin-top: 70px; } - @media(max-width: 990px){ + @media (max-width: 990px) { margin-top: 0; } } @@ -22,7 +22,7 @@ .side_mobile { margin-top: 50px; - @media(max-width: 792px){ + @media (max-width: 792px) { margin-top: 70px; } @@ -31,7 +31,6 @@ } } - @media (max-width: 768px) { width: 100%; } @@ -75,17 +74,17 @@ height: 100%; .list { - width: 100%; + width: 100%; overflow-y: auto; - &::-webkit-scrollbar{ + &::-webkit-scrollbar { height: 5px; width: 2px; } &::-webkit-scrollbar-track { background: initial; - margin: 0; + margin: 0; } &::-webkit-scrollbar-thumb { @@ -0,0 +1,28 @@ +.btn-classic { + background-color: var(--air-color); + border-radius: 13px; + text-transform: none; + color: white; + height: 43px; + font-size: 13px; + font-weight: 600; + border: none; + box-shadow: none; + &:hover { + background-color: var(--text-color-purple); + opacity: 0.9; + } + &:disabled { + color: white; + } +} + +.btn-danger { + @extend .btn-classic; + color: #ff2372; + background-color: var(--new-ui-btn-danger-bg); + &:hover { + background-color: var(--new-ui-btn-danger-bg); + opacity: 0.9; + } +} @@ -85,13 +85,11 @@ display: flex; flex-direction: column; } - - .main_block > div { - margin: 0; - } -} - + .main_block > div { + margin: 0; + } +} .main_block { display: flex; @@ -0,0 +1,114 @@ +.toggle_button { + padding: 0; + margin-right: 12px; + border: none; +} + +.toggle_button:hover { + background-color: transparent; +} + +.wrap_toggle_button { + border: none; + border-radius: 13px; + text-transform: none; + font-weight: 500; + font-size: 15px; + /*white-space: nowrap;*/ + color: var(--new-ui-gray-color); +} + +.wrap_toggle_button_active { + border: none; + background-color: var(--new-ui-main-color); + border-radius: 13px; + text-transform: none; + font-weight: 500; + font-size: 15px; + color: var(--text-color-main); +} + +.toggle_text_active { + font-weight: 500; + font-size: 15px; + color: var(--text-color-main); +} + +.wrap_toggle_button_active_chat { + border: none; + background-color: #f2f2ff; + border-radius: 13px; + padding: 10px 15px; + text-transform: none; +} + +.toggle_text_active_chat { + font-weight: 500; + font-size: 15px; + color: #8280ff; +} + +.chat-item { + width: 100%; + border: none; + background-color: white; + border-radius: 13px; + padding: 10px 15px; + text-transform: none; +} +.chat-item_active { + width: 100%; + border: none; + background-color: #f2f2ff; + border-radius: 13px; + padding: 10px 15px; + text-transform: none; +} + +.chat-text { + font-weight: 500; + font-size: 15px; + color: var(--new-ui-main-color); +} + +.toggle_text { + font-weight: 500; + font-size: 15px; + /*white-space: nowrap;*/ + color: var(--new-ui-gray-color); + text-transform: none; +} + +.wrap_block { + width: 100%; + border-radius: 13px; + background-color: var(--new-ui-main-color); + padding: 30px; +} + +@media (max-width: 768px) { + .main_block { + width: 100%; + display: flex; + flex-direction: column; + } + + .main_block > div { + margin: 0; + } +} + +.main_block { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-top: 20px; +} + +.input_title { + font-size: 15px; + margin-bottom: 5px; + margin-left: 3px; + font-weight: 400; + color: var(--new-ui-gray-color); +} @@ -20,19 +20,19 @@ } } -.buttons_block{ +.buttons_block { display: flex; justify-content: flex-start; align-items: center; gap: 20px; } -.title_text{ +.title_text { display: block; font-size: 21px; } -.chapter_block{ +.chapter_block { padding-inline: 20px; } @@ -40,7 +40,7 @@ background-color: var(--background-color-page); } -.logsList{ +.logsList { margin-top: 15px; display: flex; justify-content: flex-start; @@ -49,11 +49,11 @@ gap: 15px; } -.mails{ +.mails { font-weight: 500; color: white; } -.mailWrap{ +.mailWrap { padding: 7px 12px; border-radius: 15px; background-color: rgba(127, 125, 243, 0.68); @@ -63,6 +63,6 @@ align-items: center; gap: 8px; } -.pointer{ +.pointer { cursor: pointer; -} \ No newline at end of file +} @@ -151,12 +151,8 @@ p { /* Изменение цвета активного элемента в выпадающем списке */ .MuiAutocomplete-option.Mui-selected { - background-color: var( - --new-ui-main-color - ); /* Замените #your-selected-color на цвет активного элемента */ - color: var( - --new-ui-main-color - ); /* Замените #your-selected-text-color на цвет текста активного элемента */ + background-color: var(--new-ui-main-color); /* Замените #your-selected-color на цвет активного элемента */ + color: var(--new-ui-main-color); /* Замените #your-selected-text-color на цвет текста активного элемента */ } .introjs-tooltiptext { @@ -0,0 +1,23 @@ +export interface AudioModel { + uid: string + title: string + description: string + link: string + model: string + languages: string[] + slug: string + companies: string + token_price: number + associations?: [] + model_config: { + n: number + model: string + top_p: number + temperature: number + presence_penalty: number + frequency_penalty: number + } + image_link: string + is_favourite: boolean + available_versions: null +} @@ -0,0 +1 @@ +export * from './audio' \ No newline at end of file @@ -1,13 +1,5 @@ - - + - - - - \ No newline at end of file @@ -1,4 +1,4 @@ -import { Template } from '@/src/domains/copywrite/proxy/types/template' +import { Template } from '#/domains/copywrite/proxy/types/template' export const emptyTemplate: Template = { id: 1000, @@ -1,8 +1,8 @@ import axios from 'axios' -import { Template } from '@/src/domains/copywrite/proxy/types/template' -import { API_URL } from '@/src/shared/lib/constants' -import { Message } from '@/src/shared/lib/types/model' +import { Template } from '#/domains/copywrite/proxy/types/template' +import { API_URL } from '#/shared/lib/constants' +import { Message } from '#/shared/lib/types/model' export class CopywriteProxy { token?: string @@ -1,7 +1,7 @@ import React from 'react' import { Typography } from '@mui/material' -import { Input, Slider } from '@/src/shared' +import { Input, Slider } from '#/shared' import { FiltersProps } from './types' @@ -20,9 +20,23 @@ export function Filters({ return ( <> - + - + Запрос для исключения из генерации @@ -1,8 +1,8 @@ import React from 'react' import { Stack, Typography } from '@mui/material' -import { Input, Slider } from '@/src/shared' -import { SelectUI } from '@/src/shared/ui/select' +import { Input, Slider } from '#/shared' +import { SelectUI } from '#/shared/ui/select' import { Filters } from './types' @@ -26,9 +26,30 @@ export function EpicPhotoFilters({ <> - - - + + + Запрос для исключения из генерации @@ -1,8 +1,8 @@ import React from 'react' import { Box, Typography } from '@mui/material' -import { Input, Slider, SwitchCustom } from '@/src/shared' -import { SelectUI } from '@/src/shared/ui/select' +import { Input, Slider, SwitchCustom } from '#/shared' +import { SelectUI } from '#/shared/ui/select' import { Filters } from './types' @@ -27,7 +27,14 @@ export function KandinskyFilters({ - + - - + + Запрос для исключения из генерации @@ -1,5 +1,5 @@ -import { IShortModel } from '@/src/entities/model-entity' -import { API_URL } from '@/src/shared/lib/constants' +import { IShortModel } from '#/entities/model-entity' +import { API_URL } from '#/shared/lib/constants' import axios from 'axios' export async function getAudio(token?: string): Promise { @@ -1,6 +1,6 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' export const getBalance = async (token: string): Promise => { try { @@ -1,15 +1,10 @@ -import { API_URL } from '@/src/shared/lib/constants' -import { IMessageRequest } from '@/src/shared/lib/types/types-gpt' +import { API_URL } from '#/shared/lib/constants' +import { IMessageRequest } from '#/shared/lib/types/types-gpt' import axios, { AxiosResponse } from 'axios' import { Message, MessageSend } from '../types' -export async function sendImage( - model: string | null, - dataForSend: MessageSend | FormData, - token?: string -) { - const HeaderDataType = - dataForSend instanceof FormData ? 'multipart/form-data' : 'application/json' +export async function sendImage(model: string | null, dataForSend: MessageSend | FormData, token?: string) { + const HeaderDataType = dataForSend instanceof FormData ? 'multipart/form-data' : 'application/json' return await axios.post(API_URL + `/media/image/${model}`, dataForSend, { withCredentials: true, @@ -22,13 +17,10 @@ export async function sendImage( } export async function getImagesBySlug(slug: string, token: string, offset?: number, limit = 10) { - return await axios.get( - API_URL + `/media/image/${slug}?limit=${limit}&offset=${offset}`, - { - validateStatus: (status) => status < 500, - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) + return await axios.get(API_URL + `/media/image/${slug}?limit=${limit}&offset=${offset}`, { + validateStatus: (status) => status < 500, + headers: { + Authorization: `Bearer ${token}`, + }, + }) } @@ -1,4 +1,4 @@ -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import axios from 'axios' import { IModel } from '../types' @@ -1,4 +1,4 @@ -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import axios from 'axios' import { IShortModel } from '../types' @@ -2,8 +2,8 @@ import { useState } from 'react' import { IModel } from '../types' import { useSession } from 'next-auth/react' import { getBotParams } from '../api' -import { useAppDispatch } from '@/src/main/store/store' -import { setParams } from '@/src/main/store/model-parametres-store' +import { useAppDispatch } from '#/app/store/store' +import { setParams } from '#/app/store/model-parametres-store' export function useImageBot(slug: string) { const [botParams, setBotParams] = useState(null) @@ -17,9 +17,7 @@ export function useImageBot(slug: string) { function setDefault(bot: IModel) { setVersion('') - dispatch( - setParams(bot.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) - ) + dispatch(setParams(bot.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {}))) } // это пиз**ц @@ -70,12 +68,7 @@ export function useImageBot(slug: string) { } else { setVersion(botParams.slug) dispatch( - setParams( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) + setParams(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) ) } } @@ -89,22 +82,14 @@ export function useImageBot(slug: string) { if (version === '') { return dispatch( - setParams( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) + setParams(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) ) } dispatch( setParams( botParams.parameters.reduce( - (a, v) => - v.versions.includes(version) - ? { ...a, [v.key]: v.values.default } - : { ...a }, + (a, v) => (v.versions.includes(version) ? { ...a, [v.key]: v.values.default } : { ...a }), {} ) ) @@ -47,9 +47,9 @@ background-color: var(--new-ui-main-color); border-radius: 15px; position: relative; - min-height: 310px; - max-height: 310px; - user-select: none !important; + min-height: 310px; + max-height: 310px; + user-select: none !important; cursor: pointer; box-sizing: border-box; @@ -60,11 +60,6 @@ width: 92vw; } - &_blocked { - min-height: 340px; - max-height: 340px; - } - &__tags { position: absolute; top: 20px; @@ -126,7 +121,7 @@ justify-content: center; background-color: #c32528; // padding-right: 20px; - padding: 12px 15px; + padding: 8px 15px; position: absolute; width: 100%; left: 0; @@ -135,7 +130,7 @@ &__text { color: white; - font-size: 14px; + font-size: 13px; font-weight: 600; } } @@ -2,14 +2,14 @@ import React, { useEffect, useMemo } from 'react' import { Avatar, Box, Button, Typography } from '@mui/material' import Image from 'next/image' import Link from 'next/link' -import LockSvg from '@/src/assets/svg/lock.svg?react' -import BlockedSvg from '@/src/assets/svg/blocked.svg?react' +import LockSvg from '#/assets/svg/lock.svg?react' +import BlockedSvg from '#/assets/svg/blocked.svg?react' import styles from './card-chat.module.scss' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' -import { c } from '@/src/shared/lib/helpers' -import { IShortModel } from '@/src/entities/model-entity' -import { SvgIcon } from '@/src/shared/ui/svg' +import { useThemeAndDevice } from '#/shared/lib/hooks' +import { c } from '#/shared/lib/helpers' +import { IShortModel } from '#/entities/model-entity' +import { SvgIcon } from '#/shared/ui/svg' export interface ChatModelCardProps extends IShortModel { accessed_models: string[] | null @@ -34,10 +34,7 @@ export function ChatModelCard({ return ( -
+
-
+
{ const theme = useAppSelector((state) => state.theme.theme) @@ -1,10 +1,13 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { IUserSetting } from '../model/types' -export const addUserSettings = async (token: string, setting: Omit): Promise => { +export const addUserSettings = async ( + token: string, + setting: Omit +): Promise => { try { const { data } = await axios.post(API_URL + '/api/users/settings/', setting, { headers: { @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { AccountType } from '../model/types' export const getAccountType = async (token: string | null | undefined): Promise => { @@ -9,11 +9,14 @@ export const getAccountType = async (token: string | null | undefined): Promise< } try { - const { data } = await axios.get>(API_URL + '/auth/account-type', { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.get>( + API_URL + '/auth/account-type', + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return data.status } catch (err) { @@ -1,6 +1,6 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { IUserSetting } from '../model/types' @@ -1,11 +1,16 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { getUpdatedSettingsLocal } from '../lib/helpers/update-setting-local' import { IUserSetting, SettingValueType } from '../model/types' -export const updateUserSettings = async (token: string, id: string, value: SettingValueType, settings: IUserSetting[]) => { +export const updateUserSettings = async ( + token: string, + id: string, + value: SettingValueType, + settings: IUserSetting[] +) => { try { const { data } = await axios.put( API_URL + `/api/users/settings/${id}`, @@ -1,4 +1,4 @@ -import { Device } from '@/src/shared/lib/types/entities' +import { Device } from '#/shared/lib/types/entities' import { IUserSetting, SettingType } from '../../model/types' @@ -12,19 +12,25 @@ interface IAddSettings { setting: Omit } -export const getUserAccountSettings = createAsyncThunk('users/getSettings', async (token: string | undefined | null) => { - if (!token) { - return initialState.state +export const getUserAccountSettings = createAsyncThunk( + 'users/getSettings', + async (token: string | undefined | null) => { + if (!token) { + return initialState.state + } + return await getUserSettings(token) } - return await getUserSettings(token) -}) +) -export const addUserAccountSettings = createAsyncThunk('users/addSettings', async ({ token, setting }: IAddSettings) => { - if (!token) { - return +export const addUserAccountSettings = createAsyncThunk( + 'users/addSettings', + async ({ token, setting }: IAddSettings) => { + if (!token) { + return + } + return await addUserSettings(token, setting) } - return await addUserSettings(token, setting) -}) +) export const updateUserAccountSettings = createAsyncThunk( 'users/updateSettings', @@ -1,4 +1,4 @@ -import { Device } from '@/src/shared/lib/types/entities' +import { Device } from '#/shared/lib/types/entities' export type AccountType = 'regular' | 'business_host' | 'business_account' @@ -2,8 +2,8 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit/src' import axios, { AxiosResponse } from 'axios' -import { loadingThunk } from '@/src/main/store/store' -import { API_URL } from '@/src/shared/lib/constants' +import { loadingThunk } from '#/app/store/store' +import { API_URL } from '#/shared/lib/constants' type UserState = { status: loadingThunk @@ -91,20 +91,14 @@ const initialState: UserState & ResponseAllInfo = { } export const getAll = async (token: string | null | undefined): Promise => { - try { - const { data } = await axios.get>( - API_URL + '/auth/me', - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) + const { data } = await axios.get>(API_URL + '/auth/me', { + headers: { + Authorization: `Bearer ${token}`, + }, + validateStatus: (status) => status === 200, + }) - return data - } catch (err) { - return initialState - } + return data } export const getAllInfo = createAsyncThunk( @@ -5,12 +5,12 @@ import { Dayjs } from 'dayjs' import { useRouter } from 'next/navigation' import { useSession } from 'next-auth/react' -import { useAppSelector } from '@/src/main/store/store' -import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal } from '@/src/shared' -import { accountApi } from '@/src/shared/api/account-endpoints' -import { API_URL } from '@/src/shared/lib/constants' -import { useShowData } from '@/src/shared/lib/hooks' -import { DateInput } from '@/src/shared/ui/date-input/date-input' +import { useAppSelector } from '#/app/store/store' +import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal } from '#/shared' +import { accountApi } from '#/shared/api/account-endpoints' +import { API_URL } from '#/shared/lib/constants' +import { useShowData } from '#/shared/lib/hooks' +import { DateInput } from '#/shared/ui/date-input/date-input' import styles from '../invite-person-in-business/ui/invite-modal.module.scss' @@ -45,7 +45,10 @@ export const ApiKeyModal = ({ setIsLoading(true) const { result } = endDate && endDate !== '' - ? await accountApi.createApiKeys({ name: title !== '' ? title : keyName, expires_at: endDate }, data?.access) + ? await accountApi.createApiKeys( + { name: title !== '' ? title : keyName, expires_at: endDate }, + data?.access + ) : await accountApi.createApiKeys({ name: title !== '' ? title : keyName }, data?.access) if (result !== null) { accountApi.getApiKeys(data?.access).then((res) => { @@ -1,14 +1,13 @@ -import { accountApi } from '@/src/shared/api/account-endpoints' +import { accountApi } from '#/shared/api/account-endpoints' export const authTelegram = async (email: any, password: any) => { const user = await accountApi.loginByEmail(email, password) if (user !== null) { - (window as any).Telegram.WebApp.sendData(user.token.access) + ;(window as any).Telegram.WebApp.sendData(user.token.access) } } - export const authTelegramYandex = async (email: any, password: any) => { const user = await accountApi.loginByEmail(email, password) @@ -1,13 +1,13 @@ import React from 'react' import { Stack } from '@mui/material' -import { CheckboxFilter } from '@/src/main/components/filters/checkbox_filter' -import { InputFilter } from '@/src/main/components/filters/input_filter' -import { SelectFilter } from '@/src/main/components/filters/select_filter' -import { Slide } from '@/src/main/components/filters/slide_filter' -import { setParams } from '@/src/main/store/model-parametres-store' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { IModelParams } from '@/src/shared/api/models/models' +import { CheckboxFilter } from '#/app/components/filters/checkbox_filter' +import { InputFilter } from '#/app/components/filters/input_filter' +import { SelectFilter } from '#/app/components/filters/select_filter' +import { Slide } from '#/app/components/filters/slide_filter' +import { setParams } from '#/app/store/model-parametres-store' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { IModelParams } from '#/shared/api/models/models' interface IProps { params: IModelParams[] @@ -23,7 +23,9 @@ export default function BotParamsMap({ params, currentVersion }: IProps) { React.useEffect(() => { if (currentVersion && params) { - const res = params.map((el) => (el.versions.length !== 0 && !el.versions.includes(currentVersion) ? null : el)) + const res = params.map((el) => + el.versions.length !== 0 && !el.versions.includes(currentVersion) ? null : el + ) if (res.every((el) => el === null)) { setNewParam({}) } @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { ResponseGetBusinessGroups, ResponseGetPersons } from '../model/types' @@ -11,7 +11,10 @@ export const addBusinessGroup = async ( token?: string ): Promise => { try { - const { data } = await axios.post<{ uid: string; title: string; token_limit: string }, AxiosResponse>( + const { data } = await axios.post< + { uid: string; title: string; token_limit: string }, + AxiosResponse + >( API_URL + '/auth/business-groups/', { title: title, @@ -1,10 +1,14 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { ResponseGetBusinessGroups, ResponseGetPersons } from '../model/types' -export const addBusinessGroupUser = async (group_id: string, email: string, token?: string): Promise => { +export const addBusinessGroupUser = async ( + group_id: string, + email: string, + token?: string +): Promise => { try { const { data } = await axios.post(API_URL + `/auth/business-groups/${group_id}/accounts/${email}/`, { headers: { @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { ResponseGetBusinessGroups } from '../model/types' @@ -1,10 +1,14 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { ResponseGetBusinessGroups, ResponseGetPersons } from '../model/types' -export const deleteBusinessGroupUser = async (group_id: string, email: string, token?: string): Promise => { +export const deleteBusinessGroupUser = async ( + group_id: string, + email: string, + token?: string +): Promise => { try { const { data } = await axios.delete(API_URL + `/auth/business-groups/${group_id}/accounts/${email}/`, { headers: { @@ -1,16 +1,19 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { ResponseGetBusinessGroups } from '../model/types' export const getBusinessGroup = async (group_id: string, token?: string): Promise => { try { - const { data } = await axios.get(API_URL + `/auth/business-groups/${group_id}/`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.get( + API_URL + `/auth/business-groups/${group_id}/`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return data } catch (err) { @@ -1,4 +1,4 @@ -import { PersonInBusiness } from '@/src/features/invite-person-in-business' +import { PersonInBusiness } from '#/features/invite-person-in-business' type Roles = 'Владелец' | 'Сотрудник' | 'Администратор' | 'Сотрудник безопасности' export const RoleSelect: Record = { @@ -11,4 +11,8 @@ export const RoleSelect: Record = { export type InviteRoles = 'Сотрудник' | 'Администратор' | 'Сотрудник безопасности' export type InviteRolesEn = 'business_host' | 'business_account' | 'business_admin' | 'business_security' -export const inviteRoles = { regular: 'Сотрудник', admin: 'Администратор', sec: 'Сотрудник безопасности' } +export const inviteRoles = { + regular: 'Сотрудник', + admin: 'Администратор', + sec: 'Сотрудник безопасности', +} @@ -20,7 +20,7 @@ margin-top: 15px; text-align: right; } - .userWrap{ + .userWrap { display: flex; align-items: flex-start; justify-content: flex-start; @@ -29,18 +29,18 @@ max-height: 100px; overflow-y: scroll; } - .accounts{ + .accounts { transition: all; transition-duration: 150ms; letter-spacing: 1px; } - .accounts:hover{ + .accounts:hover { cursor: pointer; - color: #7f7df3 + color: #7f7df3; } // настройки скрола для групп юзеров - .userWrap::-webkit-scrollbar{ + .userWrap::-webkit-scrollbar { height: 5px; width: 5px; } @@ -49,7 +49,7 @@ margin: 21px 0; } .userWrap::-webkit-scrollbar-thumb { - background-color: #D9D9D9; + background-color: #d9d9d9; border-radius: 5px; } } @@ -4,13 +4,13 @@ import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' import { Box, TextField, Typography } from '@mui/material' import { useSession } from 'next-auth/react' -import { addBusinessGroup } from '@/src/features/business-group/api/add-business-group' -import { ResponseGetBusinessGroups } from '@/src/features/business-group/model/types' -import { invitePerson } from '@/src/features/invite-person-in-business/api/invite-person' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppSelector } from '@/src/main/store/store' -import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '@/src/shared' -import { useShowData } from '@/src/shared/lib/hooks' +import { addBusinessGroup } from '#/features/business-group/api/add-business-group' +import { ResponseGetBusinessGroups } from '#/features/business-group/model/types' +import { invitePerson } from '#/features/invite-person-in-business/api/invite-person' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppSelector } from '#/app/store/store' +import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '#/shared' +import { useShowData } from '#/shared/lib/hooks' import styles from './add-business-group.module.scss' @@ -5,18 +5,18 @@ import { Box, TextField, Typography } from '@mui/material' import axios from 'axios' import { useSession } from 'next-auth/react' -import { addBusinessGroupUser } from '@/src/features/business-group/api/add-user' -import { changeBusinessGroup } from '@/src/features/business-group/api/change-group-data' -import { deleteBusinessGroupUser } from '@/src/features/business-group/api/delete-user' -import { getBusinessGroup } from '@/src/features/business-group/api/get-group-data' -import { InviteRoles, inviteRoles, InviteRolesEn, RoleSelect } from '@/src/features/business-group/lib/constants' -import { ResponseGetBusinessGroups } from '@/src/features/business-group/model/types' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppSelector } from '@/src/main/store/store' -import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import { useShowData } from '@/src/shared/lib/hooks' +import { addBusinessGroupUser } from '#/features/business-group/api/add-user' +import { changeBusinessGroup } from '#/features/business-group/api/change-group-data' +import { deleteBusinessGroupUser } from '#/features/business-group/api/delete-user' +import { getBusinessGroup } from '#/features/business-group/api/get-group-data' +import { InviteRoles, inviteRoles, InviteRolesEn, RoleSelect } from '#/features/business-group/lib/constants' +import { ResponseGetBusinessGroups } from '#/features/business-group/model/types' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppSelector } from '#/app/store/store' +import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import { useShowData } from '#/shared/lib/hooks' import styles from './add-business-group.module.scss' @@ -30,7 +30,14 @@ interface InviteModalProps extends ModalProps { current_group: ResponseGetBusinessGroups } -export const ChangeBusinessGroup: FC = ({ open, onClose, company_uid, current_group, setChanges, changes }) => { +export const ChangeBusinessGroup: FC = ({ + open, + onClose, + company_uid, + current_group, + setChanges, + changes, +}) => { const { handleSubmit, register, reset, setValue, getValues } = useForm() const [isLoading, setIsLoading] = useState(false) const [limit, setLimit] = useState('') @@ -144,7 +151,14 @@ export const ChangeBusinessGroup: FC = ({ open, onClose, compa - + В группе: @@ -161,7 +175,14 @@ export const ChangeBusinessGroup: FC = ({ open, onClose, compa - + Можно добавить: @@ -1,14 +1,17 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' export const downloadFileReq = async (start_date: string, end_date: string, token?: string): Promise => { try { - const { data } = await axios.get(API_URL + `/auth/business-security/download-report?end_date=${end_date}&start_date=${end_date}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.get( + API_URL + `/auth/business-security/download-report?end_date=${end_date}&start_date=${end_date}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return data } catch (err) { @@ -5,14 +5,20 @@ import { Dayjs } from 'dayjs' import { useRouter } from 'next/navigation' import { useSession } from 'next-auth/react' -import { ButtonGray, ButtonUI, Error, Loader, Modal } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import { useShowData } from '@/src/shared/lib/hooks' -import { DateInput } from '@/src/shared/ui/date-input/date-input' +import { ButtonGray, ButtonUI, Error, Loader, Modal } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import { useShowData } from '#/shared/lib/hooks' +import { DateInput } from '#/shared/ui/date-input/date-input' import styles from './invite-modal.module.scss' -export const DownloadModal = ({ open, setOpen }: { open: boolean; setOpen: React.Dispatch> }) => { +export const DownloadModal = ({ + open, + setOpen, +}: { + open: boolean + setOpen: React.Dispatch> +}) => { const [endDate, setEndDate] = useState() const [startDate, setStartDate] = useState() @@ -26,7 +32,9 @@ export const DownloadModal = ({ open, setOpen }: { open: boolean; setOpen: React setIsLoading(true) axios.get( API_URL + - `/auth/business-security/download-report?${startDate ? `start_date=${startDate}` : ''}${endDate ? `&end_date=${endDate}` : ''}`, + `/auth/business-security/download-report?${startDate ? `start_date=${startDate}` : ''}${ + endDate ? `&end_date=${endDate}` : '' + }`, { responseType: 'arraybuffer', headers: { Authorization: `Bearer ${session?.access}` }, @@ -0,0 +1 @@ +export * from './download-modal' \ No newline at end of file @@ -1,2 +1,3 @@ export type { PersonInBusiness } from './model/types' export type { ResponseGetPersons } from './model/types' +export * from './ui' \ No newline at end of file @@ -1,10 +1,10 @@ import React from 'react' import { encoding_for_model } from '@dqbd/tiktoken' -import { costModel } from '@/src/features/calculation-tokens-gpt/lib/constants' -import { calculatingDalle } from '@/src/features/calculation-tokens-gpt/model/calculating-dalle' -import { ImessageContext } from '@/src/shared/lib/types/types-gpt' -import { TypeModelGPT, typeModels } from '@/src/widgets/filters-gpt/lib/constants' +import { costModel } from '#/features/calculation-tokens-gpt/lib/constants' +import { calculatingDalle } from '#/features/calculation-tokens-gpt/model/calculating-dalle' +import { ImessageContext } from '#/shared/lib/types/types-gpt' +import { TypeModelGPT, typeModels } from '#/widgets/filters-gpt/lib/constants' const getFullModelTypeName = (name: string): TypeModelGPT => { return typeModels.filter((el) => el.key === name)[0].value as TypeModelGPT @@ -3,12 +3,12 @@ import { Box, Tooltip } from '@mui/material' import Typography from '@mui/material/Typography' import Image from 'next/image' -import { useCalculating } from '@/src/features/calculation-tokens-gpt/model/use-calculating' -import TooltipCalculating from '@/src/features/calculation-tokens-gpt/ui/tooltip-calculating' -import { useAppSelector } from '@/src/main/store/store' -import { baseColor } from '@/src/shared/lib/constants/colors' -import { ImessageContext } from '@/src/shared/lib/types/types-gpt' -import { TypeModelGPT } from '@/src/widgets/filters-gpt/lib/constants' +import { useCalculating } from '#/features/calculation-tokens-gpt/model/use-calculating' +import TooltipCalculating from '#/features/calculation-tokens-gpt/ui/tooltip-calculating' +import { useAppSelector } from '#/app/store/store' +import { baseColor } from '#/shared/lib/constants/colors' +import { ImessageContext } from '#/shared/lib/types/types-gpt' +import { TypeModelGPT } from '#/widgets/filters-gpt/lib/constants' interface ICalculationTokenProps { model: 'gpt' | 'sd' | 'dalle' @@ -20,70 +20,72 @@ interface ICalculationTokenProps { context?: { message: string; uid: string }[] } -export const Calculation: React.FC = memo(({ prompt, gptType, context, isAdditional, count, quality, model }) => { - const price = useCalculating(model, count, quality, prompt, gptType, isAdditional, context) +export const Calculation: React.FC = memo( + ({ prompt, gptType, context, isAdditional, count, quality, model }) => { + const price = useCalculating(model, count, quality, prompt, gptType, isAdditional, context) - const theme = useAppSelector((state) => state.theme.theme) + const theme = useAppSelector((state) => state.theme.theme) - return ( - } - componentsProps={{ - tooltip: { - sx: { - '&.MuiTooltip-tooltip': { - '&.MuiTooltip-tooltipPlacementBottom': { - marginTop: '2px', + return ( + } + componentsProps={{ + tooltip: { + sx: { + '&.MuiTooltip-tooltip': { + '&.MuiTooltip-tooltipPlacementBottom': { + marginTop: '2px', + }, + '&.MuiTooltip-tooltipPlacementTop': { + marginBottom: '7px', + }, + '&.MuiTooltip-tooltipPlacementLeft': { + marginRight: '24px', + }, }, - '&.MuiTooltip-tooltipPlacementTop': { - marginBottom: '7px', + bgcolor: theme === 'light' ? 'white' : '#4B4B4B', + borderRadius: '10px', + '& .MuiTooltip-arrow': { + color: theme === 'light' ? 'white' : '#4B4B4B', }, - '&.MuiTooltip-tooltipPlacementLeft': { - marginRight: '24px', - }, - }, - bgcolor: theme === 'light' ? 'white' : '#4B4B4B', - borderRadius: '10px', - '& .MuiTooltip-arrow': { - color: theme === 'light' ? 'white' : '#4B4B4B', + boxShadow: '0px 0px 4px rgba(0, 0, 0, 0.04), 0px 4px 32px rgba(0, 0, 0, 0.16)', }, - boxShadow: '0px 0px 4px rgba(0, 0, 0, 0.04), 0px 4px 32px rgba(0, 0, 0, 0.16)', }, - }, - }} - arrow - > - - {''} - - {price} - - - - ) -}) + {''} + + {price} + + + + ) + } +) Calculation.displayName = 'Calculation' @@ -2,8 +2,8 @@ import React from 'react' import { Box } from '@mui/material' import Typography from '@mui/material/Typography' -import { textTooltipCalculating } from '@/src/features/calculation-tokens-gpt/lib/constants' -import { useAppSelector } from '@/src/main/store/store' +import { textTooltipCalculating } from '#/features/calculation-tokens-gpt/lib/constants' +import { useAppSelector } from '#/app/store/store' const TooltipCalculating = ({ model = 'dalle' }: { model?: 'gpt' | 'dalle' | 'sd' }) => { const theme = useAppSelector((state) => state.theme.theme) @@ -29,7 +29,9 @@ const TooltipCalculating = ({ model = 'dalle' }: { model?: 'gpt' | 'dalle' | 'sd marginBottom: 0.8, }} > - Фактическое значение может немного отличаться + + Фактическое значение может немного отличаться + )} @@ -1,7 +1,7 @@ import axios, { AxiosResponse } from 'axios' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { API_URL } from '#/shared/lib/constants' import { InviteRoles, inviteRoles } from '../../invite-person-in-business/lib/constants' @@ -9,7 +9,10 @@ export const updateLimit = async (email?: string, limit?: string, token?: string try { const { data } = await axios.put<{ token_limit: string; role: string }, AxiosResponse>( API_URL + `/auth/business-host/accounts/${email}`, - { token_limit: limit, account_privileges: Object.entries(inviteRoles).find(([key, value]) => value === role)![0] }, + { + token_limit: limit, + account_privileges: Object.entries(inviteRoles).find(([key, value]) => value === role)![0], + }, { headers: { Authorization: `Bearer ${token}`, @@ -2,11 +2,11 @@ import React, { useEffect, useState } from 'react' import { Box, TextField, Typography } from '@mui/material' import { useSession } from 'next-auth/react' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { useAppSelector } from '@/src/main/store/store' -import { ButtonUI, InputStyleDark, InputStyleLight, Modal, Select } from '@/src/shared' -import styles from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import styles2 from '@/src/widgets/business-persons/ui/persons-list/persons-list.module.scss' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { useAppSelector } from '#/app/store/store' +import { ButtonUI, InputStyleDark, InputStyleLight, Modal, Select } from '#/shared' +import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import styles2 from '#/widgets/business-persons/ui/persons-list/persons-list.module.scss' import { InviteRoles, inviteRoles } from '../../invite-person-in-business/lib/constants' import { updateLimit } from '../api/set-limit' @@ -21,7 +21,15 @@ interface LimitModalProps { addList: (newUser: ResponseGetPersons, list: 'personal' | 'security') => void } -export const LimitModal: React.FC = ({ person, onClose, updateLimitProp, list, setList, addList, listType }) => { +export const LimitModal: React.FC = ({ + person, + onClose, + updateLimitProp, + list, + setList, + addList, + listType, +}) => { const theme = useAppSelector((state) => state.theme.theme) const [limit, setLimit] = useState(person?.token_limit ? Math.floor(+person.token_limit).toString() : '0') const [role, setRole] = useState('Сотрудник') @@ -33,7 +41,9 @@ export const LimitModal: React.FC = ({ person, onClose, updateL return } else if (list && res.account_type !== person?.account_type) { if ( - (res.account_type === 'business_host' || res.account_type === 'business_account' || res.account_type === 'business_admin') && + (res.account_type === 'business_host' || + res.account_type === 'business_account' || + res.account_type === 'business_admin') && listType === 'security' ) { setList(list.filter((el) => el.email !== res.email)) @@ -1,9 +1,13 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { AllowedModels } from './types' -export const changeAccess = async (token: string | undefined, value: boolean, title: string): Promise => { +export const changeAccess = async ( + token: string | undefined, + value: boolean, + title: string +): Promise => { if (!value) { try { const { data } = await axios.put<{ models: string }, AxiosResponse>( @@ -1,6 +1,6 @@ import React from 'react' -import { SwitchCustom } from '@/src/shared' +import { SwitchCustom } from '#/shared' import { changeAccess } from '../api/change-access' import { AllowedModels } from '../api/types' @@ -2,8 +2,8 @@ import React, { Dispatch, SetStateAction, useEffect, useState } from 'react' import axios from 'axios' import { useSession } from 'next-auth/react' -import { createChat, getAllChats } from '@/src/shared/api/endpoints' -import { API_URL } from '@/src/shared/lib/constants' +import { createChat, getAllChats } from '#/shared/api/endpoints' +import { API_URL } from '#/shared/lib/constants' export type Chat = { uid: string @@ -61,7 +61,9 @@ export function useChats(model: string): ChatsReturn { const removeChat = async (uid: string) => { try { setChatSetting(null) - const { status } = await axios.delete(API_URL + `/chats/${uid}/`, { headers: { Authorization: `Bearer ${data?.access}` } }) + const { status } = await axios.delete(API_URL + `/chats/${uid}/`, { + headers: { Authorization: `Bearer ${data?.access}` }, + }) if (status === 204) { let newChats = chats!.filter((el) => el.uid !== uid) @@ -1,7 +1,7 @@ import React, { ReactNode } from 'react' import { Menu, MenuItem } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' interface IProps { children: ReactNode @@ -18,7 +18,11 @@ export const ContextMenu = ({ children, pointX, pointY, clicked, handleClose }: { if (Object.values(el)[0]) { @@ -26,11 +31,14 @@ async function getStats( }) try { - const { data } = await axios.get>(API_URL + '/admin/stats' + queryParams, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.get>( + API_URL + '/admin/stats' + queryParams, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return data } catch (err) { @@ -39,7 +47,7 @@ async function getStats( } export const useGetStats = ( - token: string | null, + token: string | null | undefined, start_date?: Dayjs | null, end_date?: Dayjs | null, group_by?: string, @@ -1,6 +1,6 @@ import { Dayjs } from 'dayjs' -import { IProps } from '@/src/shared/lib/types/entities' +import { IProps } from '#/shared/lib/types/entities' export type IStatsProps = RequestStats & IProps @@ -3,10 +3,10 @@ import { KeyboardArrowLeft, KeyboardArrowRight } from '@mui/icons-material' import { Box, Button, MobileStepper, Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material' import CircularProgress from '@mui/material/CircularProgress' -import { change } from '@/src/entities/theme' -import { TranslateFields } from '@/src/features/get-admin-stats/lib/constants' -import { useAppDispatch } from '@/src/main/store/store' -import { TypographyDark, TypographyLight } from '@/src/shared/lib/constants/styles' +import { change } from '#/entities/theme' +import { TranslateFields } from '#/features/get-admin-stats/lib/constants' +import { useAppDispatch } from '#/app/store/store' +import { TypographyDark, TypographyLight } from '#/shared/lib/constants/styles' import { IStatsProps } from '../model/types' import { useGetStats } from '../model/use-get-stats/use-get-stats' @@ -26,7 +26,15 @@ export const Stats: React.FC = ({ token, device, product, group_by, return ( - + {stats.length !== 0 && loading === false && ( = ({ token, device, product, group_by, variant='text' position='static' nextButton={ - } backButton={ - @@ -53,16 +69,28 @@ export const Stats: React.FC = ({ token, device, product, group_by, ) : ( <> {stats !== undefined && Object.keys(stats).length === 0 ? ( - Данные отсутствуют + + Данные отсутствуют + ) : ( {Object.entries(stats[activeStep]).map(([key, value]) => { return ( <> - + - {TranslateFields[key as keyof typeof TranslateFields]} + { + TranslateFields[ + key as keyof typeof TranslateFields + ] + } {value} @@ -1,8 +1,8 @@ -import { getUserBalance } from '@/src/entities/balance' -import { Message, MessageSend, sendImage } from '@/src/entities/message' -import { useAppDispatch } from '@/src/main/store/store' -import { Device } from '@/src/shared/lib/types/entities' -import { formDataHelper } from '@/src/widgets/messages' +import { getUserBalance } from '#/entities/balance' +import { Message, MessageSend, sendImage } from '#/entities/message' +import { useAppDispatch } from '#/app/store/store' +import { Device } from '#/shared/lib/types/entities' +import { formDataHelper } from '#/widgets/messages' import { useSession } from 'next-auth/react' import { Dispatch, RefObject, SetStateAction, useEffect, useState } from 'react' @@ -1,4 +1,4 @@ -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' import { useState } from 'react' export function useImagesBotFilters() { @@ -1,5 +1,5 @@ -import { useShowData } from '@/src/shared' -import { MessageSend } from '@/src/shared/lib/types/model' +import { useShowData } from '#/shared' +import { MessageSend } from '#/shared/lib/types/model' import { useState, ChangeEvent } from 'react' export function useImagesUniqInput( @@ -1,8 +1,8 @@ -import { useAppSelector } from '@/src/main/store/store' -import { getImagesBySlug, Message } from '@/src/entities/message' -import { useShowData } from '@/src/shared' -import { Device } from '@/src/shared/lib/types/entities' -import { getImagesGalery } from '@/src/widgets/messages' +import { useAppSelector } from '#/app/store/store' +import { getImagesBySlug, Message } from '#/entities/message' +import { useShowData } from '#/shared' +import { Device } from '#/shared/lib/types/entities' +import { getImagesGalery } from '#/widgets/messages' import { useMediaQuery } from '@mui/material' import { useSession } from 'next-auth/react' import { useEffect, useRef, useState } from 'react' @@ -50,6 +50,7 @@ export function useImageBotPagination(deviceType: Device) { if (!data) return setLoading(true) + const { data: answer, ...response } = await getImagesBySlug( query.slug as string, data.access, @@ -62,10 +63,11 @@ export function useImageBotPagination(deviceType: Device) { if (deviceType === 'desktop') { setMessages((prev) => [...prev, ...answer]) - return + } else { + setMessages((prev) => [...answer.reverse(), ...prev]) } - setMessages((prev) => [...answer.reverse(), ...prev]) + offset.current += answer.length } const callback = async function (entries: IntersectionObserverEntry[]) { @@ -76,9 +78,13 @@ export function useImageBotPagination(deviceType: Device) { if (deviceType === 'desktop') { const active = Object.values(limits).find((item) => item.active) - if (offset.current > 0) return fetchMessages(active?.limit) + if (!active) return - fetchMessages(active?.firstLimit) + if (offset.current > 0) { + fetchMessages(active.limit) + } else { + fetchMessages(active?.firstLimit) + } } const { current } = mobileScrollContainer @@ -102,8 +108,6 @@ export function useImageBotPagination(deviceType: Device) { }) } - offset.current = offset.current + 10 - setLoading(false) }, 500) } @@ -0,0 +1,3 @@ + + +export * from './use-images-library' \ No newline at end of file @@ -0,0 +1,4 @@ +export interface ImageWithState { + image: string + state: boolean +} \ No newline at end of file @@ -0,0 +1,34 @@ +import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react' +import { ImageWithState } from './types' +import { Message } from '#/entities/message' + +export const useImagesLibrary = ( + setModal: Dispatch>, + current: string | null, + images: Message[] +) => { + const [initialCount, setInitialCount] = useState(0) + + const esc = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault() + setModal(false) + } + } + + const currentIndex = useMemo( + () => images.findIndex((item) => item.file === current), + [current] + ) + + useEffect(() => { + window.addEventListener('keydown', esc) + return () => window.removeEventListener('keydown', esc) + }, []) + + return { + initialCount, + setInitialCount, + currentIndex, + } +} @@ -0,0 +1,54 @@ +import { debounce } from 'lodash' +import { useCallback, useEffect, useState } from 'react' +import { Swiper as SwiperCore } from 'swiper' +import { ImageWithState } from './types' + +export const useLibrarySwiper = (onSlideFalse: ((...args: any) => any) | undefined, reverse: boolean) => { + const [swiper, setSwiper] = useState(null) + + + const keydown = (e: KeyboardEvent) => { + if (e.key === 'ArrowRight') { + e.preventDefault() + slideNext() + } + if (e.key === 'ArrowLeft') { + e.preventDefault() + slidePrev() + } + } + + const slidePrev = function () { + const result = swiper?.slidePrev() + + if (!result && onSlideFalse && !reverse) { + onSlideFalse() + } + } + + const slideNext = function () { + const result = swiper?.slideNext() + + if (!result && onSlideFalse && reverse) { + onSlideFalse() + } + } + + useEffect(() => { + document.addEventListener('keydown', keydown, true) + return () => { + document.removeEventListener('keydown', keydown, true) + } + }, [swiper]) + + // useEffect(() => { + // swiper?.slideTo(currentImageIndex || 0) + // }, [currentImageIndex]) + + return { + swiper, + setSwiper, + slidePrev, + slideNext, + } +} @@ -0,0 +1,189 @@ +import React, { Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react' +import Image from 'next/image' + +import styles from './modal-styles.module.scss' +import { ArrowDropDown } from '@mui/icons-material' +import { c, Loader, TooltipCustom } from '#/shared' +import { useImagesLibrary } from '../model' + +import { Swiper, SwiperSlide } from 'swiper/react' +import 'swiper/css' +import { useLibrarySwiper } from '../model/use-swiper' +import { ImageIcons, useImageIcons } from '#/widgets/messages' +import { Typography } from '@mui/material' +import { ModalImage } from './modal-image' +import { Message } from '#/entities/message' + +interface IProps { + modal: boolean + setModal: Dispatch> + current: string | null + setCurrent?: (value: string | null) => void + onSlideFalse?: (...args: any) => any + reverse?: boolean + images: Message[] +} + +export default function FullScreenModal({ + modal, + setModal, + current, + images, + onSlideFalse, + reverse = false, +}: IProps) { + const { initialCount, setInitialCount, currentIndex } = useImagesLibrary( + setModal, + current, + images + ) + + const count = useRef(0) + + const { swiper, setSwiper, slideNext, slidePrev } = useLibrarySwiper(onSlideFalse, reverse) + + const { downloadFile } = useImageIcons() + + useEffect(() => { + // debugger + if (reverse) + setTimeout(() => { + swiper?.slideNext() + count.current = images.length + }, 400) + else + setTimeout(() => { + swiper?.slideTo(images.length - count.current - 1) + count.current = images.length + }, 400) + }, [images]) + + return ( +
setModal(false)} + > +
+
+
+ { + e.stopPropagation() + if (!swiper) return + const { file, content } = images[swiper.activeIndex] + downloadFile(file as string, content) + }} + width='24' + height='24' + viewBox='0 0 15 15' + fill='none' + xmlns='http://www.w3.org/2000/svg' + > + + +
+
+ { + e.stopPropagation() + if (!swiper) return + const { file } = images[swiper.activeIndex] + window.open(file as string, '_blank') + }} + width='24' + height='24' + viewBox='0 0 15 15' + fill='none' + xmlns='http://www.w3.org/2000/svg' + > + + +
+
setModal(false)}> + + + +
+
+
+ + {modal && ( + { + console.log(swiper?.activeIndex) + }} + simulateTouch={false} + initialSlide={currentIndex} + onSwiper={(swiper) => setSwiper(swiper)} + > + {images.map((image, index) => ( + + + + ))} + + )} + +
+
+ ) +} @@ -0,0 +1,2 @@ +export { default as ImageModal } from './full-screen-modal' +export * from './modal-image' @@ -0,0 +1,23 @@ +.loader { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.image_style { + width: auto; + max-width: 70vw; + height: 100%; + position: relative; + user-select: none; + display: flex; + justify-content: center; + align-items: center; + img { + width: 100%; + height: 100%; + object-fit: contain; + user-select: none; + } +} @@ -0,0 +1,41 @@ +import { Message } from '#/entities/message' +import { Loader } from '#/shared' +import Image from 'next/image' +import React, { useEffect } from 'react' + +import styles from './modal-image.module.scss' + +interface ModalImageProps extends Message {} + +export const ModalImage = ({ file, ...image }: ModalImageProps) => { + const [state, updateImageState] = React.useState(false) + + return ( + <> + {!state && } +
+ {!file.includes('.svg') ? ( + e.stopPropagation()} + onLoadingComplete={() => { + updateImageState(true) + }} + loading='lazy' + src={file} + width={'1500'} + height={'1500'} + alt='К сожалению, изображение не загрузилось' + /> + ) : ( + e.stopPropagation()} + onLoad={() => updateImageState(true)} + alt='К сожалению, изображение не загрузилось' + className={styles.image_style} + /> + )} +
+ + ) +} @@ -0,0 +1,83 @@ +.close_block { +} +.download { +} +.actions { + position: absolute; + z-index: 105; + cursor: pointer; + right: 25px; + top: 25px; + display: flex; + align-items: center; + gap: 15px; +} + +.arrow { + position: absolute; + z-index: 1205; + cursor: pointer; + top: 50%; + + width: 50px; + height: 50px; + + background: transparent; + border: none; + outline: none; + + svg { + fill: white; + width: 50px; + height: 50px; + } + + &_left { + transform: translateY(-50%) rotate(90deg); + left: -40px; + @media screen and (max-width: 768px) { + left: -3px; + } + } + + &_right { + transform: translateY(-50%) rotate(-90deg); + right: -50px; + + @media screen and (max-width: 768px) { + right: -10px; + } + } +} + +.image_block { + position: absolute; + padding: 0 20px; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 105; + margin: auto; + width: fit-content; + height: fit-content; +} + + + +.slide { + width: 80vw !important; + display: flex !important; + align-items: center; + justify-content: center; + height: 100% !important; + max-height: unset; +} + +.swiper { + position: relative; + width: 100%; + max-width: 80vw; + max-height: 1000px; + height: 80vh; +} @@ -0,0 +1 @@ +export * from './ui' @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' import { InviteRoles, inviteRoles } from '../lib/constants' import { ResponseGetPersons } from '../model/types' @@ -1,4 +1,4 @@ -import { PersonInBusiness } from '@/src/features/invite-person-in-business' +import { PersonInBusiness } from '#/features/invite-person-in-business' type Roles = 'Владелец' | 'Сотрудник' | 'Администратор' | 'Сотрудник безопасности' export const RoleSelect: Record = { @@ -10,4 +10,8 @@ export const RoleSelect: Record = { export type InviteRoles = 'Сотрудник' | 'Администратор' | 'Сотрудник безопасности' -export const inviteRoles = { regular: 'Сотрудник', admin: 'Администратор', sec: 'Сотрудник безопасности' } +export const inviteRoles = { + regular: 'Сотрудник', + admin: 'Администратор', + sec: 'Сотрудник безопасности', +} @@ -20,7 +20,4 @@ margin-top: 15px; text-align: right; } - } - - @@ -4,9 +4,9 @@ import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' import { Box, TextField, Typography } from '@mui/material' import { useSession } from 'next-auth/react' -import { invitePerson } from '@/src/features/invite-person-in-business/api/invite-person' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppSelector } from '@/src/main/store/store' +import { invitePerson } from '#/features/invite-person-in-business/api/invite-person' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppSelector } from '#/app/store/store' import { ButtonGray, ButtonUI, @@ -19,9 +19,9 @@ import { ModalProps, onlyNumbersOption, Select, -} from '@/src/shared' -import { useShowData } from '@/src/shared/lib/hooks' -import { getBusinessGroups } from '@/src/widgets/business-persons/api/get-businessGroups' +} from '#/shared' +import { useShowData } from '#/shared/lib/hooks' +import { getBusinessGroups } from '#/widgets/business-persons/api/get-businessGroups' import { InviteRoles, inviteRoles } from '../lib/constants' import { ResponseGetBusinessGroups, ResponseGetPersons } from '../model/types' @@ -85,7 +85,11 @@ export const InviteModal: FC = ({ open, onClose, showNewPerson {/* Поле с выбором роли сотрудника */} Выберите роль - + {/* Поле с выбором группы */} Выберите бизнес-группу @@ -0,0 +1,2 @@ +export * from './pending.slice' +export { default as pendingSlice } from './pending.slice' @@ -0,0 +1,17 @@ +import { createSlice } from '@reduxjs/toolkit' + +export const pendingSlice = createSlice({ + name: 'pendingSlice', + initialState: { + userLoading: true, + }, + reducers: { + change: (state, action) => { + state.userLoading = action.payload + }, + }, +}) + +export const { change } = pendingSlice.actions + +export default pendingSlice @@ -0,0 +1 @@ +export * from './model' \ No newline at end of file @@ -1,7 +1,12 @@ import axios from 'axios' -import { FieldActivity, FieldActivitySelect, Frequency, FrequencySelect } from '@/src/features/register-business/lib/constants-step-information' -import { API_URL } from '@/src/shared/lib/constants' +import { + FieldActivity, + FieldActivitySelect, + Frequency, + FrequencySelect, +} from '#/features/register-business/lib/constants-step-information' +import { API_URL } from '#/shared/lib/constants' import { DataForCreate } from '../../model/stepper-slice' @@ -10,7 +15,9 @@ import { DataForCreateBusiness } from './types' export const createAccount = async (information: DataForCreate, token: string | null) => { const indexOfS = Object.values(FrequencySelect).indexOf(information.frequency as unknown as FrequencySelect) - const companySector = Object.entries(FieldActivitySelect).find(([key, value]) => value === information.fieldActivity) + const companySector = Object.entries(FieldActivitySelect).find( + ([key, value]) => value === information.fieldActivity + ) const data: DataForCreateBusiness = { company_name: information.companyName, @@ -12,14 +12,18 @@ export const getCompanyData = async (prompt: any) => { } try { - const { data } = await axios.post>(url, JSON.stringify({ query: prompt }), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - Authorization: 'Token ' + token, - }, - }) + const { data } = await axios.post>( + url, + JSON.stringify({ query: prompt }), + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: 'Token ' + token, + }, + } + ) return data } catch (err) { return null @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { UseFormReturn } from 'react-hook-form' -import { useAppDispatch } from '@/src/main/store/store' +import { useAppDispatch } from '#/app/store/store' import { getDataCompany } from '../stepper-slice' @@ -23,7 +23,11 @@ export const useCheckTypeCompany = (company?: string): [{ inn: RegisterOptions; } setIsIP(true) setRulesData((prev) => { - return { ...prev, inn: { ...innOptions, validate: validateInnForIP }, ogrn: { ...ogrnOptions, validate: validateOgrnForIp } } + return { + ...prev, + inn: { ...innOptions, validate: validateInnForIP }, + ogrn: { ...ogrnOptions, validate: validateOgrnForIp }, + } }) }, [company]) @@ -1,8 +1,8 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' -import { createAccount } from '@/src/features/register-business/api/create-account/create-account' -import { CompanyData } from '@/src/features/register-business/api/get-company-date/types' -import { loadingThunk, RootState } from '@/src/main/store/store' +import { createAccount } from '#/features/register-business/api/create-account/create-account' +import { CompanyData } from '#/features/register-business/api/get-company-date/types' +import { loadingThunk, RootState } from '#/app/store/store' import { getCompanyData } from '../api/get-company-date/get-company-date' import { FieldActivity, Frequency } from '../lib/constants-step-information' @@ -90,7 +90,15 @@ export const stepperSlice = createSlice({ setProcessCreate(state, action: PayloadAction) { state.loadingCreate = action.payload }, - setContact(state, action: PayloadAction<{ name: string; email: string; phone: string; job_title: string }>) { + setContact( + state, + action: PayloadAction<{ + name: string + email: string + phone: string + job_title: string + }> + ) { const { phone, email, name, job_title } = action.payload state.dataForCreate.phone = phone state.dataForCreate.email = email @@ -5,13 +5,13 @@ import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' import { Box, TextField, Typography } from '@mui/material' import { useSession } from 'next-auth/react' -import styles from '@/src/features/register-business/ui/step-information/step-information.module.scss' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { ButtonUI, Error, InputStyleDark, InputStyleLight } from '@/src/shared' -import { emailOptions } from '@/src/shared' -import { phoneOptions } from '@/src/shared/lib/constants/hook-form-options' -import { useShowData } from '@/src/shared/lib/hooks' +import styles from '#/features/register-business/ui/step-information/step-information.module.scss' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { ButtonUI, Error, InputStyleDark, InputStyleLight } from '#/shared' +import { emailOptions } from '#/shared' +import { phoneOptions } from '#/shared/lib/constants/hook-form-options' +import { useShowData } from '#/shared/lib/hooks' import { nameOptions } from '../../lib/constants-step-contact' import { createBusinessCompany, setContact } from '../../model/stepper-slice' @@ -4,13 +4,18 @@ import { FormProvider, useForm } from 'react-hook-form' import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' import { Box, TextField, Typography } from '@mui/material' -import { FieldActivity, FieldActivitySelect, Frequency, FrequencySelect } from '@/src/features/register-business/lib/constants-step-information' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { ButtonUI, InputStyleDark, InputStyleLight } from '@/src/shared' -import { Error } from '@/src/shared' -import { useShowData } from '@/src/shared/lib/hooks' -import { SelectUI } from '@/src/shared/ui/select' +import { + FieldActivity, + FieldActivitySelect, + Frequency, + FrequencySelect, +} from '#/features/register-business/lib/constants-step-information' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { ButtonUI, InputStyleDark, InputStyleLight } from '#/shared' +import { Error } from '#/shared' +import { useShowData } from '#/shared/lib/hooks' +import { SelectUI } from '#/shared/ui/select' import { setFieldActivity, setFrequency, setNumberStuff, switchNextStep } from '../../model/stepper-slice' @@ -4,13 +4,13 @@ import { useForm } from 'react-hook-form' import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' import { Autocomplete, Box, TextField, Typography } from '@mui/material' -import { CompanyData } from '@/src/features/register-business/api/get-company-date/types' -import { useCheckTypeCompany } from '@/src/features/register-business/model/legal-info/use-check-type-company' -import styles from '@/src/features/register-business/ui/step-information/step-information.module.scss' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { ButtonUI, Error, InputStyleDark, InputStyleLight } from '@/src/shared' -import { useShowData } from '@/src/shared/lib/hooks' +import { CompanyData } from '#/features/register-business/api/get-company-date/types' +import { useCheckTypeCompany } from '#/features/register-business/model/legal-info/use-check-type-company' +import styles from '#/features/register-business/ui/step-information/step-information.module.scss' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { ButtonUI, Error, InputStyleDark, InputStyleLight } from '#/shared' +import { useShowData } from '#/shared/lib/hooks' import { companyNameOptions, innOptions, ogrnOptions } from '../../lib/constatnts-step-legal-informative' import { useAutoLoadingInfo } from '../../model/legal-info/use-auto-loading-info' @@ -5,9 +5,9 @@ import StepperMui from '@mui/material/Stepper' import Image from 'next/image' import { useRouter } from 'next/router' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { Error } from '@/src/shared' -import { useShowData } from '@/src/shared/lib/hooks' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { Error } from '#/shared' +import { useShowData } from '#/shared/lib/hooks' import { steps } from '../../lib/constants' import { switchPreviousStep, switchStep } from '../../model/stepper-slice' @@ -104,7 +104,13 @@ export const Stepper = () => { ))} - {activeStep === 0 ? : activeStep === 1 ? : } + {activeStep === 0 ? ( + + ) : activeStep === 1 ? ( + + ) : ( + + )} @@ -10,16 +10,16 @@ import axios from 'axios' import Link from 'next/link' import { useRouter } from 'next/router' -import { PasswordOptions } from '@/src/features/register-by-email/lib/constants' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppSelector } from '@/src/main/store/store' -import { emailOptions } from '@/src/shared' -import { CheckBoxAgreeWithRules } from '@/src/shared' -import { Error } from '@/src/shared' -import { InputStyleDark, InputStyleLight } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants/constants' -import { useShowData } from '@/src/shared/lib/hooks' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { PasswordOptions } from '#/features/register-by-email/lib/constants' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppSelector } from '#/app/store/store' +import { emailOptions } from '#/shared' +import { CheckBoxAgreeWithRules } from '#/shared' +import { Error } from '#/shared' +import { InputStyleDark, InputStyleLight } from '#/shared' +import { API_URL } from '#/shared/lib/constants/constants' +import { useShowData } from '#/shared/lib/hooks' +import { useThemeAndDevice } from '#/shared/lib/hooks' interface IRegisterEmailFormProps { successLogin: () => void @@ -1,7 +1,7 @@ import axios, { AxiosResponse } from 'axios' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { API_URL } from '#/shared/lib/constants' export const removePerson = async (email: string, token?: string): Promise => { if (!token) { @@ -1,7 +1,7 @@ import { Dispatch, SetStateAction, useState } from 'react' import { useSession } from 'next-auth/react' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' +import { ResponseGetPersons } from '#/features/invite-person-in-business' import { removePerson } from '../api/remove' @@ -1,11 +1,17 @@ import { Box, Typography } from '@mui/material' import Image from 'next/image' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { ButtonGray, ButtonUI, Modal } from '@/src/shared' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { ButtonGray, ButtonUI, Modal } from '#/shared' import { useRemove } from '../../model/remove' -export const XMark = ({ email, setNewPersons }: { email: string; setNewPersons: (persons: ResponseGetPersons) => void }) => { +export const XMark = ({ + email, + setNewPersons, +}: { + email: string + setNewPersons: (persons: ResponseGetPersons) => void +}) => { const [open, setOpen, removeFn] = useRemove(setNewPersons, email) return ( @@ -29,7 +35,14 @@ export const XMark = ({ email, setNewPersons }: { email: string; setNewPersons: > Вы дейсвительно хотите удалить пользователя {email} ? - + { e.stopPropagation() @@ -1,7 +1,7 @@ import React from 'react' import { Box, Typography } from '@mui/material' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { useThemeAndDevice } from '#/shared/lib/hooks' interface ISuggestedMessage { title: string @@ -1,9 +1,9 @@ import React from 'react' import { Box, Stack, Typography } from '@mui/material' -import { suggestedMessagesList } from '@/src/features/send-suggested-message/lib/constants' -import SuggestedMessage from '@/src/features/send-suggested-message/ui/suggested-message' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { suggestedMessagesList } from '#/features/send-suggested-message/lib/constants' +import SuggestedMessage from '#/features/send-suggested-message/ui/suggested-message' +import { useThemeAndDevice } from '#/shared/lib/hooks' interface IProps { device: 'mobile' | 'desktop' @@ -1 +1 @@ -export { SuggestedMessages } from '@/src/features/send-suggested-message/ui/suggested-messages' +export { SuggestedMessages } from '#/features/send-suggested-message/ui/suggested-messages' @@ -12,6 +12,7 @@ export interface TitleProps { } export default function Title(props: TitleProps) { + return ( @@ -1,8 +1,8 @@ import React from 'react' import { Box, Tooltip, Typography } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' -import { baseColor } from '@/src/shared/lib/constants/colors' +import { useAppSelector } from '#/app/store/store' +import { baseColor } from '#/shared/lib/constants/colors' interface ITooltipMy { begin: () => void @@ -1,8 +1,8 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' -import { CopywriteProxy } from '@/src/domains/copywrite/proxy/copywrite-proxy' -import { Template } from '@/src/domains/copywrite/proxy/types/template' -import { Message } from '@/src/shared/lib/types/model' +import { CopywriteProxy } from '#/domains/copywrite/proxy/copywrite-proxy' +import { Template } from '#/domains/copywrite/proxy/types/template' +import { Message } from '#/shared/lib/types/model' export interface Theme { templates: Template[] | null @@ -2,11 +2,11 @@ import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/router' import { useSession } from 'next-auth/react' -import { emptyTemplate } from '@/src/domains/copywrite/lib/constants' -import { Template } from '@/src/domains/copywrite/proxy/types/template' -import { loadGeneration, loadTemplates } from '@/src/features/use-copy/copy-slice' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { Message } from '@/src/shared/lib/types/model' +import { emptyTemplate } from '#/domains/copywrite/lib/constants' +import { Template } from '#/domains/copywrite/proxy/types/template' +import { loadGeneration, loadTemplates } from '#/features/use-copy/copy-slice' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { Message } from '#/shared/lib/types/model' interface UseCopy { currentTemplate: Template | null @@ -3,12 +3,12 @@ import axios, { AxiosError, AxiosResponse } from 'axios' import { ContentState, EditorState } from 'draft-js' import { useSession } from 'next-auth/react' -import { Template } from '@/src/domains/copywrite/proxy/types/template' -import { loadGeneration } from '@/src/features/use-copy/copy-slice' -import { useAppDispatch } from '@/src/main/store/store' -import { useShowData } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import { Message, MessageSend } from '@/src/shared/lib/types/model' +import { Template } from '#/domains/copywrite/proxy/types/template' +import { loadGeneration } from '#/features/use-copy/copy-slice' +import { useAppDispatch } from '#/app/store/store' +import { useShowData } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import { Message, MessageSend } from '#/shared/lib/types/model' type Languages = 'ru' | 'en' | 'it' | 'fr' type LanguagesText = 'Русский' | 'Английский' | 'Итальянский' | 'Французский' @@ -22,7 +22,16 @@ export const langs: Record = { export const languages = { ...langs, Немецкий: 'de' } export const target_audiences = ['Вся', '18+', '21+', '30+', '14-20', '35-40'] -export const tovs = ['Нейтральный', 'Спокойный', 'Агрессивный', 'Серьезный', 'Провокационный', 'Остроумный', 'Наставнический', 'Дружелюбный'] +export const tovs = [ + 'Нейтральный', + 'Спокойный', + 'Агрессивный', + 'Серьезный', + 'Провокационный', + 'Остроумный', + 'Наставнический', + 'Дружелюбный', +] type Setting = Pick @@ -2,9 +2,9 @@ import { useEffect, useState } from 'react' import axios from 'axios' import { useSession } from 'next-auth/react' -import { dataLine as data } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import { Error } from '@/src/shared/lib/types/entities' +import { dataLine as data } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import { Error } from '#/shared/lib/types/entities' type ResponseStats = { source: string; amount: string }[] @@ -19,9 +19,12 @@ const statsColors = ['#8280FF', '#67D9A2', '#FF867E', '#7ED1FF', '#FFD700', '#FF const getStatistic = async (type: string, interval: string, token?: string): Promise => { try { - const { data } = await axios.get(API_URL + `/payments/expenses?source_strategy=${type}&interval_strategy=${interval}`, { - headers: { Authorization: `Bearer ${token}` }, - }) + const { data } = await axios.get( + API_URL + `/payments/expenses?source_strategy=${type}&interval_strategy=${interval}`, + { + headers: { Authorization: `Bearer ${token}` }, + } + ) return data } catch (e) { return { @@ -1,110 +0,0 @@ -.card { - background-color: var(--new-ui-main-color); - width: 381px; - height: 227px; - border-radius: 15px; - margin-right: 20px; - margin-top: 20px; - - cursor: pointer; - padding: 30px; - box-sizing: border-box; - - @media (max-width: 420px) { - width: 92vw; - } - - .description { - margin-top: 20px; - - .title { - font-weight: 600; - } - - .text { - color: var(--new-ui-gray-color); - font-weight: 400; - margin-top: 6px; - font-size: 15px; - } - } -} - -@keyframes fadein { - 0% { - opacity: 0; - } - 50% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.blocked { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 20px; - right: 20px; - background-color: white; - border-radius: 100px; - // padding-right: 20px; - width: 50px; - height: 50px; - transition: width 0.3s ease-in-out; - - &__icon { - } - - &:hover { - width: 60%; - padding: 12px; - // height: unset; - - .blocked__icon { - animation: fadein 0.6s ease-in-out; - } - - .blocked__text { - opacity: 1; - position: static; - animation: fadein 0.6s ease-in-out; - display: block; - } - } - - &__text { - position: absolute; - color: #ff2372; - font-weight: 500; - padding-left: 10px; - font-size: 14px; - opacity: 0; - display: none; - min-width: max-content !important; - } -} - -.locked { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.7); - border-radius: 15px; - padding-right: 20px; - - span { - color: white; - padding-top: 10px; - font-weight: 500; - } -} @@ -1,110 +0,0 @@ -import React, { useEffect, useMemo } from 'react' -import { Avatar, Box, Button, Typography } from '@mui/material' -import Image from 'next/image' -import Link from 'next/link' -import LockSvg from '@/src/assets/svg/lock.svg?react' -import BlockedSvg from '@/src/assets/svg/blocked.svg?react' - -import styles from './card.module.scss' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' -import { c } from '@/src/shared/lib/helpers' -import { IModelTag } from '@/src/entities/model-entity' -import { SvgIcon } from '@/src/shared/ui/svg' - -export type CardProps = { - title: string - icon: string - text: string - uid: string - blocked?: boolean - slug: string - tags?: IModelTag[] - accessed_models?: string[] | null -} - -const ChatCard = ({ text, icon, title, uid, slug, accessed_models, blocked, tags }: CardProps) => { - const link = useMemo(() => { - return accessed_models && !accessed_models.includes(slug) - ? '/account?scope=subscribe' - : `chat-bot/${slug}` - }, [accessed_models]) - - const { theme } = useThemeAndDevice() - - return ( - - - - - - - {title} - {text} - - - {tags && - tags.map((tag, index) => ( -
- - - - {tag.title} - -
- ))} - - {blocked ? ( -
- - - Модель недоступна - -
- ) : ( - accessed_models && - !accessed_models.includes(slug) && ( -
- - Недоступно в текущем тарифе -
- ) - )} -
- - ) -} - -export default ChatCard @@ -1,113 +0,0 @@ -import React, { useCallback } from 'react' -import { Stack } from '@mui/material' -import axios from 'axios' - -import { UniqInput } from '@/src/main/components/uniq_input' -import { useAppSelector } from '@/src/main/store/store' -import { ChatProps } from '@/src/shared/lib/types/model' -import { ChatMessagesList } from '@/src/widgets/messages/chat-messages-list' - -import 'intro.js/introjs.css' - -function Chat({ - device, - loading, - messages, - modelType, - sendMessage, - openMobileFilters, - setFile, - file, - getMessagesPagination, - input_types, - deleteMessage, - modelTitle, - currentVersion, - blocked, - deviceOs, -}: ChatProps) { - const desktop = device === 'desktop' - - const theme = useAppSelector((state) => state.theme.theme) - const [resendValue, setResendValue] = React.useState('') - - const onLoadImage = useCallback((event: React.ChangeEvent | null, file?: File) => { - if (event?.target.files) { - setFile(event.target.files[0]) - } - if (file) { - setFile(file) - } - }, []) - - const handleSetResetValue = useCallback( - (value: string) => { - if (resendValue === value) { - setResendValue((prev) => prev + ' ') - } else { - setResendValue(value) - } - }, - [resendValue] - ) - - const clearImage = () => { - setFile(null) - } - - return ( - <> - - - - - - ) -} - -Chat.displayName = 'Chat' - -export const AllChatWindow = Chat @@ -1,237 +0,0 @@ -import React, { FC, SyntheticEvent, useEffect } from 'react' -import { Divider, InputAdornment, TextField } from '@mui/material' -import Stack from '@mui/material/Stack' -import { TextFieldProps } from '@mui/material/TextField/TextField' -import Image from 'next/image' - -import { LoadImage } from '@/src/main/components/input_components/load_image' -import { SendBtn } from '@/src/main/components/input_components/send_button' -import { useAppSelector } from '@/src/main/store/store' -import { IModelInputs } from '@/src/shared/api/models/models' -import { styleInputWithoutBorderFocus } from '@/src/shared/ui/input' - -interface Input { - loading: boolean - // requestImage: () => void - wonderMe?: () => void - desktop: boolean - image?: File | null - count?: number - quality?: string - unpinImage?: () => void - imageLoad?: React.ChangeEventHandler - // openFilters: (event: React.MouseEvent) => void - sendMessage: (message: string, required: (string | null)[]) => boolean - // setInput: React.Dispatch> - // setImage: React.Dispatch> - styles: 'images' | 'chats' | 'audio' - input_types?: IModelInputs[] - blocked?: boolean - viewMobileSettings: () => void - currentVersion: string - resendValue?: string -} - -export const UniqInput: FC = ({ - image, - loading, - unpinImage, - sendMessage, - desktop, - imageLoad, - styles, - input_types, - viewMobileSettings, - currentVersion, - resendValue, - blocked, -}: Input) => { - const theme = useAppSelector((state) => state.theme.theme) - const [disabled, setDisabled] = React.useState(true) - const [required, setRequired] = React.useState<(string | null)[]>([]) - const [types, setTypes] = React.useState([]) - const [typeVersions, setTypeVersions] = React.useState({}) - const [value, setValue] = React.useState('') - - useEffect(() => { - console.log(loading) - }, [loading]) - // const [disableOnChange, setDisableOnChange] = React.useState(false) - - React.useEffect(() => { - if (input_types) { - setRequired(input_types.map((el) => (el.required ? el.type : null))) - setTypes(input_types.map((el) => el.type)) - setTypeVersions(input_types.reduce((a, v) => ({ ...a, [v.type]: v.versions }), {})) - } - }, [input_types]) - - React.useEffect(() => { - if (input_types && typeVersions && !typeVersions['text']) { - setDisabled(true) - } else if ( - typeVersions['text'] && - (typeVersions['text'].length === 0 || typeVersions['text'].includes(currentVersion)) - ) { - setDisabled(false) - } - }, [typeVersions]) - - React.useEffect(() => { - if (resendValue) { - setValue(resendValue) - } - }, [resendValue]) - - // const handlePaste = (event:ClipboardEvent) => { - // setDisableOnChange(true); - // // const pastedData = ` \n \`\`\`text \n ${event.clipboardData.getData('Text')} \n \`\`\``; - // const pastedData = event.clipboardData.getData('Text') - // setValue(value + pastedData) - // // Включаем onChange обратно после обработки вставки - // setTimeout(() => setDisableOnChange(false), 0); - // }; - - return ( - { - if (e.shiftKey && e.key === 'Enter') { - } else if (e.key === 'Enter') { - e.preventDefault() - const isSend = sendMessage(value, required) - if (isSend) { - setValue('') - if (unpinImage) unpinImage() - } - } - }} - // onPaste={(e:ClipboardEvent) => handlePaste(e)} - value={disabled || blocked ? 'Ввод текста недоступен для этой модели' : value} - onChange={(e) => { - setValue(e.target.value) - }} - InputProps={{ - endAdornment: ( - - - {typeVersions['image'] && - (typeVersions['image'].length === 0 || - typeVersions['image'].includes(currentVersion)) && ( - <> - - - - )} - {!disabled && !blocked && ( - - )} - - - ), - startAdornment: !desktop && ( - - - - - - ), - }} - /> - ) -} @@ -1 +0,0 @@ -export { Layout } from '@/src/main/layout/ui/layout' @@ -1,28 +0,0 @@ -.btn-classic { - background-color: var(--air-color); - border-radius: 13px; - text-transform: none; - color: white; - height: 43px; - font-size: 13px; - font-weight: 600; - border: none; - box-shadow: none; - &:hover { - background-color: var(--text-color-purple); - opacity: 0.9; - } - &:disabled{ - color: white; - } -} - -.btn-danger { - @extend .btn-classic; - color:#FF2372; - background-color: var(--new-ui-btn-danger-bg); - &:hover { - background-color: var(--new-ui-btn-danger-bg); - opacity: 0.9; - } -} \ No newline at end of file @@ -1,116 +0,0 @@ -.toggle_button { - padding: 0; - margin-right: 12px; - border: none; -} - -.toggle_button:hover { - background-color: transparent; -} - -.wrap_toggle_button { - border: none; - border-radius: 13px; - text-transform: none; - font-weight: 500; - font-size: 15px; - /*white-space: nowrap;*/ - color: var(--new-ui-gray-color); -} - -.wrap_toggle_button_active { - border: none; - background-color: var(--new-ui-main-color); - border-radius: 13px; - text-transform: none; - font-weight: 500; - font-size: 15px; - color: var(--text-color-main); -} - -.toggle_text_active { - font-weight: 500; - font-size: 15px; - color: var(--text-color-main); -} - -.wrap_toggle_button_active_chat { - border: none; - background-color: #f2f2ff; - border-radius: 13px; - padding: 10px 15px; - text-transform: none; -} - -.toggle_text_active_chat { - font-weight: 500; - font-size: 15px; - color: #8280ff; -} - -.chat-item { - width: 100%; - border: none; - background-color: white; - border-radius: 13px; - padding: 10px 15px; - text-transform: none; -} -.chat-item_active { - width: 100%; - border: none; - background-color: #f2f2ff; - border-radius: 13px; - padding: 10px 15px; - text-transform: none; -} - -.chat-text { - font-weight: 500; - font-size: 15px; - color: var(--new-ui-main-color); -} - -.toggle_text { - font-weight: 500; - font-size: 15px; - /*white-space: nowrap;*/ - color: var(--new-ui-gray-color); - text-transform:none; -} - -.wrap_block { - width: 100%; - border-radius: 13px; - background-color: var(--new-ui-main-color); - padding: 30px; -} - -@media (max-width: 768px) { - .main_block { - width: 100%; - display: flex; - flex-direction: column; - } - - .main_block > div { - margin: 0; - } -} - - - -.main_block { - display: flex; - align-items: flex-start; - justify-content: space-between; - margin-top: 20px; -} - -.input_title { - font-size: 15px; - margin-bottom: 5px; - margin-left: 3px; - font-weight: 400; - color: var(--new-ui-gray-color); -} @@ -3,8 +3,8 @@ import { User } from 'next-auth' import { JWT } from 'next-auth/jwt' import * as Sentry from "@sentry/browser"; -import { getAll } from '@/src/entities/user-account/model/user-type-slice' -import { API_URL } from '@/src/shared/lib/constants' +import { getAll } from '#/entities/user-account/model/user-type-slice' +import { API_URL } from '#/shared/lib/constants' import { AuthConstants } from './constants' @@ -1,76 +1,6 @@ -import * as React from 'react' -import { useEffect, useState } from 'react' -import { Box, Typography } from '@mui/material' -import axios from 'axios' -import { useSession } from 'next-auth/react' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { AudioModelsPage } from '#/views/audio' +import { getDefaultLayout } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { addToFavorites, deleteFromFavorites } from '@/src/shared/api/endpoints' -import { Card } from '@/src/shared/card/card' -import { API_URL } from '@/src/shared/lib/constants' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { IModel, IShortModel } from '@/src/entities/model-entity' +AudioModelsPage.getLayout = getDefaultLayout({ titlePage: 'Аудио' }) -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const deviceType = getTypeDevice(context) - - return { - props: { - ...(await serverSideTranslations(context.locale, ['common'])), - deviceType, - }, - } -} - -async function getImages(token?: string): Promise { - try { - const { data } = await axios.get(API_URL + '/ml_models/?category=audio', { - headers: { - Authorization: `Bearer ${token}`, - }, - }) - return data - } catch (e) { - return [] - } -} - -export const Images: React.FC = ({ deviceType }) => { - const [bots, setBots] = useState(null) - - const { data } = useSession() - - useEffect(() => { - if (!data?.access) { - return - } - - getImages(data?.access).then((res) => setBots(res)) - }, [data?.access]) - - return ( - - - Аудио - - - {bots?.map((item, idx) => { - return ( - - ) - })} - - - ) -} - -export default Images +export default AudioModelsPage @@ -1,3 +1,6 @@ -import { ChatBotPage } from '@/src/views/chat-bot' +import { ChatBotPage } from '#/views/chat-bot' +import { getDefaultLayout } from '#/widgets/layouts' + +ChatBotPage.getLayout = getDefaultLayout() export default ChatBotPage @@ -1,3 +1,6 @@ -import { ChatBotsPage } from "@/src/views/chat-bot"; +import { ChatBotsPage } from "#/views/chat-bot"; +import { getDefaultLayout } from "#/widgets/layouts"; + +ChatBotsPage.getLayout = getDefaultLayout({ titlePage: 'Чат-боты' }); export default ChatBotsPage @@ -1,30 +1,30 @@ .card { - background-color: var(--new-ui-main-color); - width: 381px; - height: 227px; - border-radius: 15px; - margin-right: 20px; - margin-top: 20px; + background-color: var(--new-ui-main-color); + width: 381px; + height: 227px; + border-radius: 15px; + margin-right: 20px; + margin-top: 20px; - cursor: pointer; - padding: 30px; - box-sizing: border-box; + cursor: pointer; + padding: 30px; + box-sizing: border-box; - @media (max-width:768px) { - width: 100%; - } - .description { - margin-top: 20px; + @media (max-width: 768px) { + width: 100%; + } + .description { + margin-top: 20px; - .title { - font-weight: 600; - } + .title { + font-weight: 600; + } - .text { - color: var(--new-ui-gray-color); - font-weight: 400; - margin-top: 6px; - font-size: 15px; - } - } + .text { + color: var(--new-ui-gray-color); + font-weight: 400; + margin-top: 6px; + font-size: 15px; + } + } } @@ -7,16 +7,16 @@ import dynamic from 'next/dynamic' import Image from 'next/image' import { getSession } from 'next-auth/react' -import { toEditorState } from '@/src/domains/copywrite/lib/helper' -import { useCopy } from '@/src/features/use-copy/use-copy' -import { languages, target_audiences, tovs, useTemplate } from '@/src/features/use-copy/use-template' -import { Layout } from '@/src/main/layout' -import { Input, Loader, TooltipCustom } from '@/src/shared' -import { api } from '@/src/shared/api/endpoints' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { Message } from '@/src/shared/lib/types/model' -import { IDalleProps } from '@/src/shared/lib/types/types-dalle' -import { SelectUI } from '@/src/shared/ui/select' +import { toEditorState } from '#/domains/copywrite/lib/helper' +import { useCopy } from '#/features/use-copy/use-copy' +import { languages, target_audiences, tovs, useTemplate } from '#/features/use-copy/use-template' +import { Layout } from '#/app/layout' +import { Input, Loader, TooltipCustom } from '#/shared' +import { api } from '#/shared/api/endpoints' +import { getTypeDevice } from '#/shared/lib/helpers' +import { Message } from '#/shared/lib/types/model' +import { IDalleProps } from '#/shared/lib/types/types-dalle' +import { SelectUI } from '#/shared/ui/select' import Title from '../../features/title/title' @@ -106,7 +106,9 @@ const Create: React.FC = ({ device, token, favorites }) => { setShowGeneration(false) } - const Editor = dynamic(() => import('react-draft-wysiwyg').then((res) => res.Editor), { ssr: false }) + const Editor = dynamic(() => import('react-draft-wysiwyg').then((res) => res.Editor), { + ssr: false, + }) const EditorWrap = (): JSX.Element | null => { if (showGeneration) { @@ -127,7 +129,13 @@ const Create: React.FC = ({ device, token, favorites }) => { > {el.content.slice(0, 60)}... - copy(el.content)} src={'/svg/copy.svg'} width={22} height={22} alt={'copy'} /> + copy(el.content)} + src={'/svg/copy.svg'} + width={22} + height={22} + alt={'copy'} + />
)) @@ -147,19 +155,31 @@ const Create: React.FC = ({ device, token, favorites }) => { } return ( - - + <Layout isLoader={currentTemplate === null} titlePage={'Копирайтинг'}> + <Title + title={showGeneration ? 'Мои генерации' : theme ? theme : 'Пустой шаблон'} + type={'Копирайтинг'} + linkBack={'/copy'} + /> <Box display={'flex'} justifyContent='space-between' flexDirection={desktop ? 'row' : 'column-reverse'} - sx={{ marginBottom: desktop ? 0 : 3, width: desktop ? '98%' : '100%', marginTop: desktop ? 3 : 0 }} + sx={{ + marginBottom: desktop ? 0 : 3, + width: desktop ? '98%' : '100%', + marginTop: desktop ? 3 : 0, + }} > <Box className='pd-30 bg-color-block border-radius-main' width='100%' position='relative'> <Box height='95%' sx={{ overflowY: 'auto' }}> <Box width='50%' display='flex' marginBottom={2}> {!showGeneration && ( - <Button fullWidth className='btn-classic' onClick={() => setShowGeneration(true)}> + <Button + fullWidth + className='btn-classic' + onClick={() => setShowGeneration(true)} + > Мои генерации </Button> )} @@ -178,17 +198,32 @@ const Create: React.FC<IDalleProps> = ({ device, token, favorites }) => { <EditorWrap /> </Box> </Box> - <Box height='auto' className='pd-30 bg-color-block border-radius-main' width='500px' marginLeft={1.5}> + <Box + height='auto' + className='pd-30 bg-color-block border-radius-main' + width='500px' + marginLeft={1.5} + > <Typography className='font-16 color-gray'>Настройки генерации</Typography> <Stack spacing={0.5} sx={{ marginTop: 2.5 }}> <Typography className='title-main-gray'>Ваш запрос</Typography> <textarea value={content} onChange={(e) => setContent(e.target.value)} /> </Stack> <Box sx={{ marginTop: 1 }}> - <SelectUI title={'Тон'} value={tov} onChange={(e) => setTov(e.target.value)} list={tovs} /> + <SelectUI + title={'Тон'} + value={tov} + onChange={(e) => setTov(e.target.value)} + list={tovs} + /> </Box> <Box sx={{ marginTop: 1 }}> - <SelectUI title={'Язык'} value={lang} onChange={(e) => setLang(e.target.value)} list={Object.keys(languages)} /> + <SelectUI + title={'Язык'} + value={lang} + onChange={(e) => setLang(e.target.value)} + list={Object.keys(languages)} + /> </Box> <Box sx={{ marginTop: 1 }}> <SelectUI @@ -208,10 +243,18 @@ const Create: React.FC<IDalleProps> = ({ device, token, favorites }) => { </Stack> <Stack spacing={0.5} sx={{ marginTop: 1 }}> <Typography className='title-main-gray'>Ресурсы (ссылки, через запятую)</Typography> - <Input value={resource_urls} onChange={(e) => setResourceUrls(e.target.value.split(','))} /> + <Input + value={resource_urls} + onChange={(e) => setResourceUrls(e.target.value.split(','))} + /> </Stack> <Stack sx={{ marginTop: 1 }}> - <Typography onClick={clearSetting} className='text' color='#FF2372 !important' sx={{ cursor: 'pointer' }}> + <Typography + onClick={clearSetting} + className='text' + color='#FF2372 !important' + sx={{ cursor: 'pointer' }} + > Сбросить настройки </Typography> </Stack> @@ -4,12 +4,12 @@ import { Typography } from '@mui/material' import Box from '@mui/material/Box' import { getSession, useSession } from 'next-auth/react' -import { loadTemplates } from '@/src/features/use-copy/copy-slice' -import { Layout } from '@/src/main/layout' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { api } from '@/src/shared/api/endpoints' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { IDalleProps } from '@/src/shared/lib/types/types-dalle' +import { loadTemplates } from '#/features/use-copy/copy-slice' +import { Layout } from '#/app/layout' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { api } from '#/shared/api/endpoints' +import { getTypeDevice } from '#/shared/lib/helpers' +import { IDalleProps } from '#/shared/lib/types/types-dalle' import Card from './card' @@ -49,12 +49,16 @@ const CopyPage: React.FC<IDalleProps> = ({ device, token, favorites }) => { }, [data?.access]) return ( - <Layout titlePage={'Копирайтинг'} device={device}> + <Layout titlePage={'Копирайтинг'}> <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Копирайтинг</Typography> <Box display={'flex'} flexDirection={desktop ? 'row' : 'column-reverse'} - sx={{ marginBottom: desktop ? 0 : 3, width: desktop ? '98%' : '100%', marginTop: desktop ? 3 : 0 }} + sx={{ + marginBottom: desktop ? 0 : 3, + width: desktop ? '98%' : '100%', + marginTop: desktop ? 3 : 0, + }} > {templates?.map((el) => { return ( @@ -1,15 +1,6 @@ -import { getDeviceOs, getTypeDevice } from '@/src/shared/lib/helpers' -import { ImageModelPage } from '@/src/views/images' +import { ImageModelPage } from '#/views/images' +import { getDefaultLayout } from '#/widgets/layouts' -export const getServerSideProps = (c: any) => { - - - return { - props: { - deviceType: getTypeDevice(c), - deviceOs: getDeviceOs(c), - }, - } -} +ImageModelPage.getLayout = getDefaultLayout() export default ImageModelPage @@ -1,3 +1,6 @@ -import { ImageModelsPage } from "@/src/views/images"; +import { ImageModelsPage } from "#/views/images"; +import { getDefaultLayout } from "#/widgets/layouts"; + +ImageModelsPage.getLayout = getDefaultLayout({ titlePage: 'Изображения' }) export default ImageModelsPage @@ -1,47 +1,7 @@ -import React, { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { Box } from '@mui/material' -import CircularProgress from '@mui/material/CircularProgress' -import { useRouter } from 'next/router' - -import { addReferral } from '@/src/entities/user-account/model/user-type-slice' +import { ReferralPage } from '#/views/referral' export async function getServerSideProps({ params }: { params: Promise<{ email: string }> }) { - const email = (await params).email - - return { - props: { - email, - }, - } -} - -function ReferralRegister({ email }: { email: string }) { - const dispatch = useDispatch() - const { push } = useRouter() - - useEffect(() => { - localStorage.setItem('referral', email) - dispatch(addReferral(email)) - push('/register') - }, []) - - return ( - <Box sx={{ width: '100vw', height: '100vh', background: '#4b4b4b', position: 'relative' }}> - <CircularProgress - size={50} - thickness={5} - sx={{ - color: '#7F7DF3', - position: 'absolute', - top: '40%', - right: 0, - left: 0, - margin: '0 auto', - }} - /> - </Box> - ) + return { props: { email: (await params).email } } } -export default ReferralRegister +export default ReferralPage @@ -1,56 +1,3 @@ -import React from 'react' -import { Box, Typography } from '@mui/material' -import Link from 'next/link' -import { useRouter } from 'next/router' +import { NotFoundPage } from '#/views/not-found' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' - -const Custom404 = () => { - const theme = useAppSelector((state) => state.theme.theme) - - const { push } = useRouter() - - React.useEffect(() => { - setTimeout(() => push('/'), 7000) - }, []) - - return ( - <Layout titlePage={'Неизвестная страница'} isAuthPage={true}> - <Box display='flex' justifyContent='center' alignItems='center' sx={{ width: '100%', height: '90vh' }}> - <Box sx={{ width: '410px', textAlign: 'center' }}> - <Typography - sx={{ - fontSize: '23px', - color: theme === 'light' ? 'dark' : 'white', - }} - > - Ошибка 404 - </Typography> - <Typography - sx={{ - fontSize: '19px', - color: theme === 'light' ? 'dark' : 'white', - marginTop: 1.5, - }} - > - К сожалению, такая страница не найдена 😢 - </Typography> - <Link href='/'> - <Typography - sx={{ - fontSize: '18px', - marginTop: 2, - color: '#7F7DF3', - }} - > - Вернуться на главную - </Typography> - </Link> - </Box> - </Box> - </Layout> - ) -} - -export default Custom404 +export default NotFoundPage @@ -1,6 +1,6 @@ -import React, { useEffect } from 'react' +import React, { ReactElement, ReactNode } from 'react' import { Provider } from 'react-redux' -import { StyledEngineProvider } from '@mui/material' +import { StyledEngineProvider, ThemeProvider } from '@mui/material' import axios from 'axios' import * as https from 'https' import type { AppProps } from 'next/app' @@ -8,22 +8,34 @@ import { Raleway } from 'next/font/google' import { SessionProvider } from 'next-auth/react' import { appWithTranslation } from 'next-i18next' -import { store } from '@/src/main/store/store' +import { store } from '#/app/store/store' import ErrorBoundary from './error-boundary' -import '@/src/main/styles/globals.css' -import '@/src/main/styles/styles-pages/system.scss' -import { register } from '../instrumentation' +import '#/app/styles/globals.css' +import '#/app/styles/styles-pages/system.scss' +import { NextPage } from 'next' +import { pingFangFont } from '#/shared/lib/constants/font/font' axios.defaults.httpsAgent = new https.Agent({ rejectUnauthorized: false, }) +axios.defaults.validateStatus = () => true + const inter = Raleway({ subsets: ['latin'] }) -function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { - register() +export type NextPageWithLayout<P = {}, DP = {}, IP = P> = NextPage<P, IP> & { + getLayout?: (page: ReactElement) => ReactNode + dynamicProps?: DP +} + +export interface AppPropsWithLayout extends AppProps { + Component: NextPageWithLayout +} + +function App({ Component, pageProps: { session, ...pageProps } }: AppPropsWithLayout) { + const getLayout = Component.getLayout ?? ((page) => page) return ( <> @@ -37,7 +49,9 @@ function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { <StyledEngineProvider injectFirst> <Provider store={store}> <SessionProvider session={session} refetchInterval={5 * 60}> - <Component {...pageProps} /> + <ThemeProvider theme={pingFangFont}> + {getLayout(<Component {...pageProps} />)} + </ThemeProvider> </SessionProvider> </Provider> </StyledEngineProvider> @@ -1,9 +1,6 @@ import { Head, Html, Main, NextScript } from 'next/document' -import { register } from '../instrumentation' -import { useEffect } from 'react' export default function Document() { - return ( <Html lang='ru'> <Head> @@ -38,6 +35,7 @@ export default function Document() { </div> </noscript> <Main /> + <div id='modal-container'></div> <NextScript /> </body> </Html> @@ -1,581 +1,6 @@ -import * as React from 'react' -import { useEffect, useMemo, useRef, useState } from 'react' -import { Avatar, Box, Button, Stack, Tab, Tabs, Typography } from '@mui/material' -import CircularProgress from '@mui/material/CircularProgress' -import axios from 'axios' -import Image from 'next/image' -import { useRouter } from 'next/router' -import { getServerSession } from 'next-auth' -import { signOut, useSession } from 'next-auth/react' +import { AccountPage } from '#/views/account' +import { getDefaultLayout } from '#/widgets/layouts' -import { getUserBalance } from '@/src/entities/balance' -import { getAllInfo, unfollowEmail } from '@/src/entities/user-account/model/user-type-slice' -import { Layout } from '@/src/main/layout' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import styles2 from '@/src/main/styles/accountTabs.module.css' -import styles from '@/src/main/styles/business.module.scss' -import { ButtonUI, Input, Loader, Modal, Success, SwitchCustom, useShowData } from '@/src/shared' -import { accountApi } from '@/src/shared/api/account-endpoints' -import { API_URL } from '@/src/shared/lib/constants/constants' -import { getAccessToken, getTypeDevice } from '@/src/shared/lib/helpers' -import { IProps } from '@/src/shared/lib/types/entities' -import { ScreenForInactive } from '@/src/widgets/business' -import BusinessHost from '@/src/widgets/business-host/business-host' -import { Info } from '@/src/widgets/business-info' -import { Subscription } from '@/src/widgets/payment/model/payment' -import { Referral } from '@/src/widgets/referral' +AccountPage.getLayout = getDefaultLayout({ titlePage: 'Аккаунт' }) -import { DownloadModal } from '../features/business-security-download/ui/download-modal' - -const scopes = [ - { title: 'Настройки', scope: 'setting' }, - { title: 'Управление оплатой', scope: 'subscribe' }, - { title: 'Корпоративный аккаунт', scope: 'business' }, - { title: 'Реферальная программа', scope: 'referral' }, -] - -export interface IPaymentsPlan { - uid: string - price: string - tokens_per_plan: string -} - -interface IAccountProps extends IProps { - plans: IPaymentsPlan[] | null - redirect?: { - destination: string - permanent: boolean - } -} - -export async function getServerSideProps(context: any): Promise<{ props: IAccountProps }> { - const device = getTypeDevice(context) - - const token = (await getAccessToken(context.req)) || null - - //@ts-ignore - const session = await getServerSession(context.req, context.res) - - const plans = token ? await accountApi.getPaymentsPlans(token) : null - - //@ts-ignore - if (!session) { - return { - //@ts-ignore - redirect: { - destination: '/', - permanent: false, - }, - } - } - return { - props: { - device, - token, - plans, - }, - } -} - -const Account: React.FC<IAccountProps> = ({ device, token, plans }) => { - const { - email, - is_subscribed_to_emails, - account_type, - first_name, - last_name, - username, - profile_picture_link, - is_social, - status: userInfoLoaded, - referral_code, - } = useAppSelector((state) => state.user) - - const { status, account_type: type } = useAppSelector((state) => state.user) - - const [name, setName] = useState('') - - const [lastName, setLastName] = useState('') - - const fileInputRef = useRef(null) - - const [loading, setLoading] = useState(false) - - const { data } = useSession() - const handleDivClick = () => { - ;(fileInputRef.current! as any).click() - } - - const dispatch = useAppDispatch() - - const { error, showError, isError } = useShowData() - - const handleFileChange = async (event: any) => { - const formData = new FormData() - formData.append('new_picture', event.target.files[0]) - - try { - setLoading(true) - await axios.put(API_URL + '/auth/reset-profile-pic', formData, { - headers: { - Authorization: `Bearer ${data?.access}`, - 'Content-Type': 'multipart/form-data', - }, - }) - dispatch(getAllInfo(data?.access)) - showError('Изображение успешно загружено!') - setLoading(false) - } catch (e) { - setLoading(false) - showError('Ошибка загрузки изображения на сервере!', true) - } - } - - const [currentPassword, setCurrentPassword] = React.useState<string>('') - - const [newPassword1, setNewPassword1] = React.useState<string>('') - - const [newPassword2, setNewPassword2] = React.useState<string>('') - - const [promocode, setPromocode] = React.useState<string>('') - - const [success, setSuccess] = React.useState('') - - const [confirmDeleteModal, setConfirmDeleteModal] = useState(false) - - const { push, query } = useRouter() - - const [scope, setScope] = useState(query['scope'] || 'setting') - - const [downloadModal, setDownloadModal] = useState<boolean>(false) - - const desktop = device === 'desktop' - - const changeScope = async (scope: string) => { - setScope(scope) - await addQueryParams(scope) - } - - async function addQueryParams(scope: string) { - await push({ - pathname: '/account', - query: { scope }, - }) - } - - useEffect(() => { - if (query['scope'] !== undefined && scope !== query['scope']) { - changeScope(query['scope'] as string) - } - }, [query]) - - useEffect(() => { - if (Object.keys(query).length === 0) addQueryParams('setting') - }, []) - - const changePassword = async () => { - if (newPassword1 !== newPassword2) { - showError('Укажите одинаковые новы пароли!', true) - return - } - const status = await accountApi.changePassword(token, newPassword1, newPassword2, currentPassword) - - if (status === 200) { - showError('Пароль успешно изменён!') - setNewPassword1('') - setNewPassword2('') - setCurrentPassword('') - setTimeout(() => setSuccess(''), 6000) - return - } - - showError('К сожалению, произошла ошибка', true) - } - - function body() { - if (type === 'regular') { - return ( - <Box width='80vw' height='60vh' display='flex' alignItems='center' justifyContent='center'> - <ScreenForInactive /> - </Box> - ) - } - if (type === 'business_account') { - return <Box className={styles.main}>Информация о корп.аккаунте доступна только владельцу и администраторам.</Box> - } - if (type === 'business_host') { - return <BusinessHost /> - } - if (type === 'business_admin') { - return <BusinessHost /> - } - if (type === 'business_security') { - return ( - <> - <DownloadModal open={downloadModal} setOpen={setDownloadModal} /> - <Box className={styles.main}> - <Info security={true} /> - <Typography className={styles.tite_text} sx={{ marginTop: '20px' }}> - Раздел 1. Запросы сотрудников по моделям - </Typography> - <Box className={styles.chapter_block}> - <ButtonUI - onClick={() => { - setDownloadModal(true) - }} - sx={{ marginTop: '20px' }} - text={'Скачать'} - /> - </Box> - </Box> - </> - ) - } - } - - const isUserDataChange = useMemo(() => name !== first_name || last_name !== lastName, [name, lastName]) - - const promocodeActivate = async () => { - if (!promocode.trim()) { - showError('Введите корректный промокод', true) - return - } - let resStatus = 404 - try { - const { status } = await axios.post( - API_URL + '/payments/promocode', - { code: promocode }, - { headers: { Authorization: `Bearer ${token}` } } - ) - resStatus = status - } catch (e) {} - - if (resStatus === 200) { - showError('Промокод успешно активирован! Токены уже зачислены!') - dispatch(getUserBalance(data?.access)) - return - } - - if (resStatus === 403) { - showError('Промокод уже был активирован!', true) - return - } - - if (resStatus === 404) { - showError('Промокод не найден!', true) - return - } - } - - async function changeUserData() { - if (!isUserDataChange) { - showError('Вы не изменили данные', true) - return - } - - try { - await axios.put( - API_URL + '/auth/user-data', - { - username: username, - email: email, - first_name: name, - last_name: lastName, - }, - { headers: { Authorization: `Bearer ${token}` } } - ) - - showError('Данные успешно изменены!') - dispatch(getAllInfo(token)) - } catch (e) {} - } - - const deleteAccount = async () => { - try { - const { status } = await axios.delete(API_URL + '/auth/remove', { - headers: { - Authorization: `Bearer ${token}`, - }, - }) - - if (status === 200) await signOut() - } catch (err) {} - } - - useEffect(() => { - setName(first_name) - setLastName(last_name) - }, [first_name, last_name]) - - return ( - <Layout titlePage={'Аккаунт'} device={device}> - <Box - sx={{ - width: '100%', - height: '100%', - marginTop: desktop ? 3 : 2, - }} - > - <Stack - direction={desktop ? 'row' : 'column'} - justifyContent='start' - alignItems={desktop ? 'flex-start' : 'center'} - spacing={2} - sx={{ overflowY: 'auto', overflowX: 'hidden' }} - > - <Stack - direction='column' - justifyContent='flex-start' - alignItems='flex-start' - spacing={2} - sx={{ - width: desktop ? '90%' : '100%', - height: '100%', - }} - > - <Typography sx={{ fontSize: 24, fontWeight: 'bold' }}>Ваш аккаунт</Typography> - <Tabs - value={scope} - variant='scrollable' - className={styles2.wrap_toggle_button} - aria-label='Platform' - scrollButtons={false} - sx={{ '& .MuiTabs-indicator': { display: 'none' } }} - > - {scopes.map((el) => { - if (!desktop && el.scope === 'business') { - return null - } - if (el.scope === 'referral' && account_type !== 'regular') { - return null - } - return ( - <Tab - disableRipple - onClick={() => changeScope(el.scope)} - key={el.title} - className={scope === el.scope ? styles2.wrap_toggle_button_active : styles2.wrap_toggle_button} - value={el.scope} - label={el.title} - /> - ) - })} - </Tabs> - {scope === 'setting' ? ( - <> - <Box className={styles2.wrap_block}> - <Typography className='title-block'>Основные</Typography> - <Box className={styles2.main_block}> - <Box sx={{ marginRight: 2 }}> - <Box onClick={handleDivClick} sx={{ cursor: 'pointer', position: 'relative' }}> - <input - type='file' - ref={fileInputRef} - style={{ display: 'none' }} - onChange={handleFileChange} - accept='image/*' - /> - <Box - sx={{ - position: 'absolute', - zIndex: 100, - top: '40px', - right: '37px', - }} - > - {!loading || !(userInfoLoaded === 'succeeded') ? ( - <Image src={'/Edit.svg'} height={30} width={30} alt={'Image change'} /> - ) : ( - <Loader /> - )} - </Box> - <Avatar - src={profile_picture_link as string} - sx={{ - width: 110, - height: 110, - backgroundColor: '#8280FF', - }} - > - {''} - </Avatar> - </Box> - </Box> - <Box width='100%' sx={{ marginLeft: '30px' }}> - <Box sx={{ marginTop: '0px' }}> - <Typography className={styles2.input_title}>Имя</Typography> - <Input - inputProps={{ maxLength: 15 }} - value={name} - onChange={(e) => setName(e.target.value)} - fullWidth - /> - </Box> - <Box sx={{ marginTop: '15px' }}> - <Typography className={styles2.input_title}>Фамилия</Typography> - <Input - inputProps={{ maxLength: 15 }} - value={lastName} - onChange={(e) => setLastName(e.target.value)} - fullWidth - /> - </Box> - <Box sx={{ marginTop: '15px' }}> - <Box display='flex' alignItems='center' justifyContent='space-between'> - <Typography className={styles2.input_title}>Email</Typography> - <Box display='flex' alignItems='center' sx={{ marginBottom: '5px' }}> - <SwitchCustom - checked={is_subscribed_to_emails} - onChange={async () => { - await dispatch(unfollowEmail(token)) - showError('Данные изменены!') - }} - /> - <Typography - sx={{ - color: '#868686', - fontSize: '15px', - marginLeft: 1, - }} - > - Отписаться от рассылки - </Typography> - </Box> - </Box> - <Input value={email} fullWidth /> - </Box> - <Box sx={{ marginTop: '15px' }}> - <Typography className={styles2.input_title}>Никнейм</Typography> - <Input value={username} fullWidth /> - </Box> - <ButtonUI onClick={changeUserData} sx={{ marginTop: '20px' }} text={'Сохранить'} /> - {/*referral_code.code.trim() && ( - <Box marginTop={1} display='flex'> - <Typography className='text'>Ваша реферальная ссылка:</Typography> -   - <TooltipCustom title='Нажмите чтобы скопировать'> - <Typography - onClick={() => navigator.clipboard.writeText(referral_code.code.trim())} - className='text' - sx={{ color: '#8280FF !important', cursor: 'pointer' }} - > - {referral_code.code} - </Typography> - </TooltipCustom> - </Box> -) */} - </Box> - </Box> - </Box> - <Stack spacing={2} className={styles2.wrap_block}> - <Typography className='title-block'>Активация промокода</Typography> - <Input - placeholder='Введите ваш промокод' - value={promocode} - onChange={(e) => setPromocode(e.target.value)} - fullWidth - /> - <Button onClick={promocodeActivate} className='btn-classic'> - Активировать - </Button> - </Stack> - {!is_social && ( - <Box className={styles2.wrap_block}> - <Typography className='title-block'>Изменить пароль</Typography> - <Box className={styles2.main_block} flexDirection='column'> - <Box sx={{ width: '100%' }}> - <Typography className={styles2.input_title}>Текущий пароль</Typography> - <Input - placeholder='Укажите текущий пароль' - value={currentPassword} - onChange={(e) => setCurrentPassword(e.target.value)} - fullWidth - /> - </Box> - <Box display='flex' justifyContent='space-between' sx={{ width: '100%', marginTop: '15px' }}> - <Box sx={{ width: '48%' }}> - <Typography className={styles2.input_title}>Новый пароль</Typography> - <Input - placeholder='Укажите новый пароль' - value={newPassword1} - onChange={(e) => setNewPassword1(e.target.value)} - fullWidth - /> - </Box> - <Box sx={{ width: '48%' }}> - <Typography className={styles2.input_title}>Подтвердить пароль</Typography> - <Input - placeholder='Укажите новый пароль ещё раз' - value={newPassword2} - onChange={(e) => setNewPassword2(e.target.value)} - fullWidth - /> - </Box> - </Box> - <ButtonUI - onClick={changePassword} - sx={{ marginTop: '20px' }} - text={'Изменить пароль'} - style={{ width: '150px' }} - /> - </Box> - </Box> - )} - <Stack spacing={2} className={styles2.wrap_block} sx={{ marginBottom: 5 }}> - <Typography className='title-block'>Удаление аккаунта</Typography> - <Typography className='text'>Удаление аккаунта приведет к потере всех настроек</Typography> - <ButtonUI - fullWidth - onClick={() => setConfirmDeleteModal(true)} - sx={{ marginTop: '20px' }} - text={'Удалить аккаунт'} - style={{ - width: '100%', - backgroundColor: 'rgba(255, 35, 114, 0.10)', - color: '#FF2372', - }} - /> - </Stack> - - <Modal open={confirmDeleteModal} onClose={() => setConfirmDeleteModal(false)}> - <Stack spacing={2}> - <Typography className='title-struct'>Удаление аккаунта</Typography> - <Typography className='text'>Вы действительно хотите удалить ваш аккаунт?</Typography> - <Button onClick={deleteAccount} fullWidth sx={{ marginTop: 3 }} className='btn-danger'> - Удалить - </Button> - </Stack> - </Modal> - </> - ) : scope === 'business' ? ( - <Box> - {status == 'pending' || type === null ? ( - <Box className={styles.loading}> - <CircularProgress - size={25} - thickness={5} - sx={{ - color: '#7F7DF3', - }} - /> - </Box> - ) : ( - <Box>{body()}</Box> - )} - </Box> - ) : scope === 'subscribe' ? ( - <Subscription device={device} /> - ) : scope === 'referral' && account_type === 'regular' ? ( - <Referral device={device} /> - ) : ( - <></> - )} - <Success message={error} open={Boolean(error)} isError={isError} /> - </Stack> - </Stack> - </Box> - </Layout> - ) -} - -export default Account +export default AccountPage @@ -1,49 +1,6 @@ -import React from 'react' -import { Stack } from '@mui/material' -import { getSession } from 'next-auth/react' +import { AdminPage } from '#/views/admin' +import { getDefaultLayout } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import { getAccessToken, getTypeDevice } from '@/src/shared/lib/helpers' -import { IProps } from '@/src/shared/lib/types/entities' -import { StatsField } from '@/src/widgets/stats-field' +AdminPage.getLayout = getDefaultLayout({ titlePage: 'Админка' }) -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const device = getTypeDevice(context) - - const token = (await getAccessToken(context.req)) || null - - const { req } = context - const session = await getSession({ req }) - - if (true) { - return { - //@ts-ignore - redirect: { - destination: '/', - permanent: false, - }, - } - } - - return { - props: { - device, - token, - }, - } -} - -const Admin: React.FC<IProps> = ({ token, device }) => { - const theme = useAppSelector((state) => state.theme.theme) - - return ( - <Layout titlePage={'Статистика'} device={device}> - <Stack direction='column' justifyContent='flex-start' alignItems='center' sx={{ width: '80vw', margin: '40px auto' }}> - <StatsField device={device} token={token} theme={theme} /> - </Stack> - </Layout> - ) -} - -export default Admin +export default AdminPage @@ -1,219 +1,6 @@ -import React, { useEffect, useState } from 'react' -import { Box, Stack, Table, TableBody, TableHead, TableRow, TextField, Typography } from '@mui/material' -import Button from '@mui/material/Button' -import TableCell from '@mui/material/TableCell' -import axios from 'axios' -import Image from 'next/image' -import Link from 'next/link' -import { useSession } from 'next-auth/react' +import { ApiKeysPage } from '#/views/api-keys' +import { getDefaultLayout } from '#/widgets/layouts' -import { getAll, ResponseAllInfo } from '@/src/entities/user-account/model/user-type-slice' -import { ApiKeyModal } from '@/src/features/api-key-modal/api-key-modal' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import { TooltipCustom } from '@/src/shared' -import { accountApi } from '@/src/shared/api/account-endpoints' -import { API_URL } from '@/src/shared/lib/constants' -import styles from '@/src/shared/styles/api-keys.module.scss' -import { InputStyleSmallDark, InputStyleSmallLight } from '@/src/shared/ui/input' +ApiKeysPage.getLayout = getDefaultLayout({ device: 'desktop', titlePage: 'API-ключи' }) -const ApiKeys: React.FC = () => { - const [keys, setKeys] = useState<Array<any> | null>(null) - const [loading, setLoading] = useState(false) - const { data, status } = useSession() - const [modal, setModal] = useState<boolean>(false) - const [userInfo, setUserInfo] = useState<ResponseAllInfo | null>() - const [disabled, setDisabled] = React.useState<boolean>(true) - - useEffect(() => { - getAll(data?.access).then((res) => setUserInfo(res)) - }, [data?.access]) - - useEffect(() => { - if (status === 'authenticated') { - setLoading(true) - accountApi.getApiKeys(data?.access).then((res) => { - setKeys(res) - setLoading(false) - }) - } - }, [status]) - - React.useEffect(() => { - if ( - userInfo?.account_type === 'business_security' || - userInfo?.account_type === 'business_account' || - status !== 'authenticated' || - loading - ) { - setDisabled(true) - } else { - setDisabled(false) - } - }, [status, userInfo, loading]) - - const deleteKey = async (name: string) => { - if (keys === null || status !== 'authenticated') { - return - } - - setLoading(true) - const result = await accountApi.deleteApiKey(name, data?.access) - if (result !== null && result === 200) { - accountApi.getApiKeys(data?.access).then((res) => { - setKeys(res) - setLoading(false) - }) - } - setLoading(false) - } - - return ( - <> - <ApiKeyModal open={modal} setOpen={setModal} keys={keys} setKeys={setKeys} /> - <Layout titlePage={'API ключи'} device={'desktop'}> - <Box style={{ marginTop: '20px', marginBottom: '20px' }}> - <Typography sx={{ fontSize: 35, fontWeight: '600' }}>API ключи</Typography> - </Box> - <Stack spacing={2} className={styles.wrap}> - <Stack spacing={2} className='pd-30 bg-color-block border-radius-main'> - <Typography className='text'> - API-ключ — это инструмент, который идентифицирует пользователя или программу, запрашивающих доступ к API платформы. - С помощью ключа можно отслеживать, кто и когда пользуется API, рассчитывать оплату. - </Typography> - <Box display='flex' alignItems='center'> - <Button onClick={() => setModal(true)} disabled={disabled} className='btn-classic' sx={{ width: '130px' }}> - Создать ключ - </Button> - <Link href='https://air-docs.readthedocs.io/' target='_blank'> - <Typography className='text' sx={{ marginLeft: '15px', color: '#8280FF !important' }}> - Документация - </Typography> - </Link> - </Box> - </Stack> - - {keys && keys.length !== 0 ? ( - <Stack spacing={1} className='pd-30 bg-color-block border-radius-main'> - <Typography className='title-block'>Мои ключи</Typography> - <Table sx={{ minWidth: 650, overflowX: 'scroll' }} aria-label='simple table'> - <TableHead> - <TableRow sx={{ '&:last-child td, &:last-child th ': { border: 0 } }}> - <TableCell align='left'>Имя</TableCell> - <TableCell align='left'>Ключ</TableCell> - <TableCell align='left'>Лимит токенов</TableCell> - <TableCell align='left'>Создан</TableCell> - <TableCell align='left'>Действителен до</TableCell> - </TableRow> - </TableHead> - <TableBody> - {keys && - keys.map((el) => { - return ( - <KeyRow - key={el.key} - keyValue={el.key} - name={el.name} - limit={el.token_limit} - created_at={el.created_at.split('T')[0]} - expires_at={el.expires_at} - deleteKey={deleteKey} - /> - ) - })} - </TableBody> - </Table> - </Stack> - ) : ( - <Box height={140} width={'100%'} display='flex' alignItems='center' justifyContent='center'> - <Box textAlign='center'> - <Typography className='title-vspomogatel'>У вас пока нет ключей 😞 </Typography> - <Typography className='title-main-gray'>Создайте первый ключ</Typography> - </Box> - </Box> - )} - </Stack> - </Layout> - </> - ) -} - -const KeyRow = (props: any) => { - const [isCopy, setIsCopy] = useState(false) - const [limit, setLimit] = useState(props.limit) - const theme = useAppSelector((state) => state.theme.theme) - const { data: session } = useSession() - - const copy = (text: string) => { - navigator.clipboard.writeText(text) - setIsCopy(true) - setTimeout(() => setIsCopy(false), 3000) - } - - useEffect(() => { - let timeout = window.setTimeout(() => { - if (limit !== props.limit) { - axios.patch( - API_URL + '/public/api-key', - { token_limit: Number(limit), name: props.name }, - { headers: { Authorization: `Bearer ${session?.access}` } } - ) - } - }, 1000) - return () => window.clearTimeout(timeout) - }, [limit]) - - return ( - <TableRow key={props.key} sx={{ '&:last-child td, &:last-child th ': { border: 0 } }}> - <TableCell component='th' align='left' scope='row'> - {props.name} - </TableCell> - <TableCell component='th' align='left' sx={{ color: '#8280FF !important' }}> - {props.keyValue} - </TableCell> - <TableCell component='th' align='center' sx={{ color: '#8280FF !important' }} width={160}> - <TextField - value={limit} - placeholder={limit === null || limit === '' ? 'Бесконечно' : ''} - type='text' - onChange={(e) => setLimit(e.target.value)} - sx={theme === 'light' ? { ...InputStyleSmallLight } : { ...InputStyleSmallDark }} - /> - </TableCell> - <TableCell component='th' align='left'> - {props.created_at.split('T')[0]} - </TableCell> - <TableCell component='th' align='left'> - {props.expires_at !== null ? props.expires_at : 'Бессрочно'} - </TableCell> - <TableCell component='th' align='left'> - <Stack direction='row' spacing={2}> - {isCopy ? ( - <Image src={'/svg/tic.svg'} width={20} height={22} alt={'copy'} /> - ) : ( - <TooltipCustom title={'Скопировать ключ'}> - <Image - onClick={() => copy(props.keyValue)} - src={'/svg/copy.svg'} - width={20} - height={20} - style={{ cursor: 'pointer' }} - alt={'copy'} - /> - </TooltipCustom> - )} - <Image - onClick={() => props.deleteKey(props.name)} - style={{ cursor: 'pointer' }} - src='/svg/main_menu/trash.svg' - width={20} - height={20} - alt='Удалить' - /> - </Stack> - </TableCell> - </TableRow> - ) -} - -export default ApiKeys +export default ApiKeysPage @@ -1,50 +1,6 @@ -import React, { useEffect } from 'react' -import { Box } from '@mui/system' -import axios from 'axios' -import { useRouter } from 'next/router' -import process from 'process' +import AuthSocial from '#/views/auth-social/ui/auth-social' +import { getDefaultLayout } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { API_URL } from '@/src/shared/lib/constants' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { Device } from '@/src/shared/lib/types/entities' - -const client_secret = process.env.NEXT_PUBLIC_DJANGO_GOOGLE_APP_CLIENT_SECRET -const client_id = process.env.NEXT_PUBLIC_DJANGO_GOOGLE_APP_CLIENT_ID - -export async function getServerSideProps(context: any): Promise<{ props: { device: Device } }> { - const device = getTypeDevice(context) - - return { - props: { - device, - }, - } -} - -async function getTokenByAPIGoogle(token: string) { - const { data } = await axios.post(API_URL + '/auth/login-social/convert-token', { - grant_type: 'convert_token', - client_id: client_id, - client_secret: client_secret, - backend: 'google-oauth2', - token: token, - }) - - return data -} - -const AuthSocial: React.FC<{ device: Device }> = ({ device }) => { - useEffect(() => { - const token = new URLSearchParams(window.location.href.replace('#', '&')).get('access_token') || '' - getTokenByAPIGoogle(token).then((res) => res) - }, []) - - return ( - <Layout titlePage={'Подтверждение авторизации...'} device={device}> - <Box></Box> - </Layout> - ) -} +AuthSocial.getLayout = getDefaultLayout({ titlePage: 'Редирект' }) export default AuthSocial @@ -0,0 +1,6 @@ +import { ChangePasswordPage } from '#/views/change-password' +import { getLayout, LayoutWithoutSideMenu } from '#/widgets/layouts' + +ChangePasswordPage.getLayout = getLayout(LayoutWithoutSideMenu, { titlePage: 'Смена пароля' }) + +export default ChangePasswordPage @@ -7,12 +7,11 @@ import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import { getRandomImage } from '@/src/pages/login' -import { API_URL } from '@/src/shared/lib/constants/constants' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { TDeviceProp } from '@/src/shared/lib/types/entities' +import { Layout } from '#/app/layout' +import { useAppSelector } from '#/app/store/store' +import { API_URL } from '#/shared/lib/constants/constants' +import { getRandomImage, getTypeDevice } from '#/shared/lib/helpers' +import { TDeviceProp } from '#/shared/lib/types/entities' export async function getServerSideProps(context: any): Promise<{ props: TDeviceProp }> { const device = getTypeDevice(context) @@ -90,7 +89,10 @@ const ChangePassword: React.FC<TDeviceProp> = ({ device }) => { </Link> {desktop && ( <Card> - <CardMedia sx={{ height: '100vh', width: '600px' }} image={`/login_images/${refImage.current}`}> + <CardMedia + sx={{ height: '100vh', width: '600px' }} + image={`/login_images/${refImage.current}`} + > <Stack direction='column-reverse' justifyContent='flex-start' @@ -183,7 +185,8 @@ const ChangePassword: React.FC<TDeviceProp> = ({ device }) => { paddingTop: 0, paddingBottom: 0, '& label': { color: '#8853FA' }, - backgroundColor: theme === 'light' ? 'transparent' : '#3D3D3D', + backgroundColor: + theme === 'light' ? 'transparent' : '#3D3D3D', input: { color: theme === 'light' ? '#272727' : '#E1E1E1', }, @@ -210,7 +213,8 @@ const ChangePassword: React.FC<TDeviceProp> = ({ device }) => { paddingTop: 0, paddingBottom: 0, '& label': { color: '#8853FA' }, - backgroundColor: theme === 'light' ? 'transparent' : '#3D3D3D', + backgroundColor: + theme === 'light' ? 'transparent' : '#3D3D3D', input: { color: theme === 'light' ? '#272727' : '#E1E1E1', }, @@ -1,83 +1,6 @@ -import React from 'react' -import { Box, Typography } from '@mui/material' -import axios from 'axios' -import Link from 'next/link' -import { useRouter } from 'next/router' +import { ConfirmPage } from '#/views/confirm' +import { getLayout, LayoutWithoutSideMenu } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { API_URL } from '@/src/shared/lib/constants/constants' +ConfirmPage.getLayout = getLayout(LayoutWithoutSideMenu, { titlePage: 'Подтверждение' }) -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const UA = context.req.headers['user-agent'] - const isMobile = Boolean(UA.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i)) - return { - props: { - device: isMobile ? 'mobile' : 'desktop', - }, - } -} - -const Confirm: React.FC = () => { - const { push, query } = useRouter() - const [success, setSuccess] = React.useState(false) - - React.useEffect(() => { - let formData: any = new FormData() - - formData.append('token', query.token) - - axios.post(API_URL + '/auth/confirm', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }) - .then(() => { - setSuccess(true) - }) - .catch(() => { - push('/') - }) - }, []) - - React.useEffect(() => { - if (success) { - setTimeout(() => push('/login'), 10000) - } - }, [success]) - - return ( - <Layout titlePage={'Подтверждение аккаунта'} isAuthPage={true}> - <Box - sx={{ - width: '100%', - height: '100vh', - alignItems: 'center', - display: 'flex', - }} - > - <Typography - sx={{ - margin: '0 auto', - textAlign: 'center', - }} - > - {' '} - {success ? ( - <> - <Typography sx={{ color: '#7F7DF3', fontSize: 17 }}> - Ваша почта подтверждена, вы будете перенаправлены на страницу авторизации - </Typography> - <Link style={{ color: '#7F7DF3', fontSize: 17 }} href={'/login'}> - Войти в аккаунт - </Link> - </> - ) : ( - 'Подтверждение...' - )} - </Typography> - </Box> - </Layout> - ) -} - -export default Confirm +export default ConfirmPage @@ -1,88 +1,7 @@ -import * as React from 'react' -import { useMemo } from 'react' -import { useCookies } from 'react-cookie' -import { Box, Stack, Typography } from '@mui/material' -import { serialize } from 'cookie' -import { GetServerSidePropsContext } from 'next' -import { useRouter } from 'next/router' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { MainPage } from '#/views/index' +import { getDefaultLayout } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import styles from '@/src/main/styles/styles-pages/index-styles.module.scss' -import { ReferralBlock } from '@/src/shared' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { getCurrentTimeAndGreeting } from '@/src/shared/lib/helpers/getCurrentDateForMainScreen' -import { TelegramBlock } from '@/src/shared/ui/telegram-block' -import { DemoInfo } from '../shared/ui/demo-info' -import { SocialMedia } from '../shared/ui/social-media-block' -import { Statistics } from '../widgets/stats/stats' +MainPage.getLayout = getDefaultLayout({ titlePage: 'AIR' }) -export async function getServerSideProps(context: GetServerSidePropsContext): Promise<{ props: any }> { - const deviceType = getTypeDevice(context) - - const { res, query } = context - - const allCookies = Object.entries(query).map((key, value) => serialize(key.toString(), value.toString())) - - res.setHeader('Set-Cookie', allCookies) - res.setHeader('Set-Cookie', '111') - return { - props: { - ...(await serverSideTranslations((context as any).locale, ['common'])), - deviceType, - }, - } -} - -export const Main: React.FC<any> = ({ deviceType }) => { - const { first_name, account_type } = useAppSelector((state) => state.user) - - const [, setCookie] = useCookies() - - const { query, push } = useRouter() - - React.useEffect(() => { - Object.entries(query).forEach(([key, value]) => setCookie(key, value)) - }, []) - - const [greeting, time] = useMemo(() => getCurrentTimeAndGreeting(first_name), [first_name]) - - return ( - <Layout titlePage={'AIR'} device={deviceType}> - <Box width='100%'> - <Box className={styles.wrap}> - <Typography className='title-main-gray'>{time}</Typography> - <Typography sx={{ fontSize: deviceType === 'desktop' ? '28px' : '24px', fontWeight: 600 }}>{greeting}</Typography> - <Box className={styles.main}> - <Statistics deviceType={deviceType} /> - <Stack className={deviceType === 'desktop' ? styles.side_desk : styles.side_mobile}> - {/*<TelegramBlock />*/} - <DemoInfo /> - {account_type === 'regular' && <ReferralBlock />} - <SocialMedia /> - <Stack className='pd-30 bg-color-block border-radius-main mt-15' spacing={2}> - <Typography className='title-block'>Полезная статья</Typography> - <Box> - <Typography className='title-vspomogatel' sx={{ fontSize: '21px !important' }}> - {' '} - Что такое токен ? - </Typography> - <Typography className='text' sx={{ marginTop: '10px' }}> - Токен — это цифровая валюта для оплаты ИИ-продуктов и услуг. Юзеры используют эту валюту, чтобы - получить либо доступ к нейросетям, либо результат генерации — текст, картинку и т.д. Еще токенами - называют единицы текста, на которых обучается нейросеть. Например, в предложении «‎Я люблю шоколад» - слова «‎Я», «‎люблю» и «шоколад» могут быть токенами. - </Typography> - </Box> - </Stack> - </Stack> - </Box> - </Box> - </Box> - </Layout> - ) -} - -export default Main +export default MainPage @@ -1,47 +1,6 @@ -import React from 'react' -import { getSession } from 'next-auth/react' +import { RegisterBusinessPage } from '#/views/register-business' +import { getLayout, LayoutWithoutSideMenu } from '#/widgets/layouts' -import { Stepper } from '@/src/features/register-business' -import { Layout } from '@/src/main/layout' -import { getAccessToken, getTypeDevice } from '@/src/shared/lib/helpers' -import { Device } from '@/src/shared/lib/types/entities' +RegisterBusinessPage.getLayout = getLayout(LayoutWithoutSideMenu, { titlePage: 'Регистрация' }) -type BusinessProps = { - device: Device - token: string | null -} -export async function getServerSideProps(context: any): Promise<{ props: BusinessProps }> { - const token = (await getAccessToken(context.req)) || null - - const device = getTypeDevice(context) - - const { req } = context - const session = await getSession({ req }) - - //@ts-ignore - if (!token) { - return { - //@ts-ignore - redirect: { - destination: '/', - permanent: false, - }, - } - } - - return { - props: { - device, - token, - }, - } -} -const Register: React.FC<BusinessProps> = ({ device }) => { - return ( - <Layout device={device} titlePage={'Регистрация'}> - <Stepper /> - </Layout> - ) -} - -export default Register +export default RegisterBusinessPage @@ -1,246 +1,6 @@ -import React, { ChangeEvent } from 'react' -import AttachFileIcon from '@mui/icons-material/AttachFile' -import CloseIcon from '@mui/icons-material/Close' -import { Box, Button, Stack, TextField, Typography } from '@mui/material' -import axios from 'axios' -import { useSession } from 'next-auth/react' +import { ReportsMobilePage } from '#/views/reports-mobile' +import { getDefaultLayout } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import { API_URL } from '@/src/shared/lib/constants/constants' +ReportsMobilePage.getLayout = getDefaultLayout({ titlePage: 'Сообщить об ошибке', device: 'mobile' }) -const styleSend = { - position: 'absolute' as 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -65%)', - width: 300, - height: 'auto', - bgcolor: 'background.paper', - borderRadius: 5, - boxShadow: 16, - p: 4, - textAlign: 'center', -} - -const styleSendDark = { - position: 'absolute' as 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -65%)', - width: 300, - height: 'auto', - bgcolor: '#2B2828', - borderRadius: 5, - boxShadow: 16, - p: 4, - textAlign: 'center', -} - -const styleMobile = { - width: '100%', - position: 'absolute' as 'absolute', - top: '35%', - left: '50%', - transform: 'translate(-50%, -50%)', - p: 4, - zIndex: 99, -} - -const ReportsMobile = () => { - const [errorMessage, setErrorMessage] = React.useState<string>('') - - const [error, setError] = React.useState(false) - - const [isSend, setIsSend] = React.useState(false) - - const [file, setFile] = React.useState<File>() - - const { data: session } = useSession() - - const theme = useAppSelector((state) => state.theme.theme) - - const email = useAppSelector((state) => state.user.email) - - const sendError = async () => { - if (errorMessage.trim() === '') { - setError(true) - setTimeout(() => setError(false), 3000) - return - } - - let formData: any = new FormData() - - formData.append('report_text', `${email}: ${errorMessage}`) //append the values with key, value pair - if (file) { - formData.append('images', file) - } - - try { - const { status } = await axios.post(API_URL + '/reports/', formData, { - headers: { - 'content-type': 'multipart/form-data ', - 'content-length': file ? `${file.size}` : '', - Authorization: `Bearer ${session?.access}`, - }, - }) - - if (status === 201) { - setErrorMessage('') - setIsSend(true) - setTimeout(() => { - setIsSend(false) - }, 4000) - } - } catch (err) { - setError(true) - setTimeout(() => setError(false), 3000) - } - } - - const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { - if (e.target.files) { - setFile(e.target.files[0]) - } - } - - return ( - <Layout titlePage={'Сообщение об ошибке'} device={'mobile'}> - {isSend ? ( - <Box sx={theme === 'light' ? styleSend : styleSendDark}> - <Typography sx={{ color: '#22B47F', fontSize: '23px' }}>Спасибо!</Typography> - <Typography sx={{ fontSize: '17px', marginTop: 2 }}> - Ваше сообщение направлено нашим специалистам. Ответ придет на почту, указанную при регистрации - </Typography> - </Box> - ) : ( - <Box sx={styleMobile}> - <Typography - variant='h6' - component='h2' - sx={{ - color: theme === 'light' ? '#5A5A5A' : '#E1E1E1', - lineHeight: '28.5px', - fontSize: '21px', - fontWeight: '600px', - }} - > - Сообщить об ошибке - </Typography> - <TextField - error={error} - id='outlined-multiline-static' - multiline - rows={4} - placeholder='Опишите детально возникшую проблему' - fullWidth - style={{ - color: theme === 'light' ? '#272727' : '#E1E1E1', - }} - onChange={(evt) => setErrorMessage(evt.target.value)} - InputProps={{ - startAdornment: ( - <> - <Button - variant='contained' - component='label' - sx={{ - position: 'absolute', - padding: 0, - bottom: 13, - left: -10, - backgroundColor: 'transparent', - border: 'none', - boxShadow: 'none', - margin: 0, - rotate: '30deg', - '&:hover': { - backgroundColor: 'transparent', - boxShadow: 'none', - }, - }} - > - <AttachFileIcon - color='info' - sx={{ - cursor: 'pointer', - color: '#868686', - width: 25, - height: 25, - }} - /> - <input onChange={handleFileChange} type='file' hidden accept='.jpg,.jpeg,.png' /> - </Button> - </> - ), - }} - sx={{ - marginTop: 2.5, - marginBottom: 1.2, - fontSize: 13, - '& label': { color: 'red' }, - backgroundColor: theme === 'light' ? 'transparent' : '#3D3D3D', - input: { - color: theme === 'light' ? '#272727' : '#E1E1E1', - }, - }} - /> - {file && ( - <Box sx={{ display: 'flex', alignItems: 'center' }}> - <Typography sx={{ fontSize: '15px' }}>Добавлено 1 изображение: {file.name}</Typography> - <CloseIcon - onClick={() => setFile(undefined)} - sx={{ - width: '18px', - height: '18px', - cursor: 'pointer', - marginLeft: '3px', - marginTop: '2px', - }} - /> - </Box> - )} - <Stack direction={'column-reverse'} justifyContent='flex-end' alignItems='flex-end' spacing={1} sx={{ marginTop: 1.5 }}> - <Button - fullWidth - sx={{ - color: theme === 'light' ? '#5A5A5A' : '#D0D0D0', - backgroundColor: theme === 'light' ? '#E8E8E8' : '#5D5A5A', - fontWeight: 500, - fontSize: '16px', - textTransform: 'none', - padding: '8px 12px', - borderRadius: '13px', - border: 'none', - cursor: 'pointer', - marginTop: 1, - }} - > - Отмена - </Button> - <Button - fullWidth - variant='contained' - onClick={sendError} - sx={{ - color: 'white', - backgroundColor: '#7F7DF3', - fontWeight: 500, - fontSize: '16px', - textTransform: 'none', - padding: '7px 12px', - borderRadius: '13px', - border: 'none', - cursor: 'pointer', - }} - > - Отправить - </Button> - </Stack> - </Box> - )} - </Layout> - ) -} - -export default ReportsMobile +export default ReportsMobilePage @@ -1,173 +1,6 @@ -import * as React from 'react' -import { useState } from 'react' -import { Box, Card, CardMedia, Link, Stack, TextField, Typography } from '@mui/material' -import Button from '@mui/material/Button' -import axios from 'axios' -import Image from 'next/image' -import Router from 'next/router' +import { ResetPage } from '#/views/reset' +import { getLayout, LayoutWithoutSideMenu } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import { getRandomImage } from '@/src/pages/login' -import { Error, Input } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants/constants' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { TDeviceProp } from '@/src/shared/lib/types/entities' +ResetPage.getLayout = getLayout(LayoutWithoutSideMenu, { titlePage: 'Сброс пароля' }) -export async function getServerSideProps(context: any): Promise<{ props: TDeviceProp }> { - const device = getTypeDevice(context) - - return { - props: { - device, - }, - } -} - -const Reset: React.FC<TDeviceProp> = ({ device }) => { - const [input_email, set_Email] = useState<string>('') - const [isSend, setIsSend] = useState(false) - const [isError, setIsError] = React.useState(false) - const sendMail = () => { - axios.post(API_URL + '/auth/update-pass', { - email: input_email, - }) - .then(function (response) { - if (response.status === 200) { - setIsSend(true) - setTimeout(() => Router.push('/login'), 10000) - } - }) - .catch(function (error) { - setIsError(true) - setTimeout(() => setIsError(false), 4000) - }) - } - - const desktop = device === 'desktop' - - const refImage = React.useRef(getRandomImage()) - - const theme = useAppSelector((state) => state.theme.theme) - - return ( - <Layout titlePage={'Восстановление пароля'} device={device} isAuthPage={true}> - <Stack - direction={desktop ? 'row' : 'column'} - alignItems='center' - justifyContent={desktop ? 'space-between' : 'center'} - sx={{ - padding: 0, - margin: desktop ? 0 : '60px auto', - backgroundColor: theme === 'light' ? 'white' : '#373737', - }} - > - {desktop && ( - <Card> - <CardMedia sx={{ height: '100vh', width: '600px' }} image={`/login_images/${refImage.current}`}> - <Stack - direction='column-reverse' - justifyContent='flex-start' - alignItems='flex-start' - spacing={2} - sx={{ - wdith: '100%', - height: '100%', - paddingBottom: 3, - paddingLeft: 3, - }} - > - <Typography - variant='body2' - sx={{ - color: '#FFFFFF', - lineHeight: '23.8px', - fontSize: '17px', - fontWeight: '400px', - }} - > - Midjourney - </Typography> - <Typography - variant='body2' - sx={{ - color: '#FFFFFF', - lineHeight: '28px', - fontSize: '20px', - fontWeight: '600px', - }} - > - by honeynek - </Typography> - </Stack> - </CardMedia> - </Card> - )} - {!isSend ? ( - <Box - sx={{ - width: '70%', - height: '100%', - marginTop: desktop ? 0 : 25, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }} - > - <Stack direction='column' justifyContent='center' alignItems='center' spacing={0} sx={{ width: '350px' }}> - <Typography sx={{ fontSize: 25 }}>Забыли пароль?</Typography> - - <Typography marginTop={1.5} sx={{ fontSize: 16 }}> - Введите адрес электронной почты, которую вы использовали для регистрации в сервисе - </Typography> - <Box textAlign='left' width='100%' marginTop={1.5}> - <Typography marginLeft={0.5} className='title-main-gray'> - Email - </Typography> - </Box> - <Input onChange={(event) => set_Email(event.target.value)} type={'email'} fullWidth /> - <Link - style={{ - position: 'absolute', - top: 45, - right: '7%', - cursor: 'pointer', - fontSize: 17, - }} - href={'/'} - > - <Image src={'/x-mark.svg'} width={18} height={18} alt={''} /> - </Link> - <Box marginTop={2} width='100%'> - <Button variant='contained' fullWidth className='btn-classic' onClick={sendMail}> - Восстановить аккаунт - </Button> - </Box> - <Error open={isError} error={'Аккаунт с такой почтой не найдено'} /> - </Stack> - </Box> - ) : ( - <Box - sx={{ - width: '40vh', - height: '100%', - marginRight: '25%', - }} - > - <Typography - sx={{ - fontSize: 21, - textAlign: 'center', - color: theme === 'light' ? '#272727' : '#E1E1E1', - }} - > - Письмо для восстановления пароля отправлено вам на почту! - </Typography> - </Box> - )} - </Stack> - </Layout> - ) -} - -export default Reset +export default ResetPage @@ -1,30 +1,6 @@ -import React from 'react' -import { Box } from '@mui/material' +import { WhisperPage } from '#/views/whisper' +import { getDefaultLayout } from '#/widgets/layouts' -import { Layout } from '@/src/main/layout' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { Device } from '@/src/shared/lib/types/entities' -import { WhisperWidget } from '@/src/widgets/whisper' +WhisperPage.getLayout = getDefaultLayout({ titlePage: 'Whisper' }) -export async function getServerSideProps(context: any): Promise<{ props: { device: Device } }> { - const device = getTypeDevice(context) - - return { - props: { - device, - }, - } -} -const Whisper: React.FC<{ device: Device }> = ({ device }) => { - return ( - <Layout titlePage={'Whisper'} device={device}> - <Box sx={{ width: '100%' }}> - <Box sx={{ width: '50%', margin: '20px auto' }}> - <WhisperWidget /> - </Box> - </Box> - </Layout> - ) -} - -export default Whisper +export default WhisperPage @@ -1,7 +1,7 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' -import { IModel, IShortModel } from '@/src/entities/model-entity' +import { API_URL } from '#/shared/lib/constants' +import { IShortModel, IModel } from '#/entities/model-entity' const model_api = { async getBots(token?: string): Promise<IShortModel[]> { @@ -4,12 +4,12 @@ import base64 from 'base64-encode-file' import { useSession } from 'next-auth/react' import process from 'process' -import { getUserBalance } from '@/src/entities/balance' -import { useAppDispatch } from '@/src/main/store/store' -import { API_URL } from '@/src/shared/lib/constants' -import { Device } from '@/src/shared/lib/types/entities' -import { IMessageRequest } from '@/src/shared/lib/types/types-gpt' -import { Message, MessageSend } from '@/src/entities/message' +import { getUserBalance } from '#/entities/balance' +import { useAppDispatch } from '#/app/store/store' +import { API_URL } from '#/shared/lib/constants' +import { Device } from '#/shared/lib/types/entities' +import { IMessageRequest } from '#/shared/lib/types/types-gpt' +import { Message, MessageSend } from '#/entities/message' const formDataHelper = (file: File, dataForSend: MessageSend<any>): FormData => { const FD = new FormData() @@ -1,8 +1,8 @@ import axios, { AxiosResponse } from 'axios' import { User } from 'next-auth' -import { API_URL } from '@/src/shared/lib/constants' -import { IOffer } from '@/src/widgets/payment/model/payment' +import { API_URL } from '#/shared/lib/constants' +import { IOffer } from '#/widgets/payment/model/payment' interface IUserBalance { current_token_balance: number @@ -61,7 +61,12 @@ export const accountApi = { } }, - async changePassword(token: string | null, password_1: string, password_2: string, current_password: string): Promise<number> { + async changePassword( + token: string | null, + password_1: string, + password_2: string, + current_password: string + ): Promise<number> { try { const { status } = await axios.put( API_URL + '/auth/reset-pass', @@ -2,10 +2,10 @@ import axios, { AxiosResponse } from 'axios' import { Session } from 'next-auth' import * as process from 'process' -import { Chat } from '@/src/shared/api/type-model-chats' -import { Error } from '@/src/shared/lib/types/entities' -import { IImagesResponse } from '@/src/shared/lib/types/types-dalle' -import { IMessageRequest, ISendMessageResponse } from '@/src/shared/lib/types/types-gpt' +import { Chat } from '#/shared/api/type-model-chats' +import { Error } from '#/shared/lib/types/entities' +import { IImagesResponse } from '#/shared/lib/types/types-dalle' +import { IMessageRequest, ISendMessageResponse } from '#/shared/lib/types/types-gpt' import { Message } from '../lib/types/model' @@ -96,12 +96,16 @@ export const api = { async sendMessageChatGPT(token: string, message: any, uid: string): Promise<ISendMessageResponse> { try { - const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>(API_URL + `/chats/${uid}/messages/`, message, { - withCredentials: true, - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>( + API_URL + `/chats/${uid}/messages/`, + message, + { + withCredentials: true, + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return { data, isError: false } } catch (err) { @@ -157,7 +161,9 @@ export const createChat = async (model: string, token?: string) => { export const getAllChats = async (model: string, token?: string): Promise<Chat[] | []> => { try { - const { data } = await axios.get(API_URL + `/chats/?model=${model}`, { headers: { Authorization: `Bearer ${token}` } }) + const { data } = await axios.get(API_URL + `/chats/?model=${model}`, { + headers: { Authorization: `Bearer ${token}` }, + }) if (data.length === 0) { const newChat = await createChat(model, token) @@ -1,109 +0,0 @@ -.card { - width: 381px; - overflow: hidden; - position: relative; - height: 290px; - border-radius: 15px; - background-color: var(--new-ui-main-color); - margin-top: 15px; - margin-right: 20px; - - @media (max-width: 420px) { - width: 92vw; - height: 300px; - } - - .description { - padding: 15px 20px; - - .title { - color: var(--new-ui-text-color); - font-weight: 600; - font-size: 21px; - } - .text { - margin-top: 15px; - font-size: 15px; - color: var(--new-ui-gray-color); - } - } -} - -@keyframes fadein { - 0% { - opacity: 0; - } - 50% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.blocked { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 20px; - right: 20px; - background-color: white; - border-radius: 100px; - // padding-right: 20px; - width: 50px; - height: 50px; - transition: width 0.3s ease-in-out; - - &__icon { - } - - &:hover { - width: 60%; - padding: 12px; - // height: unset; - - .blocked__icon { - animation: fadein 0.6s ease-in-out; - } - - .blocked__text { - opacity: 1; - position: static; - animation: fadein 0.6s ease-in-out; - display: block; - } - } - - &__text { - position: absolute; - color: #ff2372; - font-weight: 500; - padding-left: 10px; - font-size: 14px; - opacity: 0; - display: none; - min-width: max-content !important; - } -} - -.locked { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.7); - border-radius: 15px; - padding-right: 20px; - - span { - color: white; - padding-top: 10px; - font-weight: 500; - } -} @@ -1,112 +0,0 @@ -import React, { useMemo } from 'react' -import { Box, Button, Typography } from '@mui/material' -import Image from 'next/image' -import Link from 'next/link' -import LockSvg from '@/src/assets/svg/lock.svg?react' -import BlockedSvg from '@/src/assets/svg/blocked.svg?react' - -import styles from './card.module.scss' -import { useThemeAndDevice } from '../lib/hooks' -import { IModelTag } from '@/src/entities/model-entity' -import { SvgIcon } from '../ui/svg' -import { c } from '../lib/helpers' - -interface IProps { - text: string - icon: string - title: string - slug: string - uid: string - tags?: IModelTag[] - blocked: boolean - accessed_models?: string[] | null -} - -export const Card = ({ text, icon, title, blocked, slug, accessed_models, tags }: IProps) => { - const link = useMemo(() => { - return accessed_models && !accessed_models.includes(slug) - ? '/account?scope=subscribe' - : `/images/${slug}` - }, [accessed_models]) - - const { theme } = useThemeAndDevice() - - return ( - <Link href={link}> - <Box sx={{ useSelect: blocked ? 'none' : 'auto' }} className={styles.card}> - <Box - height='50%' - sx={{ - backgroundImage: `url(${icon || '/model-preview.png'})`, - backgroundSize: 'cover', - }} - display='flex' - justifyContent='space-between' - width='100%' - ></Box> - <Box className={styles.description}> - <Typography className={styles.title}>{title}</Typography> - <Typography className={styles.text}>{text}</Typography> - </Box> - - {tags && - tags.map((tag, index) => ( - <div - key={index} - className={styles.blocked} - style={{ - background: theme === 'light' ? tag.color : '#FFFFFF', - color: theme === 'light' ? '#FFFFFF' : tag.color, - marginRight: blocked ? (index + 1) * 60 : 0, - }} - > - <SvgIcon - className={styles.blocked__icon} - color={theme === 'light' ? '#FFFFFF' : tag.color} - width={23} - height={23} - url={tag.icon} - /> - - <span - className={c(styles.blocked__text)} - style={{ - color: theme === 'light' ? '#FFFFFF' : tag.color, - }} - > - {tag.title} - </span> - </div> - ))} - - {blocked ? ( - <div - className={styles.blocked} - style={{ background: theme === 'light' ? '#FF2372' : '#FFFFFF' }} - > - <BlockedSvg - className={styles.blocked__icon} - width={23} - height={23} - color={theme === 'light' ? '#FFFFFF' : '#FF2372'} - /> - <span - className={c(styles.blocked__text)} - style={{ color: theme === 'light' ? '#FFFFFF' : '#FF2372' }} - > - Модель недоступна - </span> - </div> - ) : ( - accessed_models && - !accessed_models.includes(slug) && ( - <div className={styles.locked}> - <LockSvg width={53} height={53} color='#8280FF' /> - <span>Недоступно в текущем тарифе</span> - </div> - ) - )} - </Box> - </Link> - ) -} @@ -13,7 +13,7 @@ export enum ModelPagesList { export const API_URL = process.env.NEXT_PUBLIC_API_HOST export const surpriseMePrompts = [ - 'Vintage 90\'s anime style. stylish model posing in 7/11 convenience store., sci-fi.', + "Vintage 90's anime style. stylish model posing in 7/11 convenience store., sci-fi.", 'an exhausted pepe the frog at the frontlines in the army at night with his platoon fighting, key lighting, soft lights, foggy, by steve hanks, by lisa yuskavage, by serov valentin, by tarkovsky, 8 k render, detailed, cute cartoon style, very cute adorable face', @@ -29,7 +29,7 @@ export const surpriseMePrompts = [ '8k render of a glowing reflective iridescent male marble bust, glowing, dark hues, 8k. iridescent. very iridescent. trending on behance', - 'cute adorable baby phoenix made of crystal ball with low poly eye\'s highly detailed intricated concept art trending artstation 8k', + "cute adorable baby phoenix made of crystal ball with low poly eye's highly detailed intricated concept art trending artstation 8k", 'lighthouse,retro cover, high details, intricate details, by vincent di fate, artgerm julie bell beeple, 60s, inking, vintage 60s print, screen print', @@ -1,6 +1,7 @@ import { RegisterOptions } from 'react-hook-form/dist/types/validator' -const EMAIL_REGEXP = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu +const EMAIL_REGEXP = + /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu const PHONE_REGEXP = /^((8|\+7)[\- ]?)?(\(?\d{3}\)?[\- ]?)?[\d\- ]{7,10}$/ export const emailOptions: RegisterOptions = { @@ -1,4 +1,4 @@ -import { colorDark, colorLight } from '@/src/shared/lib/constants/colors' +import { colorDark, colorLight } from '#/shared/lib/constants/colors' export const TypographyLight = { fontWeight: 600, @@ -1,6 +1,6 @@ import { GetServerSidePropsContext } from 'next' -import { Device, DeviceOs } from '@/src/shared/lib/types/entities' +import { Device, DeviceOs } from '#/shared/lib/types/entities' /** * @deprecated @@ -29,7 +29,6 @@ export const getDeviceType = (): Device => { return isMobile ? 'mobile' : 'desktop' } - /** * @deprecated */ @@ -7,7 +7,10 @@ export const useAutoLoad = (desktop: boolean): [boolean, Dispatch<SetStateAction const isMobile = desktop ? document : document.getElementById('messages-list') const scrollHandler = (e: any) => { - if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 100) { + if ( + e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < + 100 + ) { setFetching(true) } } @@ -1,4 +1,4 @@ -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' import { DeviceOs } from '../types/entities' export const useThemeAndDevice = (device?: 'desktop' | 'mobile', deviceOs?: DeviceOs) => { @@ -3,7 +3,7 @@ export type DeviceOs = 'android' | 'ios' export interface IProps { device: Device - token: string | null + token: string | null | undefined theme?: 'dark' | 'light' } @@ -1,7 +1,7 @@ import React from 'react' -import { IModelInputs } from '@/src/shared/api/models/models' -import { Device, DeviceOs } from '@/src/shared/lib/types/entities' +import { IModelInputs } from '#/shared/api/models/models' +import { Device, DeviceOs } from '#/shared/lib/types/entities' export type MessageSend<T> = { content: string @@ -33,7 +33,6 @@ export type ChatProps<T> = { deviceOs?: DeviceOs modelType: string messages: Message[] - blocked?: boolean sendMessage: (message: string, required: (string | null)[]) => boolean openMobileFilters: () => void setting?: T @@ -50,4 +49,6 @@ export type ChatProps<T> = { currentVersion: string } -export type FiltersChatProps<T> = Pick<ChatProps<T>, 'device' | 'setting'> & { resetSettings: () => void } +export type FiltersChatProps<T> = Pick<ChatProps<T>, 'device' | 'setting'> & { + resetSettings: () => void +} @@ -1,4 +1,4 @@ -import { IProps } from '@/src/shared/lib/types/entities' +import { IProps } from '#/shared/lib/types/entities' export interface IImagesResponse { question: string @@ -1,4 +1,4 @@ -import { IProps } from '@/src/shared/lib/types/entities' +import { IProps } from '#/shared/lib/types/entities' export interface IMessageResponse { question: string @@ -1,4 +1,4 @@ -import { IProps } from '@/src/shared/lib/types/entities' +import { IProps } from '#/shared/lib/types/entities' export interface IResponseSD { question: string @@ -1,3 +1,3 @@ -import { RootState } from '@/src/main/store/store' +import { RootState } from '#/app/store/store' export const balanceSelector = (state: RootState) => Math.floor(state.balance.balance) @@ -1,12 +1,11 @@ .wrap { - width: 85%; + width: 85%; - @media (max-width: 1200px) { - width: 95%; - } + @media (max-width: 1200px) { + width: 95%; + } - @media (max-width: 768px) { - width: 100%; - } - -} \ No newline at end of file + @media (max-width: 768px) { + width: 100%; + } +} @@ -1,68 +0,0 @@ -.main { - width: 100%; - display: flex; - justify-content: flex-start; - gap: 20px; - margin-bottom: 24px; - - .chatWindow { - width: 70%; - } - - .settings { - width: 25%; - } - - @media (max-width: 768px) { - width: 100%; - display: flex; - - flex-direction: column-reverse; - - .chatWindow { - width: 100%; - } - - .settings { - width: 100%; - } - } -} -.wrapModelTitle { - margin-bottom: 20px; - margin-top: 15px; - @media (max-width: 768px) { - margin: 0; - margin-top: 15px; - } - .bigTitle { - @media (max-width: 768px) { - display: none; - overflow: hidden; - } - } -} - - -.blocked { - display: flex; - align-items: center; - justify-content: center; - background-color: white; - border-radius: 100px; - padding-right: 20px; - padding: 8px 12px; - width: max-content; - - span { - color: #ff2372; - font-weight: 500; - padding-left: 10px; - font-size: 14px; - width: max-content; - } -} - -.icon{ - -} \ No newline at end of file @@ -1,82 +1,77 @@ +.wrap { + width: 100%; + background-color: var(--new-ui-main-color); + border-radius: 15px; + padding: 30px; + height: fit-content; + //margin-bottom: 15px; + max-height: 350px; + overflow-y: auto; + @media (max-width: 768px) { + padding: 10px 20px; + } -.wrap { - width: 100%; - background-color: var(--new-ui-main-color); - border-radius: 15px; - padding: 30px; - height: fit-content; - //margin-bottom: 15px; - max-height: 350px; - overflow-y: auto; + .container { + display: block; - @media (max-width:768px) { - padding: 10px 20px; - } + @media (max-width: 768px) { + display: flex; + overflow-x: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + &::-webkit-scrollbar { + width: 2px; + } + } - .container { - display: block; - - @media (max-width:768px) { - display: flex; - overflow-x: scroll; - scrollbar-width: none; - -ms-overflow-style: none; - &::-webkit-scrollbar { - width: 2px ; - } - } + .item { + padding: 0; + border: none; + width: 100%; - .item { - padding: 0; - border:none; - width: 100%; - - - &:hover { - background-color: transparent; - } - - .main { - display: flex; - width: 100%; - border:none; - justify-content: space-between; - background-color:transparent; - border-radius:13px; - padding:12px 15px; - text-transform: none; - - &_active { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - border:none; - border-radius:13px; - padding:12px 15px; - text-transform: none; - background-color:rgba(130, 128, 255, 0.15); - - @media (max-width:768px) { - margin-right: 10px; - margin-top: 10px; - width: 125px; - max-width: 130px; - overflow: hidden; - padding: 8px 12px; - justify-content: space-between; - } - } - - @media (max-width:768px) { - margin-top: 10px; - width: 100px; - padding: 0; - } - } - } - - } -} + &:hover { + background-color: transparent; + } + + .main { + display: flex; + width: 100%; + border: none; + justify-content: space-between; + background-color: transparent; + border-radius: 13px; + padding: 12px 15px; + text-transform: none; + &_active { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + border: none; + border-radius: 13px; + padding: 12px 15px; + text-transform: none; + background-color: rgba(130, 128, 255, 0.15); + + @media (max-width: 768px) { + margin-right: 10px; + margin-top: 10px; + width: 125px; + max-width: 130px; + overflow: hidden; + padding: 8px 12px; + justify-content: space-between; + } + } + + @media (max-width: 768px) { + margin-top: 10px; + width: 100px; + padding: 0; + } + } + } + } +} @@ -1,6 +1,6 @@ import React from 'react' import styles from './common-badge.module.scss' -import { c } from '@/src/shared/lib/helpers' +import { c } from '#/shared/lib/helpers' export type CommonBagdeVariant = 'primary' | 'outline' @@ -9,12 +9,7 @@ interface CommonBagdeProps extends React.HTMLAttributes<HTMLDivElement> { className?: string } -export const CommonBagde = ({ - variant = 'primary', - children, - className, - ...props -}: CommonBagdeProps) => { +export const CommonBagde = ({ variant = 'primary', children, className, ...props }: CommonBagdeProps) => { return ( <div className={c(styles.badge, styles[`badge_${variant}`], className)} {...props}> {children} @@ -1,4 +1,4 @@ -import { c } from '@/src/shared/lib/helpers' +import { c } from '#/shared/lib/helpers' import styles from './common-button.module.scss' export type ButtonVariant = 'outline' | 'primary' | 'primary-outline' @@ -8,13 +8,7 @@ export interface CommonButtonProps extends React.ButtonHTMLAttributes<HTMLButton variant?: ButtonVariant } -export const CommonButton = ({ - children, - variant = 'outline', - className, - disabled, - ...props -}: CommonButtonProps) => { +export const CommonButton = ({ children, variant = 'outline', className, disabled, ...props }: CommonButtonProps) => { return ( <button className={c( @@ -7,7 +7,7 @@ import Typography from '@mui/material/Typography' import Image from 'next/image' import Link from 'next/link' -import { translateTypeModel } from '@/src/shared' +import { translateTypeModel } from '#/shared' export const Card: React.FC<any> = ({ uid, @@ -160,9 +160,19 @@ export const Card: React.FC<any> = ({ } > {is_favourite ? ( - <Image src={'/svg/sub_menu/favourite.svg'} alt={'1'} width={25} height={25} /> + <Image + src={'/svg/sub_menu/favourite.svg'} + alt={'1'} + width={25} + height={25} + /> ) : ( - <Image src={'/svg/sub_menu/favourite_off.svg'} alt={'1'} width={25} height={25} /> + <Image + src={'/svg/sub_menu/favourite_off.svg'} + alt={'1'} + width={25} + height={25} + /> )} </Box> </Box> @@ -5,8 +5,8 @@ import CardMedia from '@mui/material/CardMedia' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' -import { translateTypeModel } from '@/src/shared' -import { Device } from '@/src/shared/lib/types/entities' +import { translateTypeModel } from '#/shared' +import { Device } from '#/shared/lib/types/entities' export const UnActiveCard: React.FC<any> = ({ title, image_link, languages, description, model_config, device }) => { const desktop = device === 'desktop' @@ -3,7 +3,7 @@ import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers' import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' import dayjs, { Dayjs } from 'dayjs' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' import styles from './date.module.scss' @@ -44,7 +44,13 @@ export const DateInput = ({ width: '100%', }} label={label ? label : 'Дата'} - value={date ? dayjs(new Date().toDateString()) : prevdate ? dayjs(new Date().toDateString()).add(-1, 'year') : null} + value={ + date + ? dayjs(new Date().toDateString()) + : prevdate + ? dayjs(new Date().toDateString()).add(-1, 'year') + : null + } onChange={(date) => { if (date?.isValid()) { setDate(JSON.stringify(date).split('"')[1].split('T')[0]) @@ -1,5 +1,5 @@ .button { - background-color: #8280FF; + background-color: #8280ff; border-radius: 13px; text-transform: none; color: white; @@ -69,7 +69,12 @@ export const dataLine = { return null } - const gradient = ctx.createLinearGradient(chartArea.left, chartArea.bottom, chartArea.left, chartArea.top) + const gradient = ctx.createLinearGradient( + chartArea.left, + chartArea.bottom, + chartArea.left, + chartArea.top + ) gradient.addColorStop(0.68, 'rgba(130, 128, 255, 0.20)') gradient.addColorStop(1, 'rgba(130, 128, 255, 0.10)') @@ -5,7 +5,13 @@ export const ArrowDownScroll: React.FC<SVGProps<SVGSVGElement>> = ({ ...props }) return ( <svg width='36' height='36' viewBox='0 0 36 36' fill='none' xmlns='http://www.w3.org/2000/svg'> <rect width='36' height='36' rx='18' fill='#7F7DF3' /> - <path d='M25 16L18 23L11 16' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' /> + <path + d='M25 16L18 23L11 16' + stroke='white' + stroke-width='2' + stroke-linecap='round' + stroke-linejoin='round' + /> </svg> ) } @@ -6,9 +6,9 @@ import Stack from '@mui/material/Stack' import { TextFieldProps } from '@mui/material/TextField/TextField' import Image from 'next/image' -import { Calculation } from '@/src/features/calculation-tokens-gpt' -import { useAppSelector } from '@/src/main/store/store' -import { styleInputWithoutBorderFocus } from '@/src/shared/ui/input' +import { Calculation } from '#/features/calculation-tokens-gpt' +import { useAppSelector } from '#/app/store/store' +import { styleInputWithoutBorderFocus } from '#/shared/ui/input' interface Input { loading: boolean @@ -104,7 +104,16 @@ export const InputImagesModels: FC<Input & TextFieldProps> = ({ const src = `/svg/chatgpt/send_message${theme === 'light' ? '' : '_dark'}.svg` - return <Image onClick={requestImage} height={26} width={26} className='pointer' src={src} alt={'Отправить запрос'} /> + return ( + <Image + onClick={requestImage} + height={26} + width={26} + className='pointer' + src={src} + alt={'Отправить запрос'} + /> + ) } return ( @@ -24,5 +24,3 @@ right: 25px; } } - - @@ -3,7 +3,7 @@ import { Box } from '@mui/material' import Dialog from '@mui/material/Dialog' import Image from 'next/image' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' import styles from './modal.module.scss' export interface ModalProps { @@ -3,7 +3,7 @@ import { TextField } from '@mui/material' import { TextFieldProps } from '@mui/material/TextField/TextField' import Image from 'next/image' -import { styleInputWithoutBorderFocus } from '@/src/shared/ui/input' +import { styleInputWithoutBorderFocus } from '#/shared/ui/input' import styles from './search.module.scss' export const Search = (props: TextFieldProps) => { @@ -12,7 +12,11 @@ export const Search = (props: TextFieldProps) => { sx={styleInputWithoutBorderFocus} className={styles.search} {...props} - InputProps={{ endAdornment: <Image src={'/search.svg'} style={{ marginRight: '15px' }} width={20} height={20} alt={'1'} /> }} + InputProps={{ + endAdornment: ( + <Image src={'/search.svg'} style={{ marginRight: '15px' }} width={20} height={20} alt={'1'} /> + ), + }} /> ) } @@ -2,8 +2,8 @@ import React from 'react' import { Typography } from '@mui/material' import Box from '@mui/material/Box' -import { useAppSelector } from '@/src/main/store/store' -import { PrettoSlider, PrettoSliderDark } from '@/src/widgets/filters-gpt/ui/filters' +import { useAppSelector } from '#/app/store/store' +import { PrettoSlider, PrettoSliderDark } from '#/widgets/filters-gpt/ui/filters' export const Slider = ({ value, @@ -2,9 +2,9 @@ import React from 'react' import { styled } from '@mui/material' import Switch, { SwitchProps } from '@mui/material/Switch' -import { useAppSelector } from '@/src/main/store/store' -import { baseColor } from '@/src/shared/lib/constants/colors' -import { pingFangFont } from '@/src/shared/lib/constants/font/font' +import { useAppSelector } from '#/app/store/store' +import { baseColor } from '#/shared/lib/constants/colors' +import { pingFangFont } from '#/shared/lib/constants/font/font' const AntSwitch = styled(Switch)(({ theme }) => ({ width: 29, @@ -1,12 +1,13 @@ import React from 'react' import { Tooltip } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' -import styles from './styles.module.scss' interface Props { title?: string children: React.ReactNode + className?: string + maxWidth?: string placement?: | 'bottom-end' | 'bottom-start' @@ -21,11 +22,17 @@ interface Props { | 'top-start' | 'top' } -export const TooltipCustom: React.FC<Props> = ({ children, title, placement }) => { +export const TooltipCustom: React.FC<Props> = ({ children, title, placement, className, maxWidth }) => { const theme = useAppSelector((state) => state.theme.theme) return ( <Tooltip + onClick={(e) => e.stopPropagation()} + PopperProps={{ + onClick(e) { + e.stopPropagation() + }, + }} componentsProps={{ tooltip: { sx: { @@ -33,6 +40,7 @@ export const TooltipCustom: React.FC<Props> = ({ children, title, placement }) = color: '#7F7DF3', fontSize: 15, borderRadius: '10px', + maxWidth: maxWidth || 'auto', padding: '10px 13px', '& .MuiTooltip-arrow': { color: theme === 'light' ? '#E8E8FA' : '#4B4B4B', @@ -41,6 +49,7 @@ export const TooltipCustom: React.FC<Props> = ({ children, title, placement }) = }, }, }} + className={className} title={title} arrow placement={placement} @@ -3,7 +3,7 @@ import { Box, Divider, Link, Menu, MenuItem, Typography } from '@mui/material' import Router from 'next/router' import { signOut, useSession } from 'next-auth/react' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' interface IAccountMenu { anchorEl: HTMLElement | null @@ -111,7 +111,12 @@ export const AccountMenu: React.FC<IAccountMenu> = ({ anchorEl, changeVisible, d fontWeight: '400', }} > - <Link target='_blank' href='https://air.fail/blog' underline='none' sx={{ color: '#868686' }}> + <Link + target='_blank' + href='https://air.fail/blog' + underline='none' + sx={{ color: '#868686' }} + > Блог </Link> </MenuItem> @@ -168,7 +173,12 @@ export const AccountMenu: React.FC<IAccountMenu> = ({ anchorEl, changeVisible, d marginTop: 1.2, }} > - <Link target='_blank' href='https://air.fail/blog' underline='none' sx={{ color: '#868686' }}> + <Link + target='_blank' + href='https://air.fail/blog' + underline='none' + sx={{ color: '#868686' }} + > Блог </Link> </Typography> @@ -6,7 +6,7 @@ import FormGroup from '@mui/material/FormGroup' import Menu from '@mui/material/Menu' import MenuItem from '@mui/material/MenuItem' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' interface IDalleFilterMenu { anchorEl: null | HTMLElement @@ -18,7 +18,15 @@ interface IDalleFilterMenu { sliderChange: Function } -export const DalleFilterMenu: React.FC<IDalleFilterMenu> = ({ anchorEl, closeMenu, format, changeFormat, numberImages, sliderChange, open }) => { +export const DalleFilterMenu: React.FC<IDalleFilterMenu> = ({ + anchorEl, + closeMenu, + format, + changeFormat, + numberImages, + sliderChange, + open, +}) => { const theme = useAppSelector((state) => state.theme.theme) return ( @@ -2,7 +2,7 @@ import { Button, Stack, Typography } from '@mui/material' import Image from 'next/image' import Link from 'next/link' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' export const DemoInfo = () => { const { payment_plan, status } = useAppSelector((state) => state.user) @@ -16,18 +16,27 @@ export const DemoInfo = () => { const DemoDanger = () => { return ( - <Stack className='pd-30 bg-color-block border-radius-main mt-15' sx={{ backgroundColor: '#FF6969 !important' }}> + <Stack + className='pd-30 bg-color-block border-radius-main mt-15' + sx={{ backgroundColor: '#FF6969 !important' }} + > <Image src={'/dagner-payment.svg'} width={45} height={45} alt='1' /> - <Typography sx={{ color: 'white', fontSize: 19, fontWeight: 600, marginTop: '30px' }}>Демо доступ</Typography> + <Typography sx={{ color: 'white', fontSize: 19, fontWeight: 600, marginTop: '30px' }}> + Демо доступ + </Typography> <Typography sx={{ color: 'white', fontSize: 15, fontWeight: 500, marginTop: '10px' }}> - Вам доступны 5 токенов, которые можно потратить на любые продукты в AIR. Чтобы продолжить пользоваться платформой вы можете купить - токены. + Вам доступны 5 токенов, которые можно потратить на любые продукты в AIR. Чтобы продолжить + пользоваться платформой вы можете купить токены. </Typography> <Link href='/account?scope=subscribe'> <Button fullWidth className='btn-danger' - sx={{ marginTop: '20px', color: 'white !important', backgroundColor: 'rgba(255, 255, 255, 0.20) !important' }} + sx={{ + marginTop: '20px', + color: 'white !important', + backgroundColor: 'rgba(255, 255, 255, 0.20) !important', + }} > Купить токены </Button> @@ -1,7 +1,7 @@ import React from 'react' import { Drawer } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' type DrawerProps = { open: boolean @@ -6,8 +6,8 @@ import { Box, Button, Modal, Stack, TextField, Typography } from '@mui/material' import axios from 'axios' import { useSession } from 'next-auth/react' -import { useAppSelector } from '@/src/main/store/store' -import { API_URL } from '@/src/shared/lib/constants' +import { useAppSelector } from '#/app/store/store' +import { API_URL } from '#/shared/lib/constants' const style = { position: 'absolute' as 'absolute', @@ -146,7 +146,8 @@ const ErrorModal: React.FC<IErrorModal> = ({ open, handleClose, device }) => { color: '#868686', }} > - Ваше сообщение направлено нашим специалистам. Ответ придет на почту, указанную при регистрации + Ваше сообщение направлено нашим специалистам. Ответ придет на почту, указанную при + регистрации </Typography> </Box> ) : ( @@ -202,7 +203,12 @@ const ErrorModal: React.FC<IErrorModal> = ({ open, handleClose, device }) => { height: 25, }} /> - <input onChange={handleFileChange} type='file' hidden accept='.jpg,.jpeg,.png' /> + <input + onChange={handleFileChange} + type='file' + hidden + accept='.jpg,.jpeg,.png' + /> </Button> </> ), @@ -225,7 +231,9 @@ const ErrorModal: React.FC<IErrorModal> = ({ open, handleClose, device }) => { alignItems: 'center', }} > - <Typography sx={{ fontSize: '15px' }}>Добавлено 1 изображение: {file.name}</Typography> + <Typography sx={{ fontSize: '15px' }}> + Добавлено 1 изображение: {file.name} + </Typography> <CloseIcon onClick={() => setFile(undefined)} sx={{ @@ -2,7 +2,7 @@ import React from 'react' import { Alert, Slide, Snackbar, Typography } from '@mui/material' import Image from 'next/image' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { useThemeAndDevice } from '#/shared/lib/hooks' export interface IError { error: string @@ -13,7 +13,15 @@ export interface IError { const ErrorIcon = () => { const { theme } = useThemeAndDevice() - return <Image src={`/error2${theme === 'light' ? '_white' : ''}.svg`} alt={''} width={20} height={20} style={{ marginTop: 2.5 }} /> + return ( + <Image + src={`/error2${theme === 'light' ? '_white' : ''}.svg`} + alt={''} + width={20} + height={20} + style={{ marginTop: 2.5 }} + /> + ) } export const Error: React.FC<IError> = ({ error, handleClose, open }) => { @@ -21,7 +29,12 @@ export const Error: React.FC<IError> = ({ error, handleClose, open }) => { return ( <Slide direction='down' in={open} mountOnEnter unmountOnExit> - <Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} autoHideDuration={6000} onClose={handleClose}> + <Snackbar + anchorOrigin={{ vertical: 'top', horizontal: 'right' }} + open={open} + autoHideDuration={6000} + onClose={handleClose} + > <Alert icon={<ErrorIcon />} severity='error' @@ -1,31 +1,24 @@ -import Stack from '@mui/material/Stack'; -import useMediaQuery from '@mui/material/useMediaQuery'; +import Stack from '@mui/material/Stack' +import useMediaQuery from '@mui/material/useMediaQuery' -import { DrawerCustom } from './drawer'; +import { DrawerCustom } from './drawer' -export function FiltersWrapper (props: {open: boolean, onClose: (value: boolean) => void, filetrs: React.ReactNode}) { +export function FiltersWrapper(props: { open: boolean; onClose: (value: boolean) => void; filetrs: React.ReactNode }) { + const isDesktop = useMediaQuery('(min-width: 1025px)') - const isDesktop = useMediaQuery('(min-width: 1025px)'); + if (isDesktop) { + return ( + <Stack spacing={1} width='100%' height='auto' className='pd-30 bg-color-block border-radius-main'> + {props.filetrs} + </Stack> + ) + } - if (isDesktop) { - return ( - <Stack - spacing={1} - width='100%' - height='auto' - className='pd-30 bg-color-block border-radius-main' - > - {props.filetrs} - </Stack> - ) - } - - return ( - <DrawerCustom open={props.open} onClose={() => props.onClose(false)}> - <Stack spacing={1} padding={2.4}> - {props.filetrs} - </Stack> - </DrawerCustom> - ) - -} \ No newline at end of file + return ( + <DrawerCustom open={props.open} onClose={() => props.onClose(false)}> + <Stack spacing={1} padding={2.4}> + {props.filetrs} + </Stack> + </DrawerCustom> + ) +} @@ -3,8 +3,8 @@ import { RegisterOptions } from 'react-hook-form/dist/types/validator' import { TextField } from '@mui/material' import { TextFieldProps } from '@mui/material/TextField/TextField' -import { baseColor } from '@/src/shared/lib/constants/colors' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { baseColor } from '#/shared/lib/constants/colors' +import { useThemeAndDevice } from '#/shared/lib/hooks' export const styleInputWithoutBorderFocus = { '& .MuiOutlinedInput-root:hover': { @@ -2,7 +2,7 @@ import * as React from 'react' import Menu from '@mui/material/Menu' import Typography from '@mui/material/Typography' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' interface INotificationMenu { anchorEl: null | HTMLElement @@ -2,8 +2,8 @@ import React from 'react' import { Card, CardMedia } from '@mui/material' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' +import { getRandomImage } from '../lib/helpers' -import { getRandomImage } from '@/src/pages/login' export const RandomImage = () => { const refImage = React.useRef(getRandomImage()) @@ -4,11 +4,20 @@ import Link from 'next/link' export const ReferralBlock = () => { return ( <Box> - <Box className='pd-30 bg-color-block border-radius-main mt-15' sx={{ width: '100%', backgroundColor: '#8280FF !important' }}> - <Typography className='title-block' sx={{ fontWeight: 600, marginBottom: '20px', color: 'white !important' }} fontSize='16px'> + <Box + className='pd-30 bg-color-block border-radius-main mt-15' + sx={{ width: '100%', backgroundColor: '#8280FF !important' }} + > + <Typography + className='title-block' + sx={{ fontWeight: 600, marginBottom: '20px', color: 'white !important' }} + fontSize='16px' + > Реферальная программа </Typography> - <Typography sx={{ fontSize: '15px', marginTop: '10px', fontWeight: 500, color: '#ffffff !important' }}> + <Typography + sx={{ fontSize: '15px', marginTop: '10px', fontWeight: 500, color: '#ffffff !important' }} + > Получай токены: 20% за подписки твоих рефералов! </Typography> <Link href={'/account?scope=referral'}> @@ -24,7 +33,9 @@ export const ReferralBlock = () => { width: '100%', }} > - <Typography sx={{ marginLeft: '10px', color: '#ffffff', fontSize: 16, fontWeight: 500 }}>Пригласить друга</Typography> + <Typography sx={{ marginLeft: '10px', color: '#ffffff', fontSize: 16, fontWeight: 500 }}> + Пригласить друга + </Typography> </Box> </Link> </Box> @@ -2,8 +2,8 @@ import React from 'react' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import { MenuItem, Select, SelectChangeEvent, Stack, Typography } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' -import { baseColor } from '@/src/shared/lib/constants/colors' +import { useAppSelector } from '#/app/store/store' +import { baseColor } from '#/shared/lib/constants/colors' interface ISelect { value: any @@ -26,7 +26,9 @@ export const SocialMedia = () => { }} > <Image src={'/svg/TG.svg'} height={20} width={20} alt='Telegram' /> - <Typography sx={{ marginLeft: '10px', color: '#208DD3', fontSize: 16, fontWeight: 500 }}>Telegram</Typography> + <Typography sx={{ marginLeft: '10px', color: '#208DD3', fontSize: 16, fontWeight: 500 }}> + Telegram + </Typography> </Box> </Link> <Link href={'https://vk.com/platform_air'} target='_blank'> @@ -43,7 +45,9 @@ export const SocialMedia = () => { }} > <Image src={'/svg/VK.svg'} height={20} width={20} alt='VK' /> - <Typography sx={{ marginLeft: '10px', color: '#0A5CFF', fontSize: 16, fontWeight: 500 }}>ВКонтакте</Typography> + <Typography sx={{ marginLeft: '10px', color: '#0A5CFF', fontSize: 16, fontWeight: 500 }}> + ВКонтакте + </Typography> </Box> </Link> </Box> @@ -2,7 +2,7 @@ import React, { useMemo } from 'react' import { Alert, Slide, Snackbar, Typography } from '@mui/material' import Image from 'next/image' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { useThemeAndDevice } from '#/shared/lib/hooks' interface ISuccess { message: string @@ -23,10 +23,17 @@ export const Success: React.FC<ISuccess> = ({ message, handleClose, open, isErro return ( <Slide direction='down' in={open} mountOnEnter unmountOnExit> - <Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} autoHideDuration={6000} onClose={handleClose}> + <Snackbar + anchorOrigin={{ vertical: 'top', horizontal: 'right' }} + open={open} + autoHideDuration={6000} + onClose={handleClose} + > <Alert iconMapping={{ - success: <Image src={WidgetIcon} style={{ marginTop: 3 }} width={20} alt={''} height={20} />, + success: ( + <Image src={WidgetIcon} style={{ marginTop: 3 }} width={20} alt={''} height={20} /> + ), }} severity='success' sx={ @@ -5,16 +5,27 @@ import Link from 'next/link' export const TelegramBlock = () => { return ( <Box> - <Box className='pd-30 bg-color-block border-radius-main mt-15' sx={{ width: '100%', backgroundColor: '#8280FF !important' }}> + <Box + className='pd-30 bg-color-block border-radius-main mt-15' + sx={{ width: '100%', backgroundColor: '#8280FF !important' }} + > <Image src={'/settings.svg'} height={47} width={50} alt='Telegram' /> - <Typography sx={{ fontSize: '20px', marginTop: '10px', fontWeight: 500, color: 'white !important' }}> + <Typography + sx={{ fontSize: '20px', marginTop: '10px', fontWeight: 500, color: 'white !important' }} + > Бесплатные токены </Typography> - <Typography sx={{ fontSize: '15px', marginTop: '10px', fontWeight: 500, color: 'white !important' }}> + <Typography + sx={{ fontSize: '15px', marginTop: '10px', fontWeight: 500, color: 'white !important' }} + > Получите 5 токенов за подписку на наш телеграм канал! </Typography> <Link href={'https://t.me/air_platform_bot'} target='_blank'> - <Typography sx={{ marginTop: '20px', fontSize: 16, fontWeight: 500, color: 'white !important' }}>Подписаться</Typography> + <Typography + sx={{ marginTop: '20px', fontSize: 16, fontWeight: 500, color: 'white !important' }} + > + Подписаться + </Typography> </Link> </Box> </Box> @@ -8,8 +8,10 @@ export { ButtonUI } from './ui/button/button' export { ButtonGray } from './ui/button/button-gray' export { CheckBoxAgreeWithRules } from './ui/check-box-agree-with-rules' export { DalleFilterMenu } from './ui/dalle-filter-menu' +export * from './client-only' export * from './ui/drawer' export { Error } from './ui/error' +export * from './lib/helpers' export * from './ui/graphics-main-page' export { Input, InputStyleDark, InputStyleLight } from './ui/input' export { InputImagesModels } from './ui/input-images-models/input-images-models' @@ -27,5 +29,5 @@ export { Success } from './ui/success' export { SwitchCustom } from './ui/switch/switch' export { TooltipCustom } from './ui/tooltip/tooltip' export { TooltipFreeTokens } from './ui/tooltip-free-tokens' -export { useModel } from '@/src/shared/api/models/endpoints' -export type { ChatProps, FiltersChatProps } from '@/src/shared/lib/types/model' +export { useModel } from '#/shared/api/models/endpoints' +export type { ChatProps, FiltersChatProps } from '#/shared/lib/types/model' @@ -0,0 +1 @@ +export * from './scopes' \ No newline at end of file @@ -0,0 +1,6 @@ +export const scopes = [ + { title: 'Настройки', scope: 'setting' }, + { title: 'Управление оплатой', scope: 'subscribe' }, + { title: 'Корпоративный аккаунт', scope: 'business' }, + { title: 'Реферальная программа', scope: 'referral' }, +] \ No newline at end of file @@ -0,0 +1 @@ +export * from './payments' \ No newline at end of file @@ -0,0 +1,5 @@ +export interface IPaymentsPlan { + uid: string + price: string + tokens_per_plan: string +} @@ -0,0 +1,601 @@ +import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' +import { Avatar, Box, Button, Stack, Tab, Tabs, Typography } from '@mui/material' +import CircularProgress from '@mui/material/CircularProgress' +import axios from 'axios' +import Image from 'next/image' +import { useRouter } from 'next/router' +import { signOut, useSession } from 'next-auth/react' + +import { getUserBalance } from '#/entities/balance' +import { getAllInfo, unfollowEmail } from '#/entities/user-account/model/user-type-slice' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import styles2 from '#/app/styles/accountTabs.module.css' +import styles from '#/app/styles/business.module.scss' +import { ButtonUI, Input, Loader, Modal, Success, SwitchCustom, useShowData } from '#/shared' +import { accountApi } from '#/shared/api/account-endpoints' +import { API_URL } from '#/shared/lib/constants/constants' +import { getDeviceType } from '#/shared/lib/helpers' +import { ScreenForInactive } from '#/widgets/business' +import BusinessHost from '#/widgets/business-host/business-host' +import { Info } from '#/widgets/business-info' +import { Subscription } from '#/widgets/payment/model/payment' +import { Referral } from '#/widgets/referral' +import { NextPageWithLayout } from '#/pages/_app' +import { DownloadModal } from '#/features/business-security-download' +import { scopes } from '../config' + +const Account: NextPageWithLayout = () => { + const { + email, + is_subscribed_to_emails, + account_type, + first_name, + last_name, + username, + profile_picture_link, + is_social, + status: userInfoLoaded, + referral_code, + } = useAppSelector((state) => state.user) + + const device = getDeviceType() + + const { status, account_type: type } = useAppSelector((state) => state.user) + + const [name, setName] = useState('') + + const [lastName, setLastName] = useState('') + + const [userName, setUserName] = useState('') + + const fileInputRef = useRef(null) + + const [loading, setLoading] = useState(false) + + const { data } = useSession() + + const handleDivClick = () => { + ;(fileInputRef.current! as any).click() + } + + const dispatch = useAppDispatch() + + const { error, showError, isError } = useShowData() + + const handleFileChange = async (event: any) => { + const formData = new FormData() + formData.append('new_picture', event.target.files[0]) + + try { + setLoading(true) + await axios.put(API_URL + '/auth/reset-profile-pic', formData, { + headers: { + Authorization: `Bearer ${data?.access}`, + 'Content-Type': 'multipart/form-data', + }, + }) + dispatch(getAllInfo(data?.access)) + showError('Изображение успешно загружено!') + setLoading(false) + } catch (e) { + setLoading(false) + showError('Ошибка загрузки изображения на сервере!', true) + } + } + + const [currentPassword, setCurrentPassword] = React.useState<string>('') + + const [newPassword1, setNewPassword1] = React.useState<string>('') + + const [newPassword2, setNewPassword2] = React.useState<string>('') + + const [promocode, setPromocode] = React.useState<string>('') + + const [success, setSuccess] = React.useState('') + + const [confirmDeleteModal, setConfirmDeleteModal] = useState(false) + + const { push, query } = useRouter() + + const [scope, setScope] = useState(query['scope'] || 'setting') + + const [downloadModal, setDownloadModal] = useState<boolean>(false) + + const desktop = device === 'desktop' + + const changeScope = async (scope: string) => { + setScope(scope) + await addQueryParams(scope) + } + + async function addQueryParams(scope: string) { + await push({ + pathname: '/account', + query: { scope }, + }) + } + + useEffect(() => { + if (query['scope'] !== undefined && scope !== query['scope']) { + changeScope(query['scope'] as string) + } + }, [query]) + + useEffect(() => { + if (Object.keys(query).length === 0) addQueryParams('setting') + }, []) + + const changePassword = async () => { + if (!data) return + if (newPassword1 !== newPassword2) { + showError('Укажите одинаковые новы пароли!', true) + return + } + const status = await accountApi.changePassword(data.access, newPassword1, newPassword2, currentPassword) + + if (status === 200) { + showError('Пароль успешно изменён!') + setNewPassword1('') + setNewPassword2('') + setCurrentPassword('') + setTimeout(() => setSuccess(''), 6000) + return + } + + showError('К сожалению, произошла ошибка', true) + } + + function body() { + if (type === 'regular') { + return ( + <Box width='80vw' height='60vh' display='flex' alignItems='center' justifyContent='center'> + <ScreenForInactive /> + </Box> + ) + } + if (type === 'business_account') { + return ( + <Box className={styles.main}> + Информация о корп.аккаунте доступна только владельцу и администраторам. + </Box> + ) + } + if (type === 'business_host') { + return <BusinessHost /> + } + if (type === 'business_admin') { + return <BusinessHost /> + } + if (type === 'business_security') { + return ( + <> + <DownloadModal open={downloadModal} setOpen={setDownloadModal} /> + <Box className={styles.main}> + <Info security={true} /> + <Typography className={styles.tite_text} sx={{ marginTop: '20px' }}> + Раздел 1. Запросы сотрудников по моделям + </Typography> + <Box className={styles.chapter_block}> + <ButtonUI + onClick={() => { + setDownloadModal(true) + }} + sx={{ marginTop: '20px' }} + text={'Скачать'} + /> + </Box> + </Box> + </> + ) + } + } + + const isUserDataChange = useMemo( + () => name !== first_name || last_name !== lastName || username !== userName, + [name, lastName, userName] + ) + + const promocodeActivate = async () => { + if (!data) return + if (!promocode.trim()) { + showError('Введите корректный промокод', true) + return + } + let resStatus = 404 + try { + const { status } = await axios.post( + API_URL + '/payments/promocode', + { code: promocode }, + { headers: { Authorization: `Bearer ${data.access}` } } + ) + resStatus = status + } catch (e) {} + + if (resStatus === 200) { + showError('Промокод успешно активирован! Токены уже зачислены!') + dispatch(getUserBalance(data?.access)) + return + } + + if (resStatus === 403) { + showError('Промокод уже был активирован!', true) + return + } + + if (resStatus === 404) { + showError('Промокод не найден!', true) + return + } + } + + async function changeUserData() { + if (!data) return + if (!isUserDataChange) { + showError('Вы не изменили данные', true) + return + } + + try { + await axios.put( + API_URL + '/auth/user-data', + { + username: userName, + email: email, + first_name: name, + last_name: lastName, + }, + { headers: { Authorization: `Bearer ${data.access}` } } + ) + + showError('Данные успешно изменены!') + dispatch(getAllInfo(data.access)) + } catch (e) {} + } + + const deleteAccount = async () => { + if (!data) return + try { + const { status } = await axios.delete(API_URL + '/auth/remove', { + headers: { + Authorization: `Bearer ${data.access}`, + }, + }) + + if (status === 200) await signOut() + } catch (err) {} + } + + useEffect(() => { + setName(first_name) + setLastName(last_name) + setUserName(username) + }, [first_name, last_name, username]) + + return ( + <Box + sx={{ + width: '100%', + height: '100%', + marginTop: desktop ? 3 : 2, + }} + > + <Stack + direction={desktop ? 'row' : 'column'} + justifyContent='start' + alignItems={desktop ? 'flex-start' : 'center'} + spacing={2} + sx={{ overflowY: 'auto', overflowX: 'hidden' }} + > + <Stack + direction='column' + justifyContent='flex-start' + alignItems='flex-start' + spacing={2} + sx={{ + width: desktop ? '90%' : '100%', + height: '100%', + }} + > + <Typography sx={{ fontSize: 24, fontWeight: 'bold' }}>Ваш аккаунт</Typography> + <Tabs + value={scope} + variant='scrollable' + className={styles2.wrap_toggle_button} + aria-label='Platform' + scrollButtons={false} + sx={{ '& .MuiTabs-indicator': { display: 'none' } }} + > + {scopes.map((el) => { + if (!desktop && el.scope === 'business') { + return null + } + if (el.scope === 'referral' && account_type !== 'regular') { + return null + } + return ( + <Tab + disableRipple + onClick={() => changeScope(el.scope)} + key={el.title} + className={ + scope === el.scope + ? styles2.wrap_toggle_button_active + : styles2.wrap_toggle_button + } + value={el.scope} + label={el.title} + /> + ) + })} + </Tabs> + {scope === 'setting' ? ( + <> + <Box className={styles2.wrap_block}> + <Typography className='title-block'>Основные</Typography> + <Box className={styles2.main_block}> + <Box sx={{ marginRight: 2 }}> + <Box + onClick={handleDivClick} + sx={{ + cursor: 'pointer', + position: 'relative', + }} + > + <input + type='file' + ref={fileInputRef} + style={{ display: 'none' }} + onChange={handleFileChange} + accept='image/*' + /> + <Box + sx={{ + position: 'absolute', + zIndex: 100, + top: '40px', + right: '37px', + }} + > + {!loading || !(userInfoLoaded === 'succeeded') ? ( + <Image + src={'/Edit.svg'} + height={30} + width={30} + alt={'Image change'} + /> + ) : ( + <Loader /> + )} + </Box> + <Avatar + src={profile_picture_link as string} + sx={{ + width: 110, + height: 110, + backgroundColor: '#8280FF', + }} + > + {''} + </Avatar> + </Box> + </Box> + <Box width='100%' sx={{ marginLeft: '30px' }}> + <Box sx={{ marginTop: '0px' }}> + <Typography className={styles2.input_title}>Имя</Typography> + <Input + inputProps={{ maxLength: 15 }} + value={name} + onChange={(e) => setName(e.target.value)} + fullWidth + /> + </Box> + <Box sx={{ marginTop: '15px' }}> + <Typography className={styles2.input_title}>Фамилия</Typography> + <Input + inputProps={{ maxLength: 15 }} + value={lastName} + onChange={(e) => setLastName(e.target.value)} + fullWidth + /> + </Box> + <Box sx={{ marginTop: '15px' }}> + <Box + display='flex' + alignItems='center' + justifyContent='space-between' + > + <Typography className={styles2.input_title}> + Email + </Typography> + <Box + display='flex' + alignItems='center' + sx={{ marginBottom: '5px' }} + > + <SwitchCustom + checked={is_subscribed_to_emails} + onChange={async () => { + if (!data) return + await dispatch(unfollowEmail(data.access)) + showError('Данные изменены!') + }} + /> + <Typography + sx={{ + color: '#868686', + fontSize: '15px', + marginLeft: 1, + }} + > + Отписаться от рассылки + </Typography> + </Box> + </Box> + <Input value={email} fullWidth /> + </Box> + <Box sx={{ marginTop: '15px' }}> + <Typography className={styles2.input_title}>Никнейм</Typography> + <Input + inputProps={{ maxLength: 15 }} + value={userName} + onChange={(e) => setUserName(e.target.value)} + fullWidth + /> + </Box> + <ButtonUI + onClick={changeUserData} + sx={{ marginTop: '20px' }} + text={'Сохранить'} + /> + {/*referral_code.code.trim() && ( + <Box marginTop={1} display='flex'> + <Typography className='text'>Ваша реферальная ссылка:</Typography> +   + <TooltipCustom title='Нажмите чтобы скопировать'> + <Typography + onClick={() => navigator.clipboard.writeText(referral_code.code.trim())} + className='text' + sx={{ color: '#8280FF !important', cursor: 'pointer' }} + > + {referral_code.code} + </Typography> + </TooltipCustom> + </Box> +) */} + </Box> + </Box> + </Box> + <Stack spacing={2} className={styles2.wrap_block}> + <Typography className='title-block'>Активация промокода</Typography> + <Input + placeholder='Введите ваш промокод' + value={promocode} + onChange={(e) => setPromocode(e.target.value)} + fullWidth + /> + <Button onClick={promocodeActivate} className='btn-classic'> + Активировать + </Button> + </Stack> + {!is_social && ( + <Box className={styles2.wrap_block}> + <Typography className='title-block'>Изменить пароль</Typography> + <Box className={styles2.main_block} flexDirection='column'> + <Box sx={{ width: '100%' }}> + <Typography className={styles2.input_title}> + Текущий пароль + </Typography> + <Input + placeholder='Укажите текущий пароль' + value={currentPassword} + onChange={(e) => setCurrentPassword(e.target.value)} + fullWidth + /> + </Box> + <Box + display='flex' + justifyContent='space-between' + sx={{ + width: '100%', + marginTop: '15px', + }} + > + <Box sx={{ width: '48%' }}> + <Typography className={styles2.input_title}> + Новый пароль + </Typography> + <Input + placeholder='Укажите новый пароль' + value={newPassword1} + onChange={(e) => setNewPassword1(e.target.value)} + fullWidth + /> + </Box> + <Box sx={{ width: '48%' }}> + <Typography className={styles2.input_title}> + Подтвердить пароль + </Typography> + <Input + placeholder='Укажите новый пароль ещё раз' + value={newPassword2} + onChange={(e) => setNewPassword2(e.target.value)} + fullWidth + /> + </Box> + </Box> + <ButtonUI + onClick={changePassword} + sx={{ marginTop: '20px' }} + text={'Изменить пароль'} + style={{ width: '150px' }} + /> + </Box> + </Box> + )} + <Stack spacing={2} className={styles2.wrap_block} sx={{ marginBottom: 5 }}> + <Typography className='title-block'>Удаление аккаунта</Typography> + <Typography className='text'> + Удаление аккаунта приведет к потере всех настроек + </Typography> + <ButtonUI + fullWidth + onClick={() => setConfirmDeleteModal(true)} + sx={{ marginTop: '20px' }} + text={'Удалить аккаунт'} + style={{ + width: '100%', + backgroundColor: 'rgba(255, 35, 114, 0.10)', + color: '#FF2372', + }} + /> + </Stack> + + <Modal open={confirmDeleteModal} onClose={() => setConfirmDeleteModal(false)}> + <Stack spacing={2}> + <Typography className='title-struct'>Удаление аккаунта</Typography> + <Typography className='text'> + Вы действительно хотите удалить ваш аккаунт? + </Typography> + <Button + onClick={deleteAccount} + fullWidth + sx={{ marginTop: 3 }} + className='btn-danger' + > + Удалить + </Button> + </Stack> + </Modal> + </> + ) : scope === 'business' ? ( + <Box> + {status == 'pending' || type === null ? ( + <Box className={styles.loading}> + <CircularProgress + size={25} + thickness={5} + sx={{ + color: '#7F7DF3', + }} + /> + </Box> + ) : ( + <Box>{body()}</Box> + )} + </Box> + ) : scope === 'subscribe' ? ( + <Subscription device={device} /> + ) : scope === 'referral' && account_type === 'regular' ? ( + <Referral device={device} /> + ) : ( + <></> + )} + <Success message={error} open={Boolean(error)} isError={isError} /> + </Stack> + </Stack> + </Box> + ) +} + +export default Account @@ -0,0 +1 @@ +export { default as AccountPage } from './account' @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1,31 @@ +import React from 'react' +import { Stack } from '@mui/material' + +import { useAppSelector } from '#/app/store/store' +import { StatsField } from '#/widgets/stats-field' +import { useSession } from 'next-auth/react' +import { getDeviceType } from '#/shared/lib/helpers' +import { NextPageWithLayout } from '#/pages/_app' + +/** + * @deprecated + */ +const Admin: NextPageWithLayout = () => { + const theme = useAppSelector((state) => state.theme.theme) + + const device = getDeviceType() + const { data: session } = useSession() + + return ( + <Stack + direction='column' + justifyContent='flex-start' + alignItems='center' + sx={{ width: '80vw', margin: '40px auto' }} + > + <StatsField device={device} token={session?.access} theme={theme} /> + </Stack> + ) +} + +export default Admin @@ -0,0 +1 @@ +export { default as AdminPage } from './admin' @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1,236 @@ +import React, { useEffect, useState } from 'react' +import { Box, Stack, Table, TableBody, TableHead, TableRow, TextField, Typography } from '@mui/material' +import Button from '@mui/material/Button' +import TableCell from '@mui/material/TableCell' +import axios from 'axios' +import Image from 'next/image' +import Link from 'next/link' +import { useSession } from 'next-auth/react' + +import { getAll, ResponseAllInfo } from '#/entities/user-account/model/user-type-slice' +import { ApiKeyModal } from '#/features/api-key-modal/api-key-modal' +import { Layout } from '#/app/layout' +import { useAppSelector } from '#/app/store/store' +import { TooltipCustom } from '#/shared' +import { accountApi } from '#/shared/api/account-endpoints' +import { API_URL } from '#/shared/lib/constants' +import styles from '#/shared/styles/api-keys.module.scss' +import { InputStyleSmallDark, InputStyleSmallLight } from '#/shared/ui/input' +import { NextPageWithLayout } from '#/pages/_app' + +const ApiKeys: NextPageWithLayout = () => { + const [keys, setKeys] = useState<Array<any> | null>(null) + const [loading, setLoading] = useState(false) + const { data, status } = useSession() + const [modal, setModal] = useState<boolean>(false) + const [userInfo, setUserInfo] = useState<ResponseAllInfo | null>() + const [disabled, setDisabled] = React.useState<boolean>(true) + + useEffect(() => { + getAll(data?.access).then((res) => setUserInfo(res)) + }, [data?.access]) + + useEffect(() => { + if (status === 'authenticated') { + setLoading(true) + accountApi.getApiKeys(data?.access).then((res) => { + setKeys(res) + setLoading(false) + }) + } + }, [status]) + + React.useEffect(() => { + if ( + userInfo?.account_type === 'business_security' || + userInfo?.account_type === 'business_account' || + status !== 'authenticated' || + loading + ) { + setDisabled(true) + } else { + setDisabled(false) + } + }, [status, userInfo, loading]) + + const deleteKey = async (name: string) => { + if (keys === null || status !== 'authenticated') { + return + } + + setLoading(true) + const result = await accountApi.deleteApiKey(name, data?.access) + if (result !== null && result === 200) { + accountApi.getApiKeys(data?.access).then((res) => { + setKeys(res) + setLoading(false) + }) + } + setLoading(false) + } + + return ( + <> + <ApiKeyModal open={modal} setOpen={setModal} keys={keys} setKeys={setKeys} /> + <Box style={{ marginTop: '20px', marginBottom: '20px' }}> + <Typography sx={{ fontSize: 35, fontWeight: '600' }}>API ключи</Typography> + </Box> + <Stack spacing={2} className={styles.wrap}> + <Stack spacing={2} className='pd-30 bg-color-block border-radius-main'> + <Typography className='text'> + API-ключ — это инструмент, который идентифицирует пользователя или программу, + запрашивающих доступ к API платформы. С помощью ключа можно отслеживать, кто и когда + пользуется API, рассчитывать оплату. + </Typography> + <Box display='flex' alignItems='center'> + <Button + onClick={() => setModal(true)} + disabled={disabled} + className='btn-classic' + sx={{ width: '130px' }} + > + Создать ключ + </Button> + <Link href='https://air-docs.readthedocs.io/' target='_blank'> + <Typography + className='text' + sx={{ + marginLeft: '15px', + color: '#8280FF !important', + }} + > + Документация + </Typography> + </Link> + </Box> + </Stack> + + {keys && keys.length !== 0 ? ( + <Stack spacing={1} className='pd-30 bg-color-block border-radius-main'> + <Typography className='title-block'>Мои ключи</Typography> + <Table sx={{ minWidth: 650, overflowX: 'scroll' }} aria-label='simple table'> + <TableHead> + <TableRow + sx={{ + '&:last-child td, &:last-child th ': { + border: 0, + }, + }} + > + <TableCell align='left'>Имя</TableCell> + <TableCell align='left'>Ключ</TableCell> + <TableCell align='left'>Лимит токенов</TableCell> + <TableCell align='left'>Создан</TableCell> + <TableCell align='left'>Действителен до</TableCell> + </TableRow> + </TableHead> + <TableBody> + {keys && + keys.map((el) => { + return ( + <KeyRow + key={el.key} + keyValue={el.key} + name={el.name} + limit={el.token_limit} + created_at={el.created_at.split('T')[0]} + expires_at={el.expires_at} + deleteKey={deleteKey} + /> + ) + })} + </TableBody> + </Table> + </Stack> + ) : ( + <Box height={140} width={'100%'} display='flex' alignItems='center' justifyContent='center'> + <Box textAlign='center'> + <Typography className='title-vspomogatel'>У вас пока нет ключей 😞 </Typography> + <Typography className='title-main-gray'>Создайте первый ключ</Typography> + </Box> + </Box> + )} + </Stack> + </> + ) +} + +const KeyRow = (props: any) => { + const [isCopy, setIsCopy] = useState(false) + const [limit, setLimit] = useState(props.limit) + const theme = useAppSelector((state) => state.theme.theme) + const { data: session } = useSession() + + const copy = (text: string) => { + navigator.clipboard.writeText(text) + setIsCopy(true) + setTimeout(() => setIsCopy(false), 3000) + } + + useEffect(() => { + let timeout = window.setTimeout(() => { + if (limit !== props.limit) { + axios.patch( + API_URL + '/public/api-key', + { token_limit: Number(limit), name: props.name }, + { headers: { Authorization: `Bearer ${session?.access}` } } + ) + } + }, 1000) + return () => window.clearTimeout(timeout) + }, [limit]) + + return ( + <TableRow key={props.key} sx={{ '&:last-child td, &:last-child th ': { border: 0 } }}> + <TableCell component='th' align='left' scope='row'> + {props.name} + </TableCell> + <TableCell component='th' align='left' sx={{ color: '#8280FF !important' }}> + {props.keyValue} + </TableCell> + <TableCell component='th' align='center' sx={{ color: '#8280FF !important' }} width={160}> + <TextField + value={limit} + placeholder={limit === null || limit === '' ? 'Бесконечно' : ''} + type='text' + onChange={(e) => setLimit(e.target.value)} + sx={theme === 'light' ? { ...InputStyleSmallLight } : { ...InputStyleSmallDark }} + /> + </TableCell> + <TableCell component='th' align='left'> + {props.created_at.split('T')[0]} + </TableCell> + <TableCell component='th' align='left'> + {props.expires_at !== null ? props.expires_at : 'Бессрочно'} + </TableCell> + <TableCell component='th' align='left'> + <Stack direction='row' spacing={2}> + {isCopy ? ( + <Image src={'/svg/tic.svg'} width={20} height={22} alt={'copy'} /> + ) : ( + <TooltipCustom title={'Скопировать ключ'}> + <Image + onClick={() => copy(props.keyValue)} + src={'/svg/copy.svg'} + width={20} + height={20} + style={{ cursor: 'pointer' }} + alt={'copy'} + /> + </TooltipCustom> + )} + <Image + onClick={() => props.deleteKey(props.name)} + style={{ cursor: 'pointer' }} + src='/svg/main_menu/trash.svg' + width={20} + height={20} + alt='Удалить' + /> + </Stack> + </TableCell> + </TableRow> + ) +} + +export default ApiKeys @@ -0,0 +1 @@ +export { default as ApiKeysPage } from './api-keys' @@ -0,0 +1 @@ +export * from './ui' @@ -2,19 +2,16 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { Box, Typography } from '@mui/material' import { useSession } from 'next-auth/react' -import { Card } from '@/src/shared/card/card' -import { getAudio } from '@/src/entities/audio-model' -import { IShortModel } from '@/src/entities/model-entity' -import { Layout } from '@/src/main/layout' -import { getDeviceType } from '@/src/shared/lib/helpers' +import { Card } from '#/shared/card/card' +import { getAudio } from '#/entities/audio-model' +import { NextPageWithLayout } from '#/pages/_app' +import { IShortModel } from '#/entities/model-entity' -export const AudioModelsPage = () => { +export const AudioModelsPage: NextPageWithLayout = () => { const [bots, setBots] = useState<IShortModel[] | null>(null) const { data, status } = useSession() - const deviceType = getDeviceType() - useEffect(() => { if (!data) return @@ -22,15 +19,13 @@ export const AudioModelsPage = () => { }, [status]) return ( - <Layout titlePage={'Аудио'} device={deviceType}> - <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}> - Аудио - </Typography> + <> + <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Аудио</Typography> <Box display='flex' flexWrap='wrap'> {bots?.map((item, idx) => { return ( <Card - blocked={item.blocked} + blocked={item.blocked} uid={item.uid} key={item.uid} title={item.title} @@ -41,7 +36,7 @@ export const AudioModelsPage = () => { ) })} </Box> - </Layout> + </> ) } @@ -0,0 +1,17 @@ +import { API_URL } from "#/shared/lib/constants" +import axios from "axios" + +const client_secret = process.env.NEXT_PUBLIC_DJANGO_GOOGLE_APP_CLIENT_SECRET +const client_id = process.env.NEXT_PUBLIC_DJANGO_GOOGLE_APP_CLIENT_ID + +export async function getTokenByAPIGoogle(token: string) { + const { data } = await axios.post(API_URL + '/auth/login-social/convert-token', { + grant_type: 'convert_token', + client_id: client_id, + client_secret: client_secret, + backend: 'google-oauth2', + token: token, + }) + + return data +} @@ -0,0 +1 @@ +export * from './google' \ No newline at end of file @@ -0,0 +1,14 @@ +import React, { useEffect } from 'react' +import { getTokenByAPIGoogle } from '../api' +import { NextPageWithLayout } from '#/pages/_app' + +const AuthSocial: NextPageWithLayout = () => { + useEffect(() => { + const token = new URLSearchParams(window.location.href.replace('#', '&')).get('access_token') || '' + getTokenByAPIGoogle(token).then((res) => res) + }, []) + + return <></> +} + +export default AuthSocial @@ -0,0 +1 @@ +export { default as AuthSocialPage } from './auth-social' @@ -0,0 +1,2 @@ +export * from './ui' +export * from './api' \ No newline at end of file @@ -0,0 +1,239 @@ +import React, { useRef, useState } from 'react' +import { Box, Card, CardMedia, Stack, TextField, Typography } from '@mui/material' +import Button from '@mui/material/Button' +import axios from 'axios' +import Image from 'next/image' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useAppSelector } from '#/app/store/store' +import { API_URL } from '#/shared/lib/constants/constants' +import { getDeviceType, getRandomImage } from '#/shared/lib/helpers' +import { NextPageWithLayout } from '#/pages/_app' + +const ChangePassword: NextPageWithLayout = () => { + const device = getDeviceType() + + const [password1, setPassword1] = useState<string>('') + + const [password2, setPassword2] = useState<string>('') + + const [isChange, setIsChange] = useState(false) + + const { query, push } = useRouter() + + const changePassword = async () => { + let formData: any = new FormData() + + formData.append('password_1', password1) + formData.append('password_2', password2) + + if (password1.trim() && password2.trim()) { + try { + await axios.post(API_URL + `/auth/change-pass?token=${query.token}`, formData, { + headers: { + 'content-type': 'multipart/form-data ', + }, + }) + setIsChange(true) + setTimeout(() => push('/login'), 5000) + } catch (err) {} + } + } + + const desktop = device === 'desktop' + + const refImage = useRef(getRandomImage()) + + const theme = useAppSelector((state) => state.theme.theme) + + return ( + <> + <Stack + direction={desktop ? 'row' : 'column'} + alignItems='center' + justifyContent={desktop ? 'space-between' : 'center'} + sx={{ + padding: 0, + margin: desktop ? 0 : '60px auto', + backgroundColor: theme === 'light' ? 'white' : '#373737', + }} + > + <Link + style={{ + position: 'absolute', + top: 45, + right: '7%', + cursor: 'pointer', + fontSize: 17, + }} + href={'/'} + > + <Image src={'/x-mark.svg'} width={18} height={18} alt={''} /> + </Link> + {desktop && ( + <Card> + <CardMedia + sx={{ height: '100vh', width: '600px' }} + image={`/login_images/${refImage.current}`} + > + <Stack + direction='column-reverse' + justifyContent='flex-start' + alignItems='flex-start' + spacing={2} + sx={{ + wdith: '100%', + height: '100%', + paddingBottom: 3, + paddingLeft: 3, + }} + > + <Typography + variant='body2' + sx={{ + color: '#FFFFFF', + lineHeight: '23.8px', + fontSize: '17px', + fontWeight: '400px', + }} + > + Midjourney + </Typography> + <Typography + variant='body2' + sx={{ + color: '#FFFFFF', + lineHeight: '28px', + fontSize: '20px', + fontWeight: '600px', + }} + > + by honeynek + </Typography> + </Stack> + </CardMedia> + </Card> + )} + {isChange ? ( + <Box + sx={{ + width: '40vh', + height: '100%', + marginRight: '25%', + textAlign: 'center', + }} + > + <Typography + sx={{ + fontSize: 23, + textAlign: 'center', + marginBottom: 1, + }} + > + Ваш пароль успешно изменён! + </Typography> + <Link style={{ color: '#7F7DF3', fontSize: 17 }} href={'/login'}> + Войти в аккаунт + </Link> + </Box> + ) : ( + <Box sx={{ width: '40vh', height: '100%', margin: '0 auto' }}> + <Typography + sx={{ + color: theme === 'light' ? '#181C32' : '#E1E1E1', + lineHeight: '36.4px', + fontSize: '24px', + textAlign: 'center', + marginBottom: 2, + }} + > + Восстановление пароля + </Typography> + <Typography + variant='body2' + sx={{ + color: theme === 'light' ? '#181C32' : '#A6A5A5', + lineHeight: '19.6px', + fontSize: '14px', + fontWeight: '600', + paddingTop: 0.2, + }} + > + Новый пароль + </Typography> + <TextField + fullWidth + sx={{ + marginTop: 1.5, + paddingTop: 0, + paddingBottom: 0, + '& label': { color: '#8853FA' }, + backgroundColor: theme === 'light' ? 'transparent' : '#3D3D3D', + input: { + color: theme === 'light' ? '#272727' : '#E1E1E1', + }, + }} + value={password1} + onChange={(e) => setPassword1(e.target.value)} + ></TextField> + <Typography + variant='body2' + sx={{ + color: theme === 'light' ? '#181C32' : '#A6A5A5', + lineHeight: '19.6px', + fontSize: '14px', + fontWeight: '600', + paddingTop: 0.2, + }} + > + Подтверждение пароля + </Typography> + <TextField + fullWidth + sx={{ + marginTop: 1.5, + paddingTop: 0, + paddingBottom: 0, + '& label': { color: '#8853FA' }, + backgroundColor: theme === 'light' ? 'transparent' : '#3D3D3D', + input: { + color: theme === 'light' ? '#272727' : '#E1E1E1', + }, + }} + value={password2} + onChange={(e) => setPassword2(e.target.value)} + ></TextField> + <Box> + <Button + variant='contained' + style={{ + marginTop: '18px', + }} + sx={{ + backgroundColor: '#7F7DF3', + color: '#FFFFFF', + lineHeight: '20px', + fontSize: '16px', + fontWeight: '600px', + borderRadius: '13px', + textTransform: 'none', + width: '100%', + height: 48, + '&:hover': { + backgroundColor: '#8153FB', + opacity: [0.9, 0.8, 0.7], + }, + }} + onClick={changePassword} + > + Сменить пароль + </Button> + </Box> + </Box> + )} + </Stack> + </> + ) +} + +export default ChangePassword @@ -0,0 +1 @@ +export { default as ChangePasswordPage } from './change-password' @@ -0,0 +1 @@ +export * from './ui' @@ -5,32 +5,33 @@ import { useRouter } from 'next/router' import { useSession } from 'next-auth/react' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' -import BotParamsMap from '@/src/features/bot-params/bot-params-map' -import { useChats } from '@/src/features/chats' -import Title from '@/src/features/title/title' -import { TutorialContext } from '@/src/features/tutorial-context/tutorial-context' -import { ChatSelect } from '@/src/main/components/chat_select' -import { ResetFilters } from '@/src/main/components/filters/reset_filters' -import { setParams as setParametres } from '@/src/main/store/model-parametres-store' -import { useAppSelector } from '@/src/main/store/store' -import { DrawerCustom, Error, useModel, useShowData } from '@/src/shared' -import model_api from '@/src/shared/api/models/api' +import BotParamsMap from '#/features/bot-params/bot-params-map' +import { useChats } from '#/features/chats' +import Title from '#/features/title/title' +import { TutorialContext } from '#/features/tutorial-context/tutorial-context' +import { ChatSelect } from '#/app/components/chat_select' +import { ResetFilters } from '#/app/components/filters/reset_filters' +import { setParams as setParametres } from '#/app/store/model-parametres-store' +import { useAppSelector } from '#/app/store/store' +import { DrawerCustom, Error, useModel, useShowData } from '#/shared' +import model_api from '#/shared/api/models/api' import styles from './chats-bot.module.scss' -import { Chats } from '@/src/widgets/chats' -import { c, getDeviceType, getOs } from '@/src/shared/lib/helpers' +import { Chats } from '#/widgets/chats' +import { c, getDeviceType, getOs } from '#/shared/lib/helpers' +import { NextPageWithLayout } from '#/pages/_app' +import { getDefaultLayoutDynamic } from '#/widgets/layouts' import Head from 'next/head' -import LockSvg from '@/src/assets/svg/lock.svg?react' -import BlockedSvg from '@/src/assets/svg/blocked.svg?react' -import { IModelTag, IModel } from '@/src/entities/model-entity' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' -import { SvgIcon } from '@/src/shared/ui/svg' -import { AllChatWindow } from '@/src/widgets/chat-window' -import { Layout } from '@/src/main/layout' +import LockSvg from '#/assets/svg/lock.svg?react' +import BlockedSvg from '#/assets/svg/blocked.svg?react' +import { IModelTag, IModel } from '#/entities/model-entity' +import { useThemeAndDevice } from '#/shared/lib/hooks' +import { SvgIcon } from '#/shared/ui/svg' +import { AllChatWindow } from '#/widgets/chat-window' export interface ChatBotPageProps {} -const Page = () => { +const Page: NextPageWithLayout<ChatBotPageProps> = () => { const [botParams, setBotParams] = React.useState<IModel | null>(null) const [modelType, setModelType] = React.useState<string>('') const [openFiltersMobile, setOpenFiltersMobile] = React.useState(false) @@ -209,7 +210,10 @@ const Page = () => { const { theme } = useThemeAndDevice() return ( - <Layout device={deviceType} titlePage={botParams?.title ? botParams?.title : ''}> + <> + <Head> + <title>{botParams ? botParams.title : 'Загрузка...'} +
{ input_types={botParams?.inputs} setFile={setFile} file={file} - tags={botParams?.tags || []} blocked={botParams?.blocked} getMessagesPagination={getMessagesPagination} sendMessage={onSendMessage} @@ -446,7 +449,7 @@ const Page = () => { <Error error={error} open={Boolean(error)} /> </Box> </TutorialContext.Provider> - </Layout> + </> ) } @@ -3,21 +3,18 @@ import { useEffect, useState } from 'react' import { Box, Typography } from '@mui/material' import CircularProgress from '@mui/material/CircularProgress' import { useSession } from 'next-auth/react' -import { useAppSelector } from '@/src/main/store/store' -import model_api from '@/src/shared/api/models/api' -import { ChatModelCard, IShortModel } from '@/src/entities/model-entity' +import { useAppSelector } from '#/app/store/store' +import model_api from '#/shared/api/models/api' +import { NextPageWithLayout } from '#/pages/_app' +import { ChatModelCard, IShortModel } from '#/entities/model-entity' import styles from './chat-bots.module.scss' -import { Layout } from '@/src/main/layout' -import { getDeviceType } from '@/src/shared/lib/helpers' -const Page = () => { +const Page: NextPageWithLayout = () => { const [bots, setBots] = useState<IShortModel[] | null>(null) const { data } = useSession() const user = useAppSelector((state) => state.user) - const deviceType = getDeviceType() - useEffect(() => { if (!data) return @@ -27,7 +24,7 @@ const Page = () => { }, [data]) return ( - <Layout titlePage={'Чат-боты'} device={deviceType}> + <> <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}> Чат-боты </Typography> @@ -55,7 +52,7 @@ const Page = () => { /> )} </div> - </Layout> + </> ) } @@ -0,0 +1,71 @@ +import React, { useEffect, useState } from 'react' +import { Box, Typography } from '@mui/material' +import axios from 'axios' +import Link from 'next/link' +import { useRouter } from 'next/router' + +import { API_URL } from '#/shared/lib/constants/constants' +import { NextPageWithLayout } from '#/pages/_app' + +const Confirm: NextPageWithLayout = () => { + const { push, query } = useRouter() + const [success, setSuccess] = useState(false) + + useEffect(() => { + let formData: any = new FormData() + + formData.append('token', query.token) + + axios.post(API_URL + '/auth/confirm', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then(() => { + setSuccess(true) + }) + .catch(() => { + push('/') + }) + }, []) + + useEffect(() => { + if (success) { + setTimeout(() => push('/login'), 10000) + } + }, [success]) + + return ( + <Box + sx={{ + width: '100%', + height: '100vh', + alignItems: 'center', + display: 'flex', + }} + > + <Typography + sx={{ + margin: '0 auto', + textAlign: 'center', + }} + > + {' '} + {success ? ( + <> + <Typography sx={{ color: '#7F7DF3', fontSize: 17 }}> + Ваша почта подтверждена, вы будете перенаправлены на страницу авторизации + </Typography> + <Link style={{ color: '#7F7DF3', fontSize: 17 }} href={'/login'}> + Войти в аккаунт + </Link> + </> + ) : ( + 'Подтверждение...' + )} + </Typography> + </Box> + ) +} + +export default Confirm @@ -0,0 +1 @@ +export { default as ConfirmPage } from './confirm' @@ -0,0 +1 @@ +export * from './ui' @@ -18,11 +18,11 @@ .header { width: 70%; - margin-bottom: 50px; + margin-bottom: 50px; @media screen and (max-width: 768px) { width: 100%; - margin-bottom: 0px; + margin-bottom: 0px; } } @@ -38,8 +38,6 @@ } } - - .blocked { display: flex; align-items: center; @@ -85,10 +83,9 @@ box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1); transition: width 0.3s ease-in-out; - - @media screen and (max-width: 768px) { - height: 40px; - width: 40px; + @media screen and (max-width: 768px) { + height: 40px; + width: 40px; } &__icon { @@ -133,18 +130,17 @@ gap: 5px; position: absolute; left: 0; - + width: 100%; - border-radius: 15px 15px 0px 0px; - top: -23px; - - - @media screen and (max-width: 768px) { - top: unset; - bottom: -22px; - z-index: 1; - border-radius: 0px 0px 15px 15px; - } + border-radius: 15px 15px 0px 0px; + top: -23px; + + @media screen and (max-width: 768px) { + top: unset; + bottom: -22px; + z-index: 1; + border-radius: 0px 0px 15px 15px; + } &__text { color: white !important; @@ -1,32 +1,33 @@ -import { ChatSelect } from '@/src/main/components/chat_select' -import { ResetFilters } from '@/src/main/components/filters/reset_filters' -import { UniqInput } from '@/src/main/components/uniq_input' -import { useImageBot } from '@/src/entities/model-entity/model/use-image-bot' -import BotParamsMap from '@/src/features/bot-params/bot-params-map' -import { useImageBotCreateImage } from '@/src/features/image-bot-create-image' -import { useImagesBotFilters } from '@/src/features/image-bot-filters' -import { useImagesUniqInput } from '@/src/features/image-bot-input' -import { useImageBotPagination } from '@/src/features/image-bot-pagination' -import Title from '@/src/features/title/title' -import { useShowData, DrawerCustom, Error, Loader } from '@/src/shared' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' -import { ArrowDownScroll } from '@/src/shared/ui/icon-components/scroll-down-arrow' -import { useImagesPagination, ImageMessagesList } from '@/src/widgets/messages' -import { Layout } from '@/src/main/layout' +import { ChatSelect } from '#/app/components/chat_select' +import { ResetFilters } from '#/app/components/filters/reset_filters' +import { UniqInput } from '#/app/components/uniq_input' +import { useImageBot } from '#/entities/model-entity/model/use-image-bot' +import BotParamsMap from '#/features/bot-params/bot-params-map' +import { useImageBotCreateImage } from '#/features/image-bot-create-image' +import { useImagesBotFilters } from '#/features/image-bot-filters' +import { useImagesUniqInput } from '#/features/image-bot-input' +import { useImageBotPagination } from '#/features/image-bot-pagination' +import Title from '#/features/title/title' +import { useShowData, DrawerCustom, Error, Loader } from '#/shared' +import { useThemeAndDevice } from '#/shared/lib/hooks' +import { ArrowDownScroll } from '#/shared/ui/icon-components/scroll-down-arrow' +import { useImagesPagination, ImageMessagesList } from '#/widgets/messages' +import { Layout } from '#/app/layout' import { Box, Typography, Collapse, Stack } from '@mui/material' import { useSession } from 'next-auth/react' import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect, useMemo } from 'react' -import { c, getDeviceType, getOs } from '@/src/shared/lib/helpers' -import { Device, DeviceOs } from '@/src/shared/lib/types/entities' -import LockSvg from '@/src/assets/svg/lock.svg?react' -import BlockedSvg from '@/src/assets/svg/blocked.svg?react' +import { c, getDeviceType, getOs } from '#/shared/lib/helpers' +import { Device, DeviceOs } from '#/shared/lib/types/entities' +import LockSvg from '#/assets/svg/lock.svg?react' +import BlockedSvg from '#/assets/svg/blocked.svg?react' import styles from './image-model.module.scss' -import { SvgIcon } from '@/src/shared/ui/svg' +import { NextPageWithLayout } from '#/pages/_app' +import { SvgIcon } from '#/shared/ui/svg' -const ImageModelPage = () => { +const ImageModelPage: NextPageWithLayout = () => { const { query } = useRouter() const { @@ -92,7 +93,10 @@ const ImageModelPage = () => { const { theme } = useThemeAndDevice() return ( - <Layout device={deviceType} titlePage={botParams ? botParams.title : 'Загрузка...'}> + <> + <Head> + <title>{botParams ? botParams?.title : 'Загрузка...'} +
{ isComplete={loading} device={deviceType} images={messages} + getMessagesPagination={fetchMessages} /> <Box sx={{ @@ -231,14 +236,12 @@ const ImageModelPage = () => { <Box sx={{ padding: '30px', - height: `calc(100dvh - 116px - 61px - 10px - ${ - botParams?.tags.length !== 0 ? '45px' : '0px' - } ${botParams?.blocked ? '- 30px' : ''})`, + height: `calc(100dvh - 116px - 61px - 10px - 50px ${botParams?.blocked ? '- 20px' : ''})`, overflowY: 'scroll', overflowX: 'hidden', position: 'relative', }} - ref={mobileScrollContainer} + ref={mobileScrollContainer} className={'smallScroll'} > <div @@ -249,6 +252,7 @@ const ImageModelPage = () => { isComplete={loading} device={deviceType} images={messages} + getMessagesPagination={fetchMessages} /> </Box> <Stack @@ -451,7 +455,7 @@ const ImageModelPage = () => { </DrawerCustom> <Error error={error} open={Boolean(error)} /> </Box> - </Layout> + </> ) } @@ -4,21 +4,18 @@ import { Box, Typography } from '@mui/material' import CircularProgress from '@mui/material/CircularProgress' import { useSession } from 'next-auth/react' -import { useAppSelector } from '@/src/main/store/store' -import model_api from '@/src/shared/api/models/api' -import { ImageModelCard, IShortModel } from '@/src/entities/model-entity' +import { useAppSelector } from '#/app/store/store' +import model_api from '#/shared/api/models/api' +import { NextPageWithLayout } from '#/pages/_app' +import { ImageModelCard, IShortModel } from '#/entities/model-entity' import styles from './image-models.module.scss' -import { Layout } from '@/src/main/layout' -import { getDeviceType } from '@/src/shared/lib/helpers' -export const ImageModelsPage = () => { +export const ImageModelsPage: NextPageWithLayout = () => { const [bots, setBots] = useState<IShortModel[] | null>(null) const user = useAppSelector((state) => state.user) const { data, status } = useSession() - const deviceType = getDeviceType() - useEffect(() => { if (!data) return @@ -26,7 +23,7 @@ export const ImageModelsPage = () => { }, [status]) return ( - <Layout device={deviceType} titlePage='Изображения'> + <> <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}> Изображения </Typography> @@ -56,7 +53,7 @@ export const ImageModelsPage = () => { /> )} </div> - </Layout> + </> ) } @@ -0,0 +1 @@ +export { default as MainPage } from './main' @@ -0,0 +1,66 @@ +import * as React from 'react' +import { useMemo } from 'react' +import { Box, Stack, Typography } from '@mui/material' + +import { useAppSelector } from '#/app/store/store' +import styles from '#/app/styles/styles-pages/index-styles.module.scss' +import { ReferralBlock } from '#/shared' +import { getCurrentTimeAndGreeting } from '#/shared/lib/helpers/getCurrentDateForMainScreen' + +import { DemoInfo } from '#/shared/ui/demo-info' +import { SocialMedia } from '#/shared/ui/social-media-block' +import { Statistics } from '#/widgets/stats/stats' +import { NextPageWithLayout } from '#/pages/_app' +import { getDeviceType } from '#/shared/lib/helpers/get-type-device' + +export interface MainPageProps {} + +const Main: NextPageWithLayout<MainPageProps> = () => { + const { first_name, account_type } = useAppSelector((state) => state.user) + + const deviceType = getDeviceType() + + const [greeting, time] = useMemo(() => getCurrentTimeAndGreeting(first_name), [first_name]) + + return ( + <Box width='100%'> + <Box className={styles.wrap}> + <Typography className='title-main-gray'>{time}</Typography> + <Typography + sx={{ + fontSize: deviceType === 'desktop' ? '28px' : '24px', + fontWeight: 600, + }} + > + {greeting} + </Typography> + <Box className={styles.main}> + <Statistics deviceType={deviceType} /> + <Stack className={deviceType === 'desktop' ? styles.side_desk : styles.side_mobile}> + <DemoInfo /> + {account_type === 'regular' && <ReferralBlock />} + <SocialMedia /> + <Stack className='pd-30 bg-color-block border-radius-main mt-15' spacing={2}> + <Typography className='title-block'>Полезная статья</Typography> + <Box> + <Typography className='title-vspomogatel' sx={{ fontSize: '21px !important' }}> + {' '} + Что такое токен ? + </Typography> + <Typography className='text' sx={{ marginTop: '10px' }}> + Токен — это цифровая валюта для оплаты ИИ-продуктов и услуг. Юзеры + используют эту валюту, чтобы получить либо доступ к нейросетям, либо + результат генерации — текст, картинку и т.д. Еще токенами называют единицы + текста, на которых обучается нейросеть. Например, в предложении «‎Я люблю + шоколад» слова «‎Я», «‎люблю» и «шоколад» могут быть токенами. + </Typography> + </Box> + </Stack> + </Stack> + </Box> + </Box> + </Box> + ) +} + +export default Main @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as LoginPage } from './login' @@ -0,0 +1,11 @@ +.imagebox { + @media screen and (max-width: 1000px) { + display: none; + } +} + +.form{ + @media screen and (max-width: 1000px) { + width: 100%; + } +} \ No newline at end of file @@ -0,0 +1,388 @@ +import * as React from 'react' +import { useCookies } from 'react-cookie' +import { useForm } from 'react-hook-form' +import { SubmitErrorHandler } from 'react-hook-form/dist/types/form' +import { Box, Button, Link, Stack, TextField, Typography } from '@mui/material' +import CircularProgress from '@mui/material/CircularProgress' +import Image from 'next/image' +import { useRouter } from 'next/router' +import Router from 'next/router' +import Script from 'next/script' +import { signIn } from 'next-auth/react' +import { authTelegram } from '#/features/auth-telegram' +import { PasswordOptions } from '#/features/register-by-email/lib/constants' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppSelector } from '#/app/store/store' +import { emailOptions, Error } from '#/shared' +import { getDeviceType } from '#/shared/lib/helpers' +import { useShowData } from '#/shared/lib/hooks' +import { InputStyleDark, InputStyleLight } from '#/shared/ui/input' +import { NextPageWithLayout } from '#/pages/_app' +import * as Sentry from "@sentry/browser"; +import styles from './login.module.scss' + +const Login: NextPageWithLayout = () => { + const device = getDeviceType() + + const { query } = useRouter() + + const [, setCookie] = useCookies() + + React.useEffect(() => { + Object.entries(query).forEach(([key, value]) => setCookie(key, value)) + }, []) + + const { error, showError } = useShowData() + + const [loading, setLoading] = React.useState(false) + + const desktop = device === 'desktop' + + const { register, handleSubmit, getValues, reset } = useForm() + + async function onSubmit(data: any) { + setLoading(true) + + if (window.sessionStorage.__telegram__initParams !== '{}') { + await authTelegram(data.email, data.password) + setLoading(false) + reset() + return + } + + const checkTimeout = setTimeout(() => { + setLoading(false) + showError('Авторизация была отменена из-за долгого ожидания') + Sentry.captureMessage('ClientError: 25 sec timeout error in signIn') + }, 25000) + + const pushTimeout = setTimeout(async () => { + window.location.reload() + return + }, 30000) + + const res = await signIn('credentials', { + username: data.email, + password: data.password, + redirect: false, + callbackUrl: '/', + }) + + clearTimeout(checkTimeout) + clearTimeout(pushTimeout) + + if (res?.error) { + setLoading(false) + showError('Ошибка входа') + return + } + + await Router.push('/') + + setLoading(false) + + res?.error ? showError('Ошибка входа') : '' + } + + const checkError: SubmitErrorHandler<IEmailForms> = (data) => { + showError(Object.values(data)[0].message || 'Неверные данные') + } + + const handleKeyDown = (event: any) => { + if (event.key === 'Enter') { + handleSubmit(onSubmit)() + } + } + + const handleInputChangeTrim = (event: any) => { + event.target.value = event.target.value.trim() + } + + const theme = useAppSelector((state) => state.theme.theme) + + return ( + <> + <Script src='https://telegram.org/js/telegram-web-app.js' /> + <Stack + className={styles.container} + direction={desktop ? 'row' : 'column'} + alignItems='center' + justifyContent={desktop ? 'space-between' : 'center'} + sx={{ + overflow: 'hidden', + padding: 0, + margin: desktop ? 0 : '0px auto', + backgroundColor: theme === 'light' ? 'white' : '#303035', + }} + > + {!desktop && ( + <Image + style={{ marginTop: desktop ? 0 : '80px' }} + src={'/logo.svg'} + alt={''} + width={50} + height={50} + /> + )} + <Box + className={styles.form} + sx={{ + width: '50%', + height: '100vh', + display: 'flex', + justifyContent: 'center', + alignItems: desktop ? 'center' : 'start', + marginTop: desktop ? 0 : '10px', + }} + > + <form onKeyDown={handleKeyDown} autoComplete={'off'}> + <Box + sx={{ + width: '40vh', + height: '100%', + marginTop: desktop ? 0 : 5, + }} + onKeyDown={async (e) => { + if (e.key === 'Enter') { + await handleSubmit(onSubmit, checkError) + } + }} + > + <Stack + direction='column' + justifyContent='center' + alignItems='center' + spacing={2} + > + <Typography + sx={{ + color: theme === 'light' ? '#181C32' : '#E1E1E1', + lineHeight: '36.4px', + fontSize: '26px', + fontWeight: 'bold', + marginBottom: '20px', + }} + > + Вход + </Typography> + <Button + variant='contained' + onClick={() => + signIn('yandex', { + callbackUrl: '/', + }) + } + sx={{ + backgroundColor: + theme === 'dark' ? '#1C1C1E' : '#F1FAFF', + color: theme === 'dark' ? 'white' : '#2B2B42', + lineHeight: '20px', + fontSize: '16px', + fontWeight: '600px', + borderRadius: '13px', + textTransform: 'none', + width: '100%', + boxShadow: 'none', + height: 50, + '&:hover': { + backgroundColor: '#F1FAFF', + opacity: 1, + boxShadow: 'none', + }, + }} + > + <Image + src={'/svg/auth/yandex.svg'} + alt={''} + style={{ marginRight: 15 }} + width={26} + height={26} + /> + Войти с Яндекс ID + </Button> + <Typography sx={{ fontSize: '15px', color: '#A4AAB5' }}> + или email + </Typography> + <Typography + variant='body2' + sx={{ + color: '#A4AAB5', + lineHeight: '19.6px', + fontSize: '14px', + paddingTop: 1, + paddingRight: '90%', + }} + > + Email + </Typography> + <TextField + fullWidth + sx={ + theme === 'light' + ? { ...InputStyleLight } + : { ...InputStyleDark } + } + {...register('email', emailOptions)} + onChange={handleInputChangeTrim} + /> + + <Box + sx={{ + width: '100%', + justifyContent: 'space-between', + display: 'flex', + }} + > + <Box> + <Typography + variant='body2' + display='inline' + sx={{ + color: '#A4AAB5', + lineHeight: '19.6px', + fontSize: '14px', + }} + > + Пароль + </Typography> + </Box> + <Box> + <Typography + variant='body2' + display='inline' + sx={{ + color: '#8153FB', + lineHeight: '19.6px', + fontSize: '14px', + fontWeight: '500', + }} + > + <Link + href='/reset' + style={{ + textDecoration: 'none', + color: '#7F7DF3', + }} + > + Забыли пароль? + </Link> + </Typography> + </Box> + </Box> + + <TextField + type='password' + fullWidth + sx={ + theme === 'light' + ? { ...InputStyleLight } + : { ...InputStyleDark } + } + {...register('password', { + ...PasswordOptions, + })} + /> + <Box sx={{ height: '50px', width: '100%' }}> + {!loading ? ( + <Button + variant='contained' + style={{ + marginTop: '15px', + }} + sx={{ + backgroundColor: '#7F7DF3', + color: '#FFFFFF', + lineHeight: '20px', + fontSize: '16px', + fontWeight: '600px', + borderRadius: '13px', + textTransform: 'none', + width: '100%', + height: 48, + '&:hover': { + backgroundColor: '#7F7DF3', + opacity: [0.9, 0.8, 0.7], + }, + }} + onClick={handleSubmit(onSubmit, checkError)} + > + Войти + </Button> + ) : ( + <Box + style={{ + margin: '25px auto', + display: 'flex', + justifyContent: 'center', + }} + > + <CircularProgress + size={30} + thickness={5} + sx={{ color: '#7F7DF3' }} + /> + </Box> + )} + <Box + sx={{ + width: '100%', + textAlign: 'center', + marginTop: '15px', + }} + > + <Link + href='/register' + style={{ + textDecoration: 'none', + color: '#7F7DF3', + }} + > + Зарегистрироваться + </Link> + </Box> + </Box> + <Error error={error} open={Boolean(error)} /> + </Stack> + </Box> + </form> + </Box> + {desktop && ( + <Box + className={styles.imagebox} + sx={{ + width: '50%', + height: '100vh', + backgroundColor: '#8280FF', + overflow: 'hidden', + position: 'relative', + }} + > + <Image + style={{ position: 'absolute', top: 35, left: 35 }} + src={'/svg/logo.svg'} + alt={''} + width={42} + height={42} + /> + <Image + src={'/images/dashboard.png'} + alt={''} + width={900} + height={1002} + style={{ + borderRadius: '20px', + position: 'relative', + zIndex: 1000, + bottom: -260, + right: -390, + border: '10px solid rgba(255, 255, 255, 0.30)', + transform: 'rotate(-13deg)', + }} + /> + </Box> + )} + </Stack> + </> + ) +} +export default Login @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as NotFoundPage } from './not-found' @@ -0,0 +1,57 @@ +import React, { useEffect } from 'react' +import { Box, Typography } from '@mui/material' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { Layout } from '#/app/layout' +import { useAppSelector } from '#/app/store/store' + +const NotFoundPage = () => { + const theme = useAppSelector((state) => state.theme.theme) + + const { push } = useRouter() + + useEffect(() => { + const timeout = setTimeout(() => push('/'), 7000) + + return () => clearTimeout(timeout) + }, []) + + return ( + <Layout titlePage={'Неизвестная страница'} isAuthPage={true}> + <Box display='flex' justifyContent='center' alignItems='center' sx={{ width: '100%', height: '90vh' }}> + <Box sx={{ width: '410px', textAlign: 'center' }}> + <Typography + sx={{ + fontSize: '23px', + color: theme === 'light' ? 'dark' : 'white', + }} + > + Ошибка 404 + </Typography> + <Typography + sx={{ + fontSize: '19px', + color: theme === 'light' ? 'dark' : 'white', + marginTop: 1.5, + }} + > + К сожалению, такая страница не найдена 😢 + </Typography> + <Link href='/'> + <Typography + sx={{ + fontSize: '18px', + marginTop: 2, + color: '#7F7DF3', + }} + > + Вернуться на главную + </Typography> + </Link> + </Box> + </Box> + </Layout> + ) +} + +export default NotFoundPage @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as ReferralPage } from './referral' @@ -0,0 +1,42 @@ +import React, { useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { Box } from '@mui/material' +import CircularProgress from '@mui/material/CircularProgress' +import { useRouter } from 'next/router' + +import { addReferral } from '#/entities/user-account/model/user-type-slice' +import { NextPageWithLayout } from '#/pages/_app' + +export interface ReferralRegisterProps { + email: string +} + +const ReferralRegister: NextPageWithLayout<ReferralRegisterProps> = ({ email }) => { + const dispatch = useDispatch() + const { push } = useRouter() + + useEffect(() => { + localStorage.setItem('referral', email) + dispatch(addReferral(email)) + push('/register') + }, []) + + return ( + <Box sx={{ width: '100vw', height: '100vh', background: '#4b4b4b', position: 'relative' }}> + <CircularProgress + size={50} + thickness={5} + sx={{ + color: '#7F7DF3', + position: 'absolute', + top: '40%', + right: 0, + left: 0, + margin: '0 auto', + }} + /> + </Box> + ) +} + +export default ReferralRegister @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as RegisterPage } from './register' @@ -0,0 +1,11 @@ +.imagebox { + @media screen and (max-width: 1000px) { + display: none; + } +} + +.form{ + @media screen and (max-width: 1000px) { + width: 100%; + } +} \ No newline at end of file @@ -0,0 +1,205 @@ +import * as React from 'react' +import { useEffect, useState } from 'react' +import { useCookies } from 'react-cookie' +import { useDispatch } from 'react-redux' +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' +import Image from 'next/image' +import { useRouter } from 'next/router' +import { signIn } from 'next-auth/react' + +import { addReferral } from '#/entities/user-account/model/user-type-slice' +import { RegisterEmailForm } from '#/features/register-by-email' +import { useAppSelector } from '#/app/store/store' +import { TDeviceProp } from '#/shared/lib/types/entities' +import { getDeviceType } from '#/shared/lib/helpers' +import { NextPageWithLayout } from '#/pages/_app' + +import styles from './register.module.scss' + +const Register: NextPageWithLayout = () => { + const device = getDeviceType() + + const desktop = device === 'desktop' + + const [success, setSuccess] = useState(false) + const [isReferral, setIsReferral] = useState<boolean>(true) + + const theme = useAppSelector((state) => state.theme.theme) + const user = useAppSelector((state) => state.user) + const dispatch = useDispatch() + + const { push } = useRouter() + + const onSuccessRegister = () => { + setSuccess(true) + setTimeout(() => push('/login'), 15000) + } + + useEffect(() => { + if (localStorage.getItem('referral')) { + setIsReferral(false) + dispatch(addReferral(localStorage.getItem('referral'))) + console.log(localStorage.getItem('referral')) + } else { + setIsReferral(true) + } + }, []) + + return ( + <Stack + direction={desktop ? 'row' : 'column'} + alignItems='center' + justifyContent={desktop ? 'space-between' : 'center'} + sx={{ + overflow: 'hidden', + padding: 0, + margin: desktop ? 0 : '0px auto', + backgroundColor: theme === 'light' ? 'white' : '#303035', + }} + > + {!desktop && ( + <Image + style={{ marginTop: desktop ? 0 : '20px' }} + src={'/logo.svg'} + alt={''} + width={50} + height={50} + /> + )} + <Box + className={styles.form} + sx={{ + width: desktop ? '50%' : '100%', + height: '100vh', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginTop: desktop ? 0 : '10px', + }} + > + <Box + sx={{ + width: '40vh', + height: '100%', + marginTop: desktop ? 0 : 2, + justifyContent: 'center', + alignItems: 'center', + display: 'flex', + }} + > + {success ? ( + <Box textAlign='center'> + <Typography className='text'> + Вы успешно зарегистрированы. + <br /> + Мы отправили письмо для верификации на ваш email. В случае отсутствия, проверьте + папку Спам. + <br /> + <br /> + Если вы регистрировались с корпоративной почты и письмо-подтверждение вам не + пришло, рекомендуем зарегистрироваться с личной почты + </Typography> + </Box> + ) : ( + <Stack direction='column' justifyContent='center' alignItems='center' spacing={2}> + <Typography + sx={{ + color: theme === 'light' ? '#181C32' : '#E1E1E1', + lineHeight: '36.4px', + fontSize: '26px', + fontWeight: 'bold', + marginBottom: '20px', + }} + > + Регистрация + </Typography> + + {user.referral === '' && isReferral && ( + <> + <Button + variant='contained' + onClick={() => + signIn('yandex', { + callbackUrl: '/', + }) + } + sx={{ + backgroundColor: '#F1FAFF', + color: '#7F7DF3', + lineHeight: '20px', + fontSize: '16px', + fontWeight: '600px', + borderRadius: '13px', + textTransform: 'none', + width: '100%', + boxShadow: 'none', + height: 50, + '&:hover': { + backgroundColor: '#F1FAFF', + opacity: 1, + boxShadow: 'none', + }, + }} + > + <Image + src={'/svg/auth/yandex.svg'} + alt={''} + style={{ marginRight: 15 }} + width={26} + height={26} + /> + Войти с Яндекс ID + </Button> + <Typography sx={{ fontSize: '15px', color: '#A4AAB5' }}> + или email + </Typography> + </> + )} + <RegisterEmailForm successLogin={onSuccessRegister} /> + </Stack> + )} + </Box> + </Box> + {desktop && ( + <Box + className={styles.imagebox} + sx={{ + width: '50%', + height: '100vh', + backgroundColor: '#8280FF', + overflow: 'hidden', + position: 'relative', + }} + > + <Image + style={{ position: 'absolute', top: 35, left: 35 }} + src={'/svg/logo.svg'} + alt={''} + width={42} + height={42} + /> + <Image + src={'/images/dashboard.png'} + alt={''} + width={900} + height={1002} + style={{ + borderRadius: '20px', + position: 'relative', + zIndex: 1000, + bottom: -260, + right: -390, + border: '10px solid rgba(255, 255, 255, 0.30)', + transform: 'rotate(-13deg)', + }} + /> + </Box> + )} + </Stack> + ) +} + +export default Register @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as RegisterBusinessPage } from './register-business' @@ -0,0 +1,8 @@ +import { Stepper } from '#/features/register-business' +import { NextPageWithLayout } from '#/pages/_app' + +const Register: NextPageWithLayout = () => { + return <Stepper /> +} + +export default Register @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as ReportsMobilePage } from './reports-mobile' @@ -0,0 +1,260 @@ +import React, { ChangeEvent } from 'react' +import AttachFileIcon from '@mui/icons-material/AttachFile' +import CloseIcon from '@mui/icons-material/Close' +import { Box, Button, Stack, TextField, Typography } from '@mui/material' +import axios from 'axios' +import { useSession } from 'next-auth/react' + +import { useAppSelector } from '#/app/store/store' +import { API_URL } from '#/shared/lib/constants/constants' +import { NextPageWithLayout } from '#/pages/_app' + +const styleSend = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -65%)', + width: 300, + height: 'auto', + bgcolor: 'background.paper', + borderRadius: 5, + boxShadow: 16, + p: 4, + textAlign: 'center', +} + +const styleSendDark = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -65%)', + width: 300, + height: 'auto', + bgcolor: '#2B2828', + borderRadius: 5, + boxShadow: 16, + p: 4, + textAlign: 'center', +} + +const styleMobile = { + width: '100%', + position: 'absolute' as 'absolute', + top: '35%', + left: '50%', + transform: 'translate(-50%, -50%)', + p: 4, + zIndex: 99, +} + +const ReportsMobile: NextPageWithLayout = () => { + const [errorMessage, setErrorMessage] = React.useState<string>('') + + const [error, setError] = React.useState(false) + + const [isSend, setIsSend] = React.useState(false) + + const [file, setFile] = React.useState<File>() + + const { data: session } = useSession() + + const theme = useAppSelector((state) => state.theme.theme) + + const email = useAppSelector((state) => state.user.email) + + const sendError = async () => { + if (errorMessage.trim() === '') { + setError(true) + setTimeout(() => setError(false), 3000) + return + } + + let formData: any = new FormData() + + formData.append('report_text', `${email}: ${errorMessage}`) //append the values with key, value pair + if (file) { + formData.append('images', file) + } + + try { + const { status } = await axios.post(API_URL + '/reports/', formData, { + headers: { + 'content-type': 'multipart/form-data ', + 'content-length': file ? `${file.size}` : '', + Authorization: `Bearer ${session?.access}`, + }, + }) + + if (status === 201) { + setErrorMessage('') + setIsSend(true) + setTimeout(() => { + setIsSend(false) + }, 4000) + } + } catch (err) { + setError(true) + setTimeout(() => setError(false), 3000) + } + } + + const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { + if (e.target.files) { + setFile(e.target.files[0]) + } + } + + return ( + <> + {isSend ? ( + <Box sx={theme === 'light' ? styleSend : styleSendDark}> + <Typography sx={{ color: '#22B47F', fontSize: '23px' }}>Спасибо!</Typography> + <Typography sx={{ fontSize: '17px', marginTop: 2 }}> + Ваше сообщение направлено нашим специалистам. Ответ придет на почту, указанную при + регистрации + </Typography> + </Box> + ) : ( + <Box sx={styleMobile}> + <Typography + variant='h6' + component='h2' + sx={{ + color: theme === 'light' ? '#5A5A5A' : '#E1E1E1', + lineHeight: '28.5px', + fontSize: '21px', + fontWeight: '600px', + }} + > + Сообщить об ошибке + </Typography> + <TextField + error={error} + id='outlined-multiline-static' + multiline + rows={4} + placeholder='Опишите детально возникшую проблему' + fullWidth + style={{ + color: theme === 'light' ? '#272727' : '#E1E1E1', + }} + onChange={(evt) => setErrorMessage(evt.target.value)} + InputProps={{ + startAdornment: ( + <> + <Button + variant='contained' + component='label' + sx={{ + position: 'absolute', + padding: 0, + bottom: 13, + left: -10, + backgroundColor: 'transparent', + border: 'none', + boxShadow: 'none', + margin: 0, + rotate: '30deg', + '&:hover': { + backgroundColor: 'transparent', + boxShadow: 'none', + }, + }} + > + <AttachFileIcon + color='info' + sx={{ + cursor: 'pointer', + color: '#868686', + width: 25, + height: 25, + }} + /> + <input + onChange={handleFileChange} + type='file' + hidden + accept='.jpg,.jpeg,.png' + /> + </Button> + </> + ), + }} + sx={{ + marginTop: 2.5, + marginBottom: 1.2, + fontSize: 13, + '& label': { color: 'red' }, + backgroundColor: theme === 'light' ? 'transparent' : '#3D3D3D', + input: { + color: theme === 'light' ? '#272727' : '#E1E1E1', + }, + }} + /> + {file && ( + <Box sx={{ display: 'flex', alignItems: 'center' }}> + <Typography sx={{ fontSize: '15px' }}> + Добавлено 1 изображение: {file.name} + </Typography> + <CloseIcon + onClick={() => setFile(undefined)} + sx={{ + width: '18px', + height: '18px', + cursor: 'pointer', + marginLeft: '3px', + marginTop: '2px', + }} + /> + </Box> + )} + <Stack + direction={'column-reverse'} + justifyContent='flex-end' + alignItems='flex-end' + spacing={1} + sx={{ marginTop: 1.5 }} + > + <Button + fullWidth + sx={{ + color: theme === 'light' ? '#5A5A5A' : '#D0D0D0', + backgroundColor: theme === 'light' ? '#E8E8E8' : '#5D5A5A', + fontWeight: 500, + fontSize: '16px', + textTransform: 'none', + padding: '8px 12px', + borderRadius: '13px', + border: 'none', + cursor: 'pointer', + marginTop: 1, + }} + > + Отмена + </Button> + <Button + fullWidth + variant='contained' + onClick={sendError} + sx={{ + color: 'white', + backgroundColor: '#7F7DF3', + fontWeight: 500, + fontSize: '16px', + textTransform: 'none', + padding: '7px 12px', + borderRadius: '13px', + border: 'none', + cursor: 'pointer', + }} + > + Отправить + </Button> + </Stack> + </Box> + )} + </> + ) +} + +export default ReportsMobile @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as ResetPage } from './reset' @@ -0,0 +1,170 @@ +import * as React from 'react' +import { useState } from 'react' +import { Box, Card, CardMedia, Link, Stack, Typography } from '@mui/material' +import Button from '@mui/material/Button' +import axios from 'axios' +import Image from 'next/image' +import Router from 'next/router' +import { Layout } from '#/app/layout' +import { useAppSelector } from '#/app/store/store' +import { Error, Input } from '#/shared' +import { API_URL } from '#/shared/lib/constants/constants' +import { getDeviceType, getRandomImage } from '#/shared/lib/helpers' +import { NextPageWithLayout } from '#/pages/_app' + +const Reset: NextPageWithLayout = () => { + const device = getDeviceType() + + const [input_email, set_Email] = useState<string>('') + const [isSend, setIsSend] = useState(false) + const [isError, setIsError] = React.useState(false) + const sendMail = () => { + axios.post(API_URL + '/auth/update-pass', { + email: input_email, + }) + .then(function (response) { + if (response.status === 200) { + setIsSend(true) + setTimeout(() => Router.push('/login'), 10000) + } + }) + .catch(function (error) { + setIsError(true) + setTimeout(() => setIsError(false), 4000) + }) + } + + const desktop = device === 'desktop' + + const refImage = React.useRef(getRandomImage()) + + const theme = useAppSelector((state) => state.theme.theme) + + return ( + <Stack + direction={desktop ? 'row' : 'column'} + alignItems='center' + justifyContent={desktop ? 'space-between' : 'center'} + sx={{ + padding: 0, + margin: desktop ? 0 : '60px auto', + backgroundColor: theme === 'light' ? 'white' : '#373737', + }} + > + {desktop && ( + <Card> + <CardMedia + sx={{ height: '100vh', width: '600px' }} + image={`/login_images/${refImage.current}`} + > + <Stack + direction='column-reverse' + justifyContent='flex-start' + alignItems='flex-start' + spacing={2} + sx={{ + wdith: '100%', + height: '100%', + paddingBottom: 3, + paddingLeft: 3, + }} + > + <Typography + variant='body2' + sx={{ + color: '#FFFFFF', + lineHeight: '23.8px', + fontSize: '17px', + fontWeight: '400px', + }} + > + Midjourney + </Typography> + <Typography + variant='body2' + sx={{ + color: '#FFFFFF', + lineHeight: '28px', + fontSize: '20px', + fontWeight: '600px', + }} + > + by honeynek + </Typography> + </Stack> + </CardMedia> + </Card> + )} + {!isSend ? ( + <Box + sx={{ + width: '70%', + height: '100%', + marginTop: desktop ? 0 : 25, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }} + > + <Stack + direction='column' + justifyContent='center' + alignItems='center' + spacing={0} + sx={{ width: '350px' }} + > + <Typography sx={{ fontSize: 25 }}>Забыли пароль?</Typography> + + <Typography marginTop={1.5} sx={{ fontSize: 16 }}> + Введите адрес электронной почты, которую вы использовали для регистрации в сервисе + </Typography> + <Box textAlign='left' width='100%' marginTop={1.5}> + <Typography marginLeft={0.5} className='title-main-gray'> + Email + </Typography> + </Box> + <Input onChange={(event) => set_Email(event.target.value)} type={'email'} fullWidth /> + <Link + style={{ + position: 'absolute', + top: 45, + right: '7%', + cursor: 'pointer', + fontSize: 17, + }} + href={'/'} + > + <Image src={'/x-mark.svg'} width={18} height={18} alt={''} /> + </Link> + <Box marginTop={2} width='100%'> + <Button variant='contained' fullWidth className='btn-classic' onClick={sendMail}> + Восстановить аккаунт + </Button> + </Box> + <Error open={isError} error={'Аккаунт с такой почтой не найдено'} /> + </Stack> + </Box> + ) : ( + <Box + sx={{ + width: '40vh', + height: '100%', + marginRight: '25%', + }} + > + <Typography + sx={{ + fontSize: 21, + textAlign: 'center', + color: theme === 'light' ? '#272727' : '#E1E1E1', + }} + > + Письмо для восстановления пароля отправлено вам на почту! + </Typography> + </Box> + )} + </Stack> + ) +} + +export default Reset @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1 @@ +export { default as WhisperPage } from './whisper' @@ -0,0 +1,16 @@ +import React from 'react' +import { Box } from '@mui/material' +import { WhisperWidget } from '#/widgets/whisper' +import { NextPageWithLayout } from '#/pages/_app' + +const Whisper: NextPageWithLayout = () => { + return ( + <Box sx={{ width: '100%' }}> + <Box sx={{ width: '50%', margin: '20px auto' }}> + <WhisperWidget /> + </Box> + </Box> + ) +} + +export default Whisper @@ -0,0 +1 @@ +export * from './ui' @@ -1,5 +1,4 @@ .screenWrap { - } .screen { @@ -3,14 +3,20 @@ import { Box, Typography } from '@mui/material' import Image from 'next/image' import Link from 'next/link' -import { ButtonUI } from '@/src/shared' +import { ButtonUI } from '#/shared' import styles from './screen-for-inactive.module.scss' export const ScreenForInactive = () => { return ( <Box className={styles.screenWrap}> <Box className={styles.screen}> - <Image className={styles.screenIcon} src={'/svg/business/profile.svg'} width={31} height={46} alt={''} /> + <Image + className={styles.screenIcon} + src={'/svg/business/profile.svg'} + width={31} + height={46} + alt={''} + /> <Typography className={styles.screenTitle}>Корпоративный аккаунт</Typography> <Typography className={styles.screenDescription}>Для бизнеса и команд</Typography> <Box className={styles.screenIcons}> @@ -5,24 +5,24 @@ import axios from 'axios' import { Dayjs } from 'dayjs' import { useSession } from 'next-auth/react' -import { getAll, ResponseAllInfo } from '@/src/entities/user-account/model/user-type-slice' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { useAppSelector } from '@/src/main/store/store' -import styles from '@/src/main/styles/business.module.scss' -import { InputStyleDark, InputStyleLight } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import { DateInput } from '@/src/shared/ui/date-input/date-input' -import { Info } from '@/src/widgets/business-info' -import { getInfo, InfoBusiness } from '@/src/widgets/business-info/api/get-info' -import { ModelsList } from '@/src/widgets/business-models' -import styles2 from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import { PersonsList } from '@/src/widgets/business-persons' -import { getPersons } from '@/src/widgets/business-persons/api/get-persons' -import { BusinessGroups } from '@/src/widgets/business-persons/ui/persons-list/business-group' -import { IpList } from '@/src/widgets/business-persons/ui/persons-list/ip-list' -import { LogList } from '@/src/widgets/business-persons/ui/persons-list/log-list' -import { SecurityList } from '@/src/widgets/business-persons/ui/persons-list/security-list' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +import { getAll, ResponseAllInfo } from '#/entities/user-account/model/user-type-slice' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { useAppSelector } from '#/app/store/store' +import styles from '#/app/styles/business.module.scss' +import { InputStyleDark, InputStyleLight } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import { DateInput } from '#/shared/ui/date-input/date-input' +import { Info } from '#/widgets/business-info' +import { getInfo, InfoBusiness } from '#/widgets/business-info/api/get-info' +import { ModelsList } from '#/widgets/business-models' +import styles2 from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import { PersonsList } from '#/widgets/business-persons' +import { getPersons } from '#/widgets/business-persons/api/get-persons' +import { BusinessGroups } from '#/widgets/business-persons/ui/persons-list/business-group' +import { IpList } from '#/widgets/business-persons/ui/persons-list/ip-list' +import { LogList } from '#/widgets/business-persons/ui/persons-list/log-list' +import { SecurityList } from '#/widgets/business-persons/ui/persons-list/security-list' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' type ListType = 'personal' | 'security' @@ -63,10 +63,13 @@ export default function BusinessHost() { } const download = () => { - axios.get(API_URL + `/auth/business-host/download-expenses?type=employees&from_date=${fromDate}&to_date=${toDate}`, { - responseType: 'arraybuffer', - headers: { Authorization: `Bearer ${data?.access}` }, - }).then((res) => { + axios.get( + API_URL + `/auth/business-host/download-expenses?type=employees&from_date=${fromDate}&to_date=${toDate}`, + { + responseType: 'arraybuffer', + headers: { Authorization: `Bearer ${data?.access}` }, + } + ).then((res) => { const file = new Blob([res.data], { type: 'application/ms-excel;charset=utf-8', }) @@ -80,7 +83,11 @@ export default function BusinessHost() { } useEffect(() => { - axios.put(API_URL + '/auth/business-host', { token_cap_enabled: mailing }, { headers: { Authorization: `Bearer ${data?.access}` } }) + axios.put( + API_URL + '/auth/business-host', + { token_cap_enabled: mailing }, + { headers: { Authorization: `Bearer ${data?.access}` } } + ) }, [mailing]) return ( @@ -95,7 +102,11 @@ export default function BusinessHost() { <Box className={styles2.title2}> <Box> <Typography className={styles.name}>Затраты</Typography> - <ArrowUpOrDown directionY={showPersons} onClick={() => setShowPersons((prev) => !prev)} className={styles2.arrow} /> + <ArrowUpOrDown + directionY={showPersons} + onClick={() => setShowPersons((prev) => !prev)} + className={styles2.arrow} + /> </Box> </Box> {showPersons && ( @@ -148,7 +159,11 @@ const MailingBlock = ({ info, mailing }: { info: InfoBusiness | null | undefined } const req = async (mailingArr: string[]) => { - await axios.put(API_URL + '/auth/business-host', { token_cap_emails: mailingArr }, { headers: { Authorization: `Bearer ${data?.access}` } }) + await axios.put( + API_URL + '/auth/business-host', + { token_cap_emails: mailingArr }, + { headers: { Authorization: `Bearer ${data?.access}` } } + ) } return ( @@ -175,7 +190,11 @@ const MailingBlock = ({ info, mailing }: { info: InfoBusiness | null | undefined placeholder={'Введите почту'} onChange={(e) => setNewMail(e.target.value)} fullWidth - sx={theme === 'light' ? { ...InputStyleLight } : { ...InputStyleDark }} + sx={ + theme === 'light' + ? { ...InputStyleLight } + : { ...InputStyleDark } + } /> </Box> @@ -1,6 +1,6 @@ import axios from 'axios' -import { API_URL } from '@/src/shared/lib/constants' +import { API_URL } from '#/shared/lib/constants' export type InfoBusiness = { company_name: string @@ -4,10 +4,10 @@ import Switch from '@mui/material/Switch' import axios from 'axios' import { useSession } from 'next-auth/react' -import { ResponseAllInfo } from '@/src/entities/user-account/model/user-type-slice' -import { useAppSelector } from '@/src/main/store/store' -import { Balance } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseAllInfo } from '#/entities/user-account/model/user-type-slice' +import { useAppSelector } from '#/app/store/store' +import { Balance } from '#/shared' +import { API_URL } from '#/shared/lib/constants' import { getInfo, InfoBusiness } from '../../api/get-info' @@ -52,7 +52,13 @@ export const Info = ({ <Box className={styles.wrap}> <Box className={styles.left}> {!security ? <Typography className={styles.title}>{data?.company_name}</Typography> : <></>} - <Typography className={styles.role} sx={{ fontSize: !security ? '17px' : '21px', fontWeight: !security ? '400' : '600' }}> + <Typography + className={styles.role} + sx={{ + fontSize: !security ? '17px' : '21px', + fontWeight: !security ? '400' : '600', + }} + > Роль:{' '} {type === 'business_host' ? 'Владелец' @@ -65,7 +71,9 @@ export const Info = ({ {type !== 'business_security' ? ( <Typography className={styles.role} sx={{ fontSize: '17px', fontWeight: '400' }}> Режим валидации по IP :{' '} - <Typography component='span' sx={{ color: '#7f7df3' }}>{data?.is_ip_whitelist_enabled ? 'включен' : 'выключен'}</Typography> + <Typography component='span' sx={{ color: '#7f7df3' }}> + {data?.is_ip_whitelist_enabled ? 'включен' : 'выключен'} + </Typography> </Typography> ) : ( <></> @@ -1,7 +1,7 @@ import axios from 'axios' -import { AllowedModels } from '@/src/features/changeAccessToModel' -import { API_URL } from '@/src/shared/lib/constants' +import { AllowedModels } from '#/features/changeAccessToModel' +import { API_URL } from '#/shared/lib/constants' export const getModels = async (token?: string): Promise<AllowedModels[] | []> => { try { const { data } = await axios.get<AllowedModels[]>(API_URL + '/auth/business-host/allowed-models', { @@ -9,9 +9,9 @@ import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import { useSession } from 'next-auth/react' -import { AllowedModels, SwitchAccess } from '@/src/features/changeAccessToModel' -import { Search, translateTypeModel } from '@/src/shared' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +import { AllowedModels, SwitchAccess } from '#/features/changeAccessToModel' +import { Search, translateTypeModel } from '#/shared' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' import { getModels } from '../../api/get-models' @@ -35,7 +35,11 @@ export const ModelsList = () => { <Box className={styles.title}> <Typography className={styles.name}>Нейросети</Typography> <Typography className={styles.count}>{models.length}</Typography> - <ArrowUpOrDown directionY={showModels} onClick={() => setShowModels((prev) => !prev)} className={styles.arrow} /> + <ArrowUpOrDown + directionY={showModels} + onClick={() => setShowModels((prev) => !prev)} + className={styles.arrow} + /> </Box> {showModels && ( @@ -52,9 +56,18 @@ export const ModelsList = () => { <TableBody> {models.map((model) => { return ( - <TableRow key={model.title} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + key={model.title} + sx={{ + '&:last-child td, &:last-child th': { + border: 0, + }, + }} + > <TableCell align='left'>{model.title}</TableCell> - <TableCell align='center'>{translateTypeModel(model.model_type)}</TableCell> + <TableCell align='center'> + {translateTypeModel(model.model_type)} + </TableCell> <TableCell className={styles.tableSwitch} align='center'> <Box> <SwitchAccess @@ -1,8 +1,8 @@ import axios from 'axios' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { ResponseGetBusinessGroups, ResponseGetIpList } from '@/src/features/invite-person-in-business/model/types' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { ResponseGetBusinessGroups, ResponseGetIpList } from '#/features/invite-person-in-business/model/types' +import { API_URL } from '#/shared/lib/constants' export const getBusinessGroups = async (token?: string): Promise<ResponseGetBusinessGroups[] | null> => { try { @@ -1,8 +1,8 @@ import axios from 'axios' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { ResponseGetIpList } from '@/src/features/invite-person-in-business/model/types' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { ResponseGetIpList } from '#/features/invite-person-in-business/model/types' +import { API_URL } from '#/shared/lib/constants' export const getIpList = async (token?: string): Promise<ResponseGetIpList | null> => { try { @@ -1,8 +1,8 @@ import axios from 'axios' import { Dayjs } from 'dayjs' -import { ResponseGetLogsList } from '@/src/features/invite-person-in-business/model/types' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseGetLogsList } from '#/features/invite-person-in-business/model/types' +import { API_URL } from '#/shared/lib/constants' export const getLogsList = async ( offset: number, @@ -1,7 +1,7 @@ import axios from 'axios' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { API_URL } from '@/src/shared/lib/constants' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { API_URL } from '#/shared/lib/constants' export const getPersons = async (token?: string, security?: boolean): Promise<ResponseGetPersons[] | null> => { try { @@ -1,4 +1,4 @@ -import { AcceptanceStatus } from '@/src/features/invite-person-in-business/model/types' +import { AcceptanceStatus } from '#/features/invite-person-in-business/model/types' export const translateEmailStatus = (status: AcceptanceStatus) => { switch (status) { @@ -12,22 +12,24 @@ import { filter } from 'lodash' import Image from 'next/image' import { useSession } from 'next-auth/react' -import { AddBusinessGroup } from '@/src/features/business-group' -import { ChangeBusinessGroup } from '@/src/features/business-group/ui/change-business-group' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { ResponseGetBusinessGroups } from '@/src/features/invite-person-in-business/model/types' -import { XMark } from '@/src/features/remove-person' -import { Search } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import styles from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import { getBusinessGroups } from '@/src/widgets/business-persons/api/get-businessGroups' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +import { AddBusinessGroup } from '#/features/business-group' +import { ChangeBusinessGroup } from '#/features/business-group/ui/change-business-group' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { ResponseGetBusinessGroups } from '#/features/invite-person-in-business/model/types' +import { XMark } from '#/features/remove-person' +import { Search } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import { getBusinessGroups } from '#/widgets/business-persons/api/get-businessGroups' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' import { getIpList } from '../../api/get-ipList' export const BusinessGroups = ({ company_uid }: { company_uid?: string }) => { const [businessGroups, setBusinessGroups] = useState<ResponseGetBusinessGroups[] | null>() - const [searchGroup, setSearchGroup] = useState<ResponseGetBusinessGroups[] | undefined | null | undefined>(businessGroups ? businessGroups : []) + const [searchGroup, setSearchGroup] = useState<ResponseGetBusinessGroups[] | undefined | null | undefined>( + businessGroups ? businessGroups : [] + ) const [businessAddModal, setBusinessAddModal] = useState<boolean>(false) const [showPersons, setShowPersons] = useState(true) const [search, setSearch] = useState('') @@ -96,7 +98,11 @@ export const BusinessGroups = ({ company_uid }: { company_uid?: string }) => { <Box className={styles.title2}> <Box> <Typography className={styles.name}>Бизнес-группы</Typography> - <ArrowUpOrDown directionY={showPersons} onClick={() => setShowPersons((prev) => !prev)} className={styles.arrow} /> + <ArrowUpOrDown + directionY={showPersons} + onClick={() => setShowPersons((prev) => !prev)} + className={styles.arrow} + /> </Box> <Typography onClick={() => { @@ -129,12 +135,22 @@ export const BusinessGroups = ({ company_uid }: { company_uid?: string }) => { {searchGroup && searchGroup.length !== 0 ? ( searchGroup?.map((group) => { return ( - <TableRow key={group.uid} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + key={group.uid} + sx={{ + '&:last-child td, &:last-child th': { border: 0 }, + }} + > <TableCell className={styles.tableEmail} align='left'> {group.title} </TableCell> - <TableCell className={styles.tableEmail} align='center'> - {group.token_limit ? group.token_limit.split('.')[0] : 'Не указано'} + <TableCell + className={styles.tableEmail} + align='center' + > + {group.token_limit + ? group.token_limit.split('.')[0] + : 'Не указано'} </TableCell> <TableCell @@ -162,7 +178,13 @@ export const BusinessGroups = ({ company_uid }: { company_uid?: string }) => { ) }) ) : ( - <TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + sx={{ + '&:last-child td, &:last-child th': { + border: 0, + }, + }} + > <TableCell className={styles.tableEmail} align='center'> Не найдено группы с таким именем </TableCell> @@ -9,11 +9,11 @@ import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import { useSession } from 'next-auth/react' -import { ResponseGetPersons } from '@/src/features/invite-person-in-business' -import { ResponseGetIpList } from '@/src/features/invite-person-in-business/model/types' -import { Search, Success, useShowData } from '@/src/shared' -import styles from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +import { ResponseGetPersons } from '#/features/invite-person-in-business' +import { ResponseGetIpList } from '#/features/invite-person-in-business/model/types' +import { Search, Success, useShowData } from '#/shared' +import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' import { getIpList } from '../../api/get-ipList' @@ -62,13 +62,22 @@ export const IpList = () => { ) : ( <></> )} - <ArrowUpOrDown directionY={showPersons} onClick={() => setShowPersons((prev) => !prev)} className={styles.arrow} /> + <ArrowUpOrDown + directionY={showPersons} + onClick={() => setShowPersons((prev) => !prev)} + className={styles.arrow} + /> </Box> </Box> {showPersons && ( <Box className={styles.listWrap}> <Box className={styles.search}> - <Search value={search} onChange={(e) => setSearch(e.target.value)} fullWidth placeholder='Введите IP-адрес' /> + <Search + value={search} + onChange={(e) => setSearch(e.target.value)} + fullWidth + placeholder='Введите IP-адрес' + /> </Box> <TableContainer className={styles.table} component={Paper}> <Table sx={{ minWidth: 650 }} aria-label='simple table'> @@ -81,7 +90,12 @@ export const IpList = () => { {ipList && searchIp && searchIp.length > 0 ? ( searchIp.map((ip) => { return ( - <TableRow key={ip} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + key={ip} + sx={{ + '&:last-child td, &:last-child th': { border: 0 }, + }} + > <TableCell className={styles.tableEmail} align='left'> {ip} </TableCell> @@ -89,7 +103,13 @@ export const IpList = () => { ) }) ) : ( - <TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + sx={{ + '&:last-child td, &:last-child th': { + border: 0, + }, + }} + > <TableCell className={styles.tableEmail} align='center'> IP-адреса не найдены </TableCell> @@ -10,13 +10,13 @@ import TableRow from '@mui/material/TableRow' import { Dayjs } from 'dayjs' import { useSession } from 'next-auth/react' -import { ResponseGetLogsList } from '@/src/features/invite-person-in-business/model/types' -import { useAppSelector } from '@/src/main/store/store' -import { Search } from '@/src/shared' -import { DateInput } from '@/src/shared/ui/date-input/date-input' -import styles from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import { getLogsList } from '@/src/widgets/business-persons/api/get-logsList' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +import { ResponseGetLogsList } from '#/features/invite-person-in-business/model/types' +import { useAppSelector } from '#/app/store/store' +import { Search } from '#/shared' +import { DateInput } from '#/shared/ui/date-input/date-input' +import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import { getLogsList } from '#/widgets/business-persons/api/get-logsList' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' export const LogList = () => { const [logs, setLogs] = useState<ResponseGetLogsList[] | null>() @@ -44,7 +44,9 @@ export const LogList = () => { if (search) { return logs?.filter( - (el) => el.user.toLowerCase().includes(search.toLowerCase()) || el.message.toLowerCase().includes(search.toLowerCase()) + (el) => + el.user.toLowerCase().includes(search.toLowerCase()) || + el.message.toLowerCase().includes(search.toLowerCase()) ) } else { return logs @@ -64,7 +66,11 @@ export const LogList = () => { <Box className={styles.title2}> <Box> <Typography className={styles.name}>Журнал</Typography> - <ArrowUpOrDown directionY={showPersons} onClick={() => setShowPersons((prev) => !prev)} className={styles.arrow} /> + <ArrowUpOrDown + directionY={showPersons} + onClick={() => setShowPersons((prev) => !prev)} + className={styles.arrow} + /> </Box> </Box> @@ -116,7 +122,12 @@ export const LogList = () => { </Box> <Box className={styles.listWrap}> <Box className={styles.search}> - <Search value={search} onChange={(e) => setSearch(e.target.value)} fullWidth placeholder='Введите пользователя' /> + <Search + value={search} + onChange={(e) => setSearch(e.target.value)} + fullWidth + placeholder='Введите пользователя' + /> </Box> <TableContainer className={styles.table} component={Paper}> <Table sx={{ minWidth: 650 }} aria-label='simple table'> @@ -131,22 +142,47 @@ export const LogList = () => { {searchLogs && searchLogs.length !== 0 ? ( searchLogs?.map((log, idx) => { return ( - <TableRow key={idx} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + key={idx} + sx={{ + '&:last-child td, &:last-child th': { border: 0 }, + }} + > <TableCell className={styles.tableEmail} align='left'> {log.user} </TableCell> <TableCell align='center'> {log.action_time.split('T')[0]} |{' '} - {log.action_time.split('T')[1].split('.')[0].split(':')[0]}: - {log.action_time.split('T')[1].split('.')[0].split(':')[1]} + { + log.action_time + .split('T')[1] + .split('.')[0] + .split(':')[0] + } + : + { + log.action_time + .split('T')[1] + .split('.')[0] + .split(':')[1] + } </TableCell> <TableCell align='right'>{log.message}</TableCell> </TableRow> ) }) ) : ( - <TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> - <TableCell className={styles.tableEmail} align='center'></TableCell> + <TableRow + sx={{ + '&:last-child td, &:last-child th': { + border: 0, + }, + }} + > + <TableCell + className={styles.tableEmail} + align='center' + ></TableCell> <TableCell className={styles.tableEmail} align='center'> Логов не найдено </TableCell> @@ -9,15 +9,15 @@ import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import { useSession } from 'next-auth/react' -import { PersonInBusiness } from '@/src/features/business-security-download' -import { LimitModal } from '@/src/features/change-limit' -import { InviteModal, ResponseGetPersons, RoleSelect } from '@/src/features/invite-person-in-business' -import { InviteRoles } from '@/src/features/invite-person-in-business/lib/constants' -import { XMark } from '@/src/features/remove-person' -import { Search, Success, useShowData } from '@/src/shared' -import styles from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import { getPersons } from '@/src/widgets/business-persons/api/get-persons' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +import { PersonInBusiness } from '#/features/business-security-download' +import { LimitModal } from '#/features/change-limit' +import { InviteModal, ResponseGetPersons, RoleSelect } from '#/features/invite-person-in-business' +import { InviteRoles } from '#/features/invite-person-in-business/lib/constants' +import { XMark } from '#/features/remove-person' +import { Search, Success, useShowData } from '#/shared' +import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import { getPersons } from '#/widgets/business-persons/api/get-persons' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' import { translateEmailStatus } from '../../lib/lib' @@ -99,7 +99,11 @@ export const PersonsList = ({ <Box> <Typography className={styles.name}>Сотрудники </Typography> {persons?.length !== 0 && <Typography className={styles.count}>{persons?.length}</Typography>} - <ArrowUpOrDown directionY={showPersons} onClick={() => setShowPersons((prev) => !prev)} className={styles.arrow} /> + <ArrowUpOrDown + directionY={showPersons} + onClick={() => setShowPersons((prev) => !prev)} + className={styles.arrow} + /> </Box> <Typography onClick={() => setInviteModal(true)}>Добавить сотрудника</Typography> </Box> @@ -134,33 +138,54 @@ export const PersonsList = ({ <TableBody> {searchPersons?.map((person) => { return ( - <TableRow key={person.email} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + key={person.email} + sx={{ + '&:last-child td, &:last-child th': { border: 0 }, + }} + > <TableCell className={styles.tableEmail} align='left'> {person.email} </TableCell> - <TableCell align='center'>{RoleSelect[person.account_type]}</TableCell> + <TableCell align='center'> + {RoleSelect[person.account_type]} + </TableCell> <TableCell align='center'> {translateEmailStatus(person.acceptance_status)} </TableCell> - <TableCell className={styles.tableEmail} align='center'> + <TableCell + className={styles.tableEmail} + align='center' + > {Math.floor(+person.token_limit)} </TableCell> <TableCell onClick={() => - setCurrentPerson(persons?.find((el) => el.email === person.email) || null) + setCurrentPerson( + persons?.find( + (el) => el.email === person.email + ) || null + ) } className={styles.tableLimit} align='center' > <span className={styles.change}>Изменить</span> </TableCell> - <TableCell className={styles.tableLimit} align='center'> + <TableCell + className={styles.tableLimit} + align='center' + > <XMark setNewPersons={(persons) => setPersons( (prev) => - prev?.filter((el) => el.email !== persons.email) || null + prev?.filter( + (el) => + el.email !== + persons.email + ) || null ) } email={person.email} @@ -13,15 +13,15 @@ import axios, { AxiosResponse } from 'axios' import Image from 'next/image' import { useSession } from 'next-auth/react' -import { InviteRoles } from '@/src/features/business-group/lib/constants' -import { ResponseGetBusinessGroups } from '@/src/features/business-group/model/types' -import { LimitModal } from '@/src/features/change-limit' -import { ResponseGetPersons, RoleSelect } from '@/src/features/invite-person-in-business' -import { invitePerson } from '@/src/features/invite-person-in-business/api/invite-person' -import { inviteRoles } from '@/src/features/invite-person-in-business/lib/constants' -import styles2 from '@/src/features/invite-person-in-business/ui/invite-modal.module.scss' -import { IEmailForms } from '@/src/features/register-by-email/model/types' -import { useAppSelector } from '@/src/main/store/store' +import { InviteRoles } from '#/features/business-group/lib/constants' +import { ResponseGetBusinessGroups } from '#/features/business-group/model/types' +import { LimitModal } from '#/features/change-limit' +import { ResponseGetPersons, RoleSelect } from '#/features/invite-person-in-business' +import { invitePerson } from '#/features/invite-person-in-business/api/invite-person' +import { inviteRoles } from '#/features/invite-person-in-business/lib/constants' +import styles2 from '#/features/invite-person-in-business/ui/invite-modal.module.scss' +import { IEmailForms } from '#/features/register-by-email/model/types' +import { useAppSelector } from '#/app/store/store' import { ButtonGray, ButtonUI, @@ -34,12 +34,12 @@ import { ModalProps, onlyNumbersOption, Select, -} from '@/src/shared' -import { Search, Success, useShowData } from '@/src/shared' -import { API_URL } from '@/src/shared/lib/constants' -import styles from '@/src/widgets/business-models/ui/models-list/models-list.module.scss' -import { getBusinessGroups } from '@/src/widgets/business-persons/api/get-businessGroups' -import ArrowUpOrDown from '@/src/widgets/top-bar-model/ui/arrow-up-or-down' +} from '#/shared' +import { Search, Success, useShowData } from '#/shared' +import { API_URL } from '#/shared/lib/constants' +import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' +import { getBusinessGroups } from '#/widgets/business-persons/api/get-businessGroups' +import ArrowUpOrDown from '#/widgets/top-bar-model/ui/arrow-up-or-down' import { translateEmailStatus } from '../../lib/lib' @@ -121,7 +121,11 @@ export const SecurityList = ({ <Box> <Typography className={styles.name}>Сотрудники безопасности</Typography> {persons?.length !== 0 && <Typography className={styles.count}>{persons?.length}</Typography>} - <ArrowUpOrDown directionY={showPersons} onClick={() => setShowPersons((prev) => !prev)} className={styles.arrow} /> + <ArrowUpOrDown + directionY={showPersons} + onClick={() => setShowPersons((prev) => !prev)} + className={styles.arrow} + /> </Box> <Typography onClick={() => setInviteModal(true)}>Добавить сотрудника</Typography> </Box> @@ -156,42 +160,71 @@ export const SecurityList = ({ <TableBody> {searchPersons?.map((person) => { return ( - <TableRow key={person.email} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableRow + key={person.email} + sx={{ + '&:last-child td, &:last-child th': { border: 0 }, + }} + > <TableCell className={styles.tableEmail} align='left'> {person.email} </TableCell> - <TableCell align='center'>{RoleSelect[person.account_type]}</TableCell> + <TableCell align='center'> + {RoleSelect[person.account_type]} + </TableCell> <TableCell align='center'> {translateEmailStatus(person.acceptance_status)} </TableCell> - <TableCell className={styles.tableEmail} align='center'> + <TableCell + className={styles.tableEmail} + align='center' + > {Math.floor(+person.token_limit)} </TableCell> <TableCell onClick={() => - setCurrentPerson(persons?.find((el) => el.email === person.email) || null) + setCurrentPerson( + persons?.find( + (el) => el.email === person.email + ) || null + ) } className={styles.tableLimit} align='center' > <span className={styles.change}>Изменить</span> </TableCell> - <TableCell className={styles.tableLimit} align='center'> + <TableCell + className={styles.tableLimit} + align='center' + > <Image onClick={(e) => { axios.put< - { token_limit: string; role: string }, + { + token_limit: string + role: string + }, AxiosResponse<ResponseGetPersons> >( - API_URL + `/auth/business-host/accounts/${person.email}`, - { account_privileges: 'regular' }, - { headers: { Authorization: `Bearer ${data?.access}` } } + API_URL + + `/auth/business-host/accounts/${person.email}`, + { + account_privileges: 'regular', + }, + { + headers: { + Authorization: `Bearer ${data?.access}`, + }, + } ).then((res) => { if (securityList) setSecurityList( securityList.filter( - (el) => el.email !== res.data.email + (el) => + el.email !== + res.data.email ) ) addList(res.data, 'personal') @@ -214,7 +247,11 @@ export const SecurityList = ({ </Box> )} <Success message={message} open={Boolean(message)} isError={isError} /> - <InviteSecurityModal open={inviteModal} onClose={() => setInviteModal(false)} showNewPersons={showNewPerson} /> + <InviteSecurityModal + open={inviteModal} + onClose={() => setInviteModal(false)} + showNewPersons={showNewPerson} + /> {currentPerson && ( <LimitModal list={securityList} @@ -287,7 +324,11 @@ export const InviteSecurityModal: FC<InviteModalProps> = ({ open, onClose, showN {/* Поле с выбором роли сотрудника */} <Typography className={styles2.hint}>Выберите роль</Typography> - <Select list={Object.values(securityRole)} onChange={(e) => setRole(e.target.value as InviteRoles)} value={role}></Select> + <Select + list={Object.values(securityRole)} + onChange={(e) => setRole(e.target.value as InviteRoles)} + value={role} + ></Select> {/* Поле с выбором группы */} <Typography className={styles2.hint}>Выберите бизнес-группу</Typography> @@ -1,7 +1,7 @@ import React from 'react' import { Box, Button, Grow, Menu, Typography } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' interface IContext { context: { message: string; uid: string }[] @@ -10,7 +10,12 @@ interface IContext { device?: 'mobile' | 'desktop' } -const Context: React.FC<IContext> = ({ context, deleteContextMessage, deleteAllContextMessage, device = 'desktop' }) => { +const Context: React.FC<IContext> = ({ + context, + deleteContextMessage, + deleteAllContextMessage, + device = 'desktop', +}) => { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null) const open = Boolean(anchorEl) const handleClick = (event: React.MouseEvent<HTMLElement>) => { @@ -101,7 +106,12 @@ const Context: React.FC<IContext> = ({ context, deleteContextMessage, deleteAllC <> {context.map((message) => { return ( - <Grow key={message.uid} in={true} style={{ transformOrigin: '0 0 0' }} {...(true ? { timeout: 800 } : {})}> + <Grow + key={message.uid} + in={true} + style={{ transformOrigin: '0 0 0' }} + {...(true ? { timeout: 800 } : {})} + > <Box onClick={() => deleteContextMessage(message.uid)} sx={{ width: 'fit-content' }}> <Typography sx={{ @@ -8,11 +8,11 @@ // import { useSession } from 'next-auth/react' // import remarkGfm from 'remark-gfm' // -// import FullScreenModal from '@/src/features/image-modal/full-screen-modal' -// import { useAppSelector } from '@/src/main/store/store' -// import { TooltipCustom } from '@/src/shared' -// import { useAutoScroll } from '@/src/shared/lib/hooks' -// import { Message } from '@/src/shared/lib/types/model' +// import FullScreenModal from '#/features/image-modal/full-screen-modal' +// import { useAppSelector } from '#/app/store/store' +// import { TooltipCustom } from '#/shared' +// import { useAutoScroll } from '#/shared/lib/hooks' +// import { Message } from '#/shared/lib/types/model' // // import '../styles.module.css' // @@ -1,8 +1,8 @@ import React from 'react' import { Box, Typography } from '@mui/material' -import { errorColor } from '@/src/shared/lib/constants/colors' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { errorColor } from '#/shared/lib/constants/colors' +import { useThemeAndDevice } from '#/shared/lib/hooks' const TooltipContextText = () => { const { theme } = useThemeAndDevice() @@ -1,8 +1,8 @@ import React from 'react' import { Tooltip } from '@mui/material' -import { useThemeAndDevice } from '@/src/shared/lib/hooks' -import TooltipContextText from '@/src/widgets/chat-gpt-field/ui/tooltip-context-text' +import { useThemeAndDevice } from '#/shared/lib/hooks' +import TooltipContextText from '#/widgets/chat-gpt-field/ui/tooltip-context-text' interface ITooltipContext { children: React.ReactNode @@ -2,19 +2,19 @@ import React, { useCallback } from 'react' import { Stack } from '@mui/material' import axios from 'axios' -import { UniqInput } from '@/src/main/components/uniq_input' -import { useAppSelector } from '@/src/main/store/store' -import { ChatMessagesList } from '@/src/widgets/messages' +import { UniqInput } from '#/app/components/uniq_input' +import { useAppSelector } from '#/app/store/store' +import { ChatMessagesList } from '#/widgets/messages' -import BlockedSvg from '@/src/assets/svg/blocked.svg?react' +import BlockedSvg from '#/assets/svg/blocked.svg?react' import styles from './chat-window.module.scss' import 'intro.js/introjs.css' -import { Device, DeviceOs } from '@/src/shared/lib/types/entities' -import { IModelInputs, IModelTag } from '@/src/entities/model-entity' -import { Message } from '@/src/entities/message' -import { c } from '@/src/shared/lib/helpers' +import { c } from '#/shared' +import { Device, DeviceOs } from '#/shared/lib/types/entities' +import { IModelInputs } from '#/entities/model-entity' +import { Message } from '#/entities/message' interface Elements { element: React.ReactElement @@ -37,7 +37,6 @@ export interface ChatProps<T> { elements?: Elements[] model?: string blocked?: boolean - tags: IModelTag[] getMessagesPagination?: () => Promise<void> input_types?: IModelInputs[] deleteMessage: (message_uid: string) => void @@ -57,7 +56,6 @@ function Chat<T>({ getMessagesPagination, input_types, deleteMessage, - tags, modelTitle, blocked, currentVersion, @@ -116,9 +114,7 @@ function Chat<T>({ paddingTop: 0, paddingBottom: blocked ? (desktop ? 3.8 : 5) : 1, width: '100%', - height: `calc(100dvh - 116px - 61px - 10px - ${ - tags.length !== 0 ? '45px' : '10px' - } - ${blocked ? '0px' : '0px'})`, + height: desktop ? '75vh' : 'calc(100dvh - 245px)', overflow: 'hidden', }} > @@ -14,11 +14,11 @@ import axios from 'axios' import Image from 'next/image' import { useSession } from 'next-auth/react' -import { Chat, ChatsReturn } from '@/src/features/chats' -import { useAppSelector } from '@/src/main/store/store' -import styles2 from '@/src/main/styles/account.module.css' -import { TooltipCustom } from '@/src/shared' -import { api } from '@/src/shared/api/endpoints' +import { Chat, ChatsReturn } from '#/features/chats' +import { useAppSelector } from '#/app/store/store' +import styles2 from '#/app/styles/account.module.css' +import { TooltipCustom } from '#/shared' +import { api } from '#/shared/api/endpoints' import styles from './chats.module.scss' @@ -73,9 +73,7 @@ export const Chats = ({ api.updRenameChat(localChat, newTitle, data?.access).then((res) => { if (res.data && chats) setLocalChats( - chats.filter((el) => - el.uid === res.data.uid ? (el.title = res.data.title) : el - ) + chats.filter((el) => (el.uid === res.data.uid ? (el.title = res.data.title) : el)) ) setNewTitle('') }) @@ -86,7 +84,7 @@ export const Chats = ({ <Box className={styles.wrap} sx={{ margin: desktop ? '0 0 15px 0' : '15px 0 0' }}> <Box display='flex' alignItems='center' justifyContent='space-between'> <Typography - className={styles.title} + className={styles.title} sx={{ color: '#A4AAB5', fontWeight: '600', @@ -106,13 +104,7 @@ export const Chats = ({ cursor: 'pointer', }} > - <Image - src={'/plus.svg'} - style={{ marginTop: '3px' }} - width={18} - height={18} - alt={''} - /> + <Image src={'/plus.svg'} style={{ marginTop: '3px' }} width={18} height={18} alt={''} /> </Box> </TooltipCustom> </Box> @@ -175,8 +167,7 @@ export const Chats = ({ }, '& .MuiInputBase-root': { borderRadius: '13px !important', - backgroundColor: - 'rgba(130, 128, 255, 0.15)', + backgroundColor: 'rgba(130, 128, 255, 0.15)', }, '& .MuiFormControl-root-MuiTextField-root': { borderRadius: '13px', @@ -239,9 +230,7 @@ export const Chats = ({ <Image onClick={handleClick} aria-describedby={ - Boolean(chatSetting) - ? 'simple-popover' - : undefined + Boolean(chatSetting) ? 'simple-popover' : undefined } src={'/svg/chat-setting.svg'} alt={'Настройка чата'} @@ -259,17 +248,13 @@ export const Chats = ({ sx={{ '& .MuiMenu-list': { backgroundColor: - theme === 'dark' - ? '#303035' - : '#EFF0F2', + theme === 'dark' ? '#303035' : '#EFF0F2', color: '#8280FF', borderRadius: '15px', }, '& .MuiPopover-paper': { backgroundColor: - theme === 'dark' - ? '#303035' - : '#EFF0F2', + theme === 'dark' ? '#303035' : '#EFF0F2', borderRadius: '15px', }, }} @@ -14,13 +14,17 @@ const advantagesDaVinci = const warningDaVinci = 'Тратит в 10 раз больше токенов, чем модели ChatGPT и Curie.' const descriptionCurie = 'Очень мощный, при этом быстрее и дешевле, чем text-DaVinci-003.' -const advantagesCurie = 'Способен выполнять языковой перевод, классифицировать и обобщать информацию, соблюдать заданный эмоциональный тон.' +const advantagesCurie = + 'Способен выполнять языковой перевод, классифицировать и обобщать информацию, соблюдать заданный эмоциональный тон.' const descriptionBabbage = 'Способен выполнять простые задачи, очень быстрый и недорогой.' -const advantagesBabbage = 'Способен классифицировать информацию по заданным параметрам и осуществлять семантический поиск.' +const advantagesBabbage = + 'Способен классифицировать информацию по заданным параметрам и осуществлять семантический поиск.' -const descriptionAda = 'Способен выполнять простые задачи. В серии GPT-3 является самой быстрой моделью по самой низкой цене.' -const advantagesAda = 'Способен синтаксически анализировать текст, выполнять базовую классификацию, подбирать ключевые слова.' +const descriptionAda = + 'Способен выполнять простые задачи. В серии GPT-3 является самой быстрой моделью по самой низкой цене.' +const advantagesAda = + 'Способен синтаксически анализировать текст, выполнять базовую классификацию, подбирать ключевые слова.' export const ParameterTopP = 'Тор_p - альтернатива температуре. Не\n' + @@ -2,10 +2,10 @@ import React from 'react' import { Drawer, MenuItem, Slider, Typography } from '@mui/material' import Stack from '@mui/material/Stack' -import { useAppSelector } from '@/src/main/store/store' -import { Select } from '@/src/shared' -import { IFiltersChatGPT } from '@/src/widgets/filters-gpt/ui/filters' -import TooltipModelTypes from '@/src/widgets/filters-gpt/ui/tooltip-model-types' +import { useAppSelector } from '#/app/store/store' +import { Select } from '#/shared' +import { IFiltersChatGPT } from '#/widgets/filters-gpt/ui/filters' +import TooltipModelTypes from '#/widgets/filters-gpt/ui/tooltip-model-types' const FiltersMobile: React.FC<IFiltersChatGPT> = ({ open, @@ -5,12 +5,17 @@ import { SelectChangeEvent } from '@mui/material/Select' import Stack from '@mui/material/Stack' import { styled } from '@mui/material/styles' -import { TutorialContext } from '@/src/features/tutorial-context/tutorial-context' -import { useAppSelector } from '@/src/main/store/store' -import { Select, Slider as Sl, SwitchCustom, TooltipCustom } from '@/src/shared' -import { ITypeModels, ParameterPresencePenalty, ParameterTemperature, ParameterTopP } from '@/src/widgets/filters-gpt/lib/constants' -import FiltersMobile from '@/src/widgets/filters-gpt/ui/filters-mobile' -import TooltipModelTypes from '@/src/widgets/filters-gpt/ui/tooltip-model-types' +import { TutorialContext } from '#/features/tutorial-context/tutorial-context' +import { useAppSelector } from '#/app/store/store' +import { Select, Slider as Sl, SwitchCustom, TooltipCustom } from '#/shared' +import { + ITypeModels, + ParameterPresencePenalty, + ParameterTemperature, + ParameterTopP, +} from '#/widgets/filters-gpt/lib/constants' +import FiltersMobile from '#/widgets/filters-gpt/ui/filters-mobile' +import TooltipModelTypes from '#/widgets/filters-gpt/ui/tooltip-model-types' import 'intro.js/introjs.css' @@ -137,7 +142,13 @@ export const Filters: React.FC<IFiltersChatGPT> = memo( padding: '30px', height: 'fit-content', borderRadius: '15px', - backgroundColor: desktop ? (theme === 'light' ? 'white' : '#151518') : theme === 'light' ? 'white' : '#4B4B4B', + backgroundColor: desktop + ? theme === 'light' + ? 'white' + : '#151518' + : theme === 'light' + ? 'white' + : '#4B4B4B', }} > <Typography className='title-block' sx={{ marginBottom: '15px' }}> @@ -181,7 +192,12 @@ export const Filters: React.FC<IFiltersChatGPT> = memo( </Select> </Box> {isAdditionalCtx && ( - <Box width='100%' display='flex' alignItems='center' justifyContent='space-between'> + <Box + width='100%' + display='flex' + alignItems='center' + justifyContent='space-between' + > <Typography sx={{ color: '#868686', @@ -192,7 +208,10 @@ export const Filters: React.FC<IFiltersChatGPT> = memo( > Увеличенный контекст </Typography> - <SwitchCustom checked={isAdditional} onChange={() => changeAdditionalCtx!()} /> + <SwitchCustom + checked={isAdditional} + onChange={() => changeAdditionalCtx!()} + /> </Box> )} <TooltipCustom title={ParameterTemperature} placement='left'> @@ -1,7 +1,7 @@ import React from 'react' import { Box, Tooltip, Typography } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' +import { useAppSelector } from '#/app/store/store' interface ITooltip { title: string @@ -1,8 +1,15 @@ -import { IProps } from '@/src/shared/lib/types/entities' -import { Styles } from '@/src/widgets/filters-sd/ui/filters' +import { IProps } from '#/shared/lib/types/entities' +import { Styles } from '#/widgets/filters-sd/ui/filters' export type TImageFormat = '512x512' | '640x384' | '384x640' | '768x512' | '512x768' | '1024x1024' -export type TClipGuidancePresets = 'FAST_BLUE' | 'FAST_GREEN' | 'Без фильтров' | 'SIMPLE' | 'SLOW' | 'SLOWER' | 'SLOWEST' +export type TClipGuidancePresets = + | 'FAST_BLUE' + | 'FAST_GREEN' + | 'Без фильтров' + | 'SIMPLE' + | 'SLOW' + | 'SLOWER' + | 'SLOWEST' export type TSamplerTypes = | 'DDIM' | 'DDPM' @@ -2,14 +2,20 @@ import React, { useState } from 'react' import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest' import { Box, Button, Drawer, Stack, Typography } from '@mui/material' -import { useAppSelector } from '@/src/main/store/store' -import { Select, SwitchCustom } from '@/src/shared' -import { Slider } from '@/src/shared' -import { SelectUI } from '@/src/shared/ui/select' -import { PrettoSlider, PrettoSliderDark } from '@/src/widgets/filters-gpt/ui/filters' -import { GeneratingModel } from '@/src/widgets/filters-sd/lib/constants' -import { ISDFilters, TClipGuidancePresets, TGeneratingModel, TImageFormat, TSamplerTypes } from '@/src/widgets/filters-sd/lib/types' -import { Styles,styles } from '@/src/widgets/filters-sd/ui/filters' +import { useAppSelector } from '#/app/store/store' +import { Select, SwitchCustom } from '#/shared' +import { Slider } from '#/shared' +import { SelectUI } from '#/shared/ui/select' +import { PrettoSlider, PrettoSliderDark } from '#/widgets/filters-gpt/ui/filters' +import { GeneratingModel } from '#/widgets/filters-sd/lib/constants' +import { + ISDFilters, + TClipGuidancePresets, + TGeneratingModel, + TImageFormat, + TSamplerTypes, +} from '#/widgets/filters-sd/lib/types' +import { Styles, styles } from '#/widgets/filters-sd/ui/filters' interface ISDFiltersMobile extends ISDFilters { isOpenDrawer: boolean @@ -142,7 +148,9 @@ export const FiltersMobile: React.FC<ISDFiltersMobile> = ({ size={'small'} title={'clip_guidance_preset'} value={clipGuidancePreset} - onChange={(e) => changeClipGuidancePreset(e.target.value as TClipGuidancePresets)} + onChange={(e) => + changeClipGuidancePreset(e.target.value as TClipGuidancePresets) + } list={clipGuidancePresets} /> </Box> @@ -1,10 +1,16 @@ import React from 'react' import { Box, Stack, Typography } from '@mui/material' -import { Slider, SwitchCustom } from '@/src/shared' -import { SelectUI } from '@/src/shared/ui/select' -import { GeneratingModel } from '@/src/widgets/filters-sd/lib/constants' -import { ISDFilters, TClipGuidancePresets, TGeneratingModel, TImageFormat, TSamplerTypes } from '@/src/widgets/filters-sd/lib/types' +import { Slider, SwitchCustom } from '#/shared' +import { SelectUI } from '#/shared/ui/select' +import { GeneratingModel } from '#/widgets/filters-sd/lib/constants' +import { + ISDFilters, + TClipGuidancePresets, + TGeneratingModel, + TImageFormat, + TSamplerTypes, +} from '#/widgets/filters-sd/lib/types' export type Styles = | null @@ -81,7 +87,12 @@ export const FiltersSd: React.FC<ISDFilters> = ({ /> <Box sx={{ marginTop: 1 }}> - <SelectUI title={'Стиль'} value={style} onChange={(e) => changeStyle(e.target.value as Styles)} list={Object.values(styles)} /> + <SelectUI + title={'Стиль'} + value={style} + onChange={(e) => changeStyle(e.target.value as Styles)} + list={Object.values(styles)} + /> </Box> <Slider @@ -0,0 +1,19 @@ +import { Attributes, FC, ReactElement } from 'react' +import { Layout } from '../ui' +import { LayoutProps } from '../types' +import React from 'react' + +export const getDefaultLayout = function (layoutProps: Omit<LayoutProps, 'children'> = {}) { + return (page: ReactElement) => <Layout {...layoutProps}>{page}</Layout> +} + +export const getLayout = function ( + Element: (...props: any) => JSX.Element, + layoutProps: Omit<LayoutProps, 'children'> = {} +) { + return (page: ReactElement) => <Element {...layoutProps}>{page}</Element> +} + +export const getDefaultLayoutDynamic = function (getLayoutProps: () => Omit<LayoutProps, 'children'>) { + return (page: ReactElement) => <Layout {...getLayoutProps()}>{page}</Layout> +} @@ -0,0 +1 @@ +export * from './get-layout' \ No newline at end of file @@ -0,0 +1 @@ +export * from './layout-props' \ No newline at end of file @@ -0,0 +1,10 @@ +import { Device } from "#/shared/lib/types/entities" + + +export interface LayoutProps { + children: React.ReactNode + titlePage?: string + title?: string | React.ReactNode | null + device?: Device + isLoader?: boolean +} \ No newline at end of file @@ -0,0 +1,173 @@ +import React, { useEffect, useState } from 'react' +import { Box } from '@mui/material' +import { ThemeProvider } from '@mui/material/styles' +import Head from 'next/head' +import { useRouter } from 'next/router' +import { signOut, useSession } from 'next-auth/react' + +import { getUserBalance } from '#/entities/balance' +import { useTheme } from '#/entities/theme' +import { getAllInfo } from '#/entities/user-account' +import { isSettingExist } from '#/entities/user-account/lib/helpers/is-setting-exist' +import { getUserAccountSettings, setSettings } from '#/entities/user-account/model/settings' +import InfoBar from '#/app/layout/ui/info-bar' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { Loader } from '#/shared' +import { pingFangFont } from '#/shared/lib/constants/font/font' +import { Device } from '#/shared/lib/types/entities' +import { MainMenuMobile } from '#/widgets/main-menu' +import { SideMenu } from '#/widgets/side-menu' +import { getDeviceType } from '#/shared/lib/helpers' +import { LayoutProps } from '../types' +import { change } from '#/features/pending' + +const freeRoutes = ['/login', '/register', '/reset', '/change-password'] + +export const Layout: React.FC<LayoutProps> = ({ + children, + titlePage, + title = titlePage, + isLoader, + device = getDeviceType(), +}) => { + const { data: sessionData } = useSession() + const appState = useAppSelector((state) => state) + const dispatch = useAppDispatch() + + const meStatus = useAppSelector((state) => state.user.status) + + const { userLoading } = useAppSelector((state) => state.loading) + + const desktop = device === 'desktop' + + const [sidemenuDefaultOpen, setSidemenuDefaultOpen] = useState(true) + + useTheme() + + const { data, status } = useSession() + + const { push } = useRouter() + + async function getUserData(access: string) { + await dispatch(getUserBalance(access)) + await dispatch(getAllInfo(access)) + dispatch(change(false)) + } + + useEffect(() => { + if (meStatus === 'failed') { + signOut() + push('/login') + } + }, [meStatus]) + + useEffect(() => { + if (!data) return + getUserData(data.access) + }, [data]) + + useEffect(() => { + if ( + (sessionData && !sessionStorage.getItem('firstRender')) || + (sessionData && !localStorage.getItem('global_settings')) + ) { + dispatch(getUserAccountSettings(sessionData.access)) + sessionStorage.setItem('firstRender', 'true') + } + }, [sessionData]) + + React.useEffect(() => { + const lsData = localStorage.getItem('global_settings') + if (lsData !== null) { + dispatch(setSettings(JSON.parse(lsData))) + } + + window.addEventListener('beforeunload', () => { + sessionStorage.removeItem('firstRender') + sessionStorage.clear() + }) + }, []) + + React.useEffect(() => { + if (appState.settings.state !== null && device) { + let setting = isSettingExist({ + settings: appState.settings.state, + targetDevice: device, + targetType: 'sidemenu', + }) + if (setting) + setting?.value?.sidemenu_state === 'opened' + ? setSidemenuDefaultOpen(true) + : setSidemenuDefaultOpen(false) + } + }, [appState.settings.state]) + + if (userLoading || isLoader || status === 'loading') { + return ( + <Box + width='100vw' + height='100vh' + display='flex' + alignItems='center' + justifyContent='center' + > + <Loader size={50} /> + </Box> + ) + } + + return ( + <> + <Head> + {titlePage && <title>{titlePage}} + + + + + + + + + + {desktop ? ( + + + + + + {children} + + + + ) : ( + + + {children} + + )} + + + + ) +} @@ -0,0 +1,2 @@ +export * from './default' +export * from './layout-without-sidemenu' @@ -0,0 +1,40 @@ +import React, { Component, FunctionComponent } from 'react' +import { LayoutProps } from '../types' +import { Box } from '@mui/material' +import Head from 'next/head' +import { getDeviceType } from '#/shared/lib/helpers' +import { useTheme } from '#/entities/theme' + +export const LayoutWithoutSideMenu = ({ children, titlePage, device = getDeviceType() }: LayoutProps) => { + const desktop = device === 'desktop' + + useTheme() + + return ( + <> + + {titlePage && {titlePage}} + + + + + + + + + {children} + + + + ) +} @@ -0,0 +1,3 @@ +export * from './ui' +export * from './model' +export * from './types' \ No newline at end of file @@ -9,10 +9,10 @@ import Link from 'next/link' import Router, { useRouter } from 'next/router' import { signOut, useSession } from 'next-auth/react' -import { change } from '@/src/entities/theme' -import { getAll, ResponseAllInfo } from '@/src/entities/user-account/model/user-type-slice' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { AccountMenu } from '@/src/shared' +import { change } from '#/entities/theme' +import { getAll, ResponseAllInfo } from '#/entities/user-account/model/user-type-slice' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { AccountMenu } from '#/shared' import { menuListMiddle, menuListTop } from '../../side-menu/ui/side-menu' @@ -155,14 +155,31 @@ export const MainMenuMobile = memo(() => { dispatch(change(null)) }} > - + signOut()}> - + - + Реквизиты @@ -170,7 +187,11 @@ export const MainMenuMobile = memo(() => { - push('/account')} sx={{ width: 29, height: 29 }} src={String(profile_picture_link)}> + push('/account')} + sx={{ width: 29, height: 29 }} + src={String(profile_picture_link)} + > {first_name[0]} @@ -192,7 +213,10 @@ type MenuItemProps = { } export function MenuItem(props: MenuItemProps) { - const isActive = useMemo(() => props.link === props.pathname || props.activeList?.some((el) => props.pathname.includes(el)), [props.pathname]) + const isActive = useMemo( + () => props.link === props.pathname || props.activeList?.some((el) => props.pathname.includes(el)), + [props.pathname] + ) return ( @@ -8,11 +8,11 @@ import Image from 'next/image' import Router, { useRouter } from 'next/router' import { useSession } from 'next-auth/react' -import { change } from '@/src/entities/theme' -import { useAppDispatch, useAppSelector } from '@/src/main/store/store' -import { AccountMenu } from '@/src/shared' -import { NotificationMenu } from '@/src/shared' -import { TooltipFreeTokens } from '@/src/shared' +import { change } from '#/entities/theme' +import { useAppDispatch, useAppSelector } from '#/app/store/store' +import { AccountMenu } from '#/shared' +import { NotificationMenu } from '#/shared' +import { TooltipFreeTokens } from '#/shared' function a11yProps(index: number) { return { @@ -79,7 +79,7 @@ export const MainMenu: React.FC = memo(() => { const selectPage = pathname === '/' ? 0 : pathname === '/service-keys' ? 1 : pathname === '/admin' ? 2 : 3 - const ErrorModalLazy = dynamic(() => import('@/src/shared/ui/error-modal'), { ssr: false }) + const ErrorModalLazy = dynamic(() => import('#/shared/ui/error-modal'), { ssr: false }) return ( @@ -111,7 +111,11 @@ export const MainMenu: React.FC = memo(() => { alt={''} height={18} width={18} - src={pathname == '/' ? '/svg/main_menu/marketplace.svg' : '/svg/main_menu/marketplace_off.svg'} + src={ + pathname == '/' + ? '/svg/main_menu/marketplace.svg' + : '/svg/main_menu/marketplace_off.svg' + } /> } iconPosition='start' @@ -134,7 +138,8 @@ export const MainMenu: React.FC = memo(() => { '& .MuiTooltip-arrow': { color: theme === 'light' ? '#E8E8FA' : '#4B4B4B', }, - boxShadow: '0px 0px 4px rgba(0, 0, 0, 0.04), 0px 4px 32px rgba(0, 0, 0, 0.16)', + boxShadow: + '0px 0px 4px rgba(0, 0, 0, 0.04), 0px 4px 32px rgba(0, 0, 0, 0.16)', }, }, }} @@ -147,7 +152,11 @@ export const MainMenu: React.FC = memo(() => { } @@ -177,7 +186,13 @@ export const MainMenu: React.FC = memo(() => { alt='Error' /> )} - {openErrorModal && } + {openErrorModal && ( + + )} { width={19} alt='Bell' /> - +