@@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} Binary files /dev/null and b/public/images/dashboard.png differ @@ -1,3 +1,3 @@ { - "main-page": "AIR" + "main-page": "AIR" } @@ -1,3 +1,3 @@ { - "main-page": "AIR" + "main-page": "AIR" } @@ -1,19 +1,18 @@ { - "name": "AIR", - "short_name": "AIR", - "icons": [ - { - "src": "/logo192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/logo512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": "/", - "display": "standalone" - } - \ No newline at end of file + "name": "AIR", + "short_name": "AIR", + "icons": [ + { + "src": "/logo192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/logo512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "start_url": "/", + "display": "standalone" +} @@ -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 @@ -59,7 +59,14 @@ export const LoadImage = ({ loading, unpinImage, image, imageLoad, types }: IPro } return ( <> - + { - const link = useMemo(() => { - return accessed_models && !accessed_models.includes(slug) - ? '/account?scope=subscribe' - : `chat-bot/${slug}` + return accessed_models && !accessed_models.includes(slug) ? '/account?scope=subscribe' : `chat-bot/${slug}` }, [accessed_models]) return ( @@ -57,11 +54,7 @@ const ChatCard = ({ text, icon, title, uid, slug, accessed_models }: CardProps) textAlign: 'center', }} > - { 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} @@ -53,7 +53,11 @@ export default function FullScreenModal({ modal, setModal, image }: IProps) { className={styles.image_style} /> ) : ( - К сожалению, изображение не загрузилось + К сожалению, изображение не загрузилось )} @@ -1,30 +1,30 @@ -.close_block{ - position: absolute; - z-index: 105; - cursor: pointer; - right: 25px; - top: 25px; +.close_block { + position: absolute; + z-index: 105; + cursor: pointer; + right: 25px; + top: 25px; } -.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; +.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; } -.image_style{ - position: relative; - width: 100%; - height: 100%; - max-width: 960px; - max-height: 700px; - border-radius: 15px; - object-fit: contain; -} \ No newline at end of file +.image_style { + position: relative; + width: 100%; + height: 100%; + max-width: 960px; + max-height: 700px; + border-radius: 15px; + object-fit: contain; +} @@ -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 {/* Поле с выбором роли сотрудника */} Выберите роль - + {/* Поле с выбором группы */} Выберите бизнес-группу @@ -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' @@ -2,7 +2,7 @@ import React from 'react' import { Box, Typography } from '@mui/material' import Link from 'next/link' -import styles from '@/src/shared/styles/chats-bot-pages.module.scss' +import styles from '#/shared/styles/chats-bot-pages.module.scss' export default function Title(props: any) { 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 @@ -14,13 +14,19 @@ const initialState: Theme = { generation: null, } -export const loadTemplates = createAsyncThunk('copywrite/loadTemplates', async (token?: string): Promise => { - return await CopywriteProxy.getTemplates(token) -}) - -export const loadGeneration = createAsyncThunk('copywrite/loadGeneration', async (token?: string): Promise => { - return await CopywriteProxy.getGeneration(token) -}) +export const loadTemplates = createAsyncThunk( + 'copywrite/loadTemplates', + async (token?: string): Promise => { + return await CopywriteProxy.getTemplates(token) + } +) + +export const loadGeneration = createAsyncThunk( + 'copywrite/loadGeneration', + async (token?: string): Promise => { + return await CopywriteProxy.getGeneration(token) + } +) export const copySlice = createSlice({ name: 'copySlice', @@ -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,31 +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; - } - } -} \ No newline at end of file @@ -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); -} @@ -2,8 +2,8 @@ import axios, { AxiosResponse } from 'axios' import { User } from 'next-auth' import { JWT } from 'next-auth/jwt' -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,117 +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' +AudioModelsPage.getLayout = getDefaultLayout({ titlePage: 'Аудио' }) -interface Model { - 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 -} - -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]) - - const changeFavorite = async (uid: string) => { - let res = 400 - - if (bots?.find((el) => el.uid === uid)!.is_favourite === true) { - res = await deleteFromFavorites(uid, data?.access) - } else { - res = await addToFavorites(uid, data?.access) - } - - if (res !== 400) - setBots((prev) => - prev!.map((el) => { - if (el.uid === uid) { - el.is_favourite = !el.is_favourite - } - - return el - }) - ) - } - - return ( - - Аудио - - {bots?.map((item, idx) => { - return ( - - ) - })} - - - ) -} - -export default Images +export default AudioModelsPage @@ -1,413 +1,6 @@ -import React, { useCallback, useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { Box, Collapse, Stack, Typography } from '@mui/material' -import { useRouter } from 'next/router' -import { useSession } from 'next-auth/react' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { ChatBotPage } from '#/views/chat-bot' +import { getDefaultLayout } from '#/widgets/layouts' -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 { AllChatWindow } from '@/src/main/components/chat_window' -import { ResetFilters } from '@/src/main/components/filters/reset_filters' -import { Layout } from '@/src/main/layout' -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 { IModel } from '@/src/shared/api/models/models' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { getDeviceOs } from '@/src/shared/lib/helpers/get-type-device' -import styles from '@/src/shared/styles/chats-bot-pages.module.scss' -import { Chats } from '@/src/widgets/chats' +ChatBotPage.getLayout = getDefaultLayout() -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const deviceType = getTypeDevice(context) - const deviceOs = getDeviceOs(context) - - return { - props: { - ...(await serverSideTranslations(context.locale, ['common'])), - deviceType, - deviceOs, - }, - } -} - -const Page: React.FC = ({ deviceType, deviceOs }) => { - const [botParams, setBotParams] = React.useState(null) - const [modelType, setModelType] = React.useState('') - const [openFiltersMobile, setOpenFiltersMobile] = React.useState(false) - const [version, setVersion] = React.useState('') - const [step, setStep] = React.useState(0) - // const [filters, setFilters] = React.useState() - const [params, setParams] = React.useState(false) - const [file, setFile] = React.useState(null) - const { error, showError } = useShowData() - const desktop = deviceType === 'desktop' - const { data } = useSession() - const router = useRouter() - - const { - chats, - currentChat, - chatSetting, - setChatSetting, - setChat, - removeChat, - createNewChat, - handleClickChatSetting, - isTryRename, - setIsTryRename, - } = useChats(modelType) - const { messages, sendMessage, loading, getMessagesPagination, deleteMessage } = useModel( - currentChat, - showError, - modelType - ) - - const includeParams = useAppSelector((state) => state.params.params) - const dispatch = useDispatch() - - const deleteMessageMemo = useCallback(deleteMessage, [currentChat, messages]) - - React.useEffect(() => { - if (data?.access) { - model_api.getBotParams(router.asPath.split('/')[2], data.access).then((res) => { - setBotParams(res) - setModelType(res.slug) - if (res.versions.length !== 0) { - setVersion(res.versions[0].slug) - dispatch( - setParametres( - res.parameters.reduce( - (a, v) => - v.versions.includes(res.versions[0].slug) - ? { ...a, [v.key]: v.values.default } - : { ...a }, - {} - ) - ) - ) - } else { - setVersion('') - dispatch( - setParametres( - res.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) - } - }) - } - }, [data?.access, router.query]) - - const resetParams = () => { - if (botParams) { - dispatch(setParametres({})) - if (botParams.versions?.length !== 0) { - setVersion(botParams.versions[0].slug) - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => - v.versions.includes(botParams.versions[0].slug) - ? { ...a, [v.key]: v.values.default } - : { ...a }, - {} - ) - ) - ) - } else { - setVersion(botParams.slug) - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) - } - } - } - - const setDefaultParams = () => { - if (botParams) { - dispatch(setParametres({})) - if (version !== '') { - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => - v.versions.includes(version) - ? { ...a, [v.key]: v.values.default } - : { ...a }, - {} - ) - ) - ) - } else { - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) - } - } - } - - const viewMobileSettings = () => { - setOpenFiltersMobile(true) - } - - const hideMobileSettings = () => { - setOpenFiltersMobile(false) - } - - const onSendMessage = (input: string, required: (string | null)[]) => { - if (required.includes('text') && (input === '' || input === null)) { - showError('Введите сообщение!') - return false - } - if (required.includes('image') && file === null) { - showError('Прикрепите изображение!') - return false - } - - let data = {} - if (version === '') { - data = { - ...includeParams, - } - } else { - data = { - version: version, - ...includeParams, - } - } - - sendMessage({ - content: input, - file: file, - info: { - ...data, - }, - }) - return true - } - - return ( - - - - <Box className={styles.main}> - <Box className={styles.chatWindow}> - <AllChatWindow - deviceOs={deviceOs} - input_types={botParams?.inputs} - setFile={setFile} - file={file} - getMessagesPagination={getMessagesPagination} - sendMessage={onSendMessage} - deleteMessage={deleteMessageMemo} - openMobileFilters={viewMobileSettings} - isCalculating={true} - device={deviceType} - loading={loading} - messages={messages || []} - modelType={modelType} - modelTitle={botParams?.title} - currentVersion={version} - /> - </Box> - <Box className={styles.settings}> - <Chats - desktop={desktop} - isTryRename={isTryRename} - setIsTryRename={setIsTryRename} - currentChat={currentChat} - setChat={setChat} - chats={chats} - chatSetting={chatSetting} - setChatSetting={setChatSetting} - removeChat={removeChat} - createNewChat={createNewChat} - handleClickChatSetting={handleClickChatSetting} - /> - {desktop && ( - <Stack className='pd-30 bg-color-block border-radius-main' spacing={2}> - {botParams?.versions && botParams.versions?.length !== 0 && ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - }} - > - ВЕРСИИ - </Typography> - <ChatSelect - setDefaultParams={setDefaultParams} - value={version} - list={botParams.versions} - setValue={setVersion} - /> - </> - )} - {botParams && botParams.parameters?.length > 0 && ( - <Box - display={'flex'} - alignItems={'center'} - gap={'5px'} - sx={{ cursor: 'pointer' }} - onClick={() => { - setParams(!params) - }} - > - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - }} - > - ПАРАМЕТРЫ - </Typography> - <svg - width='14' - height='20' - viewBox='0 0 21 13' - fill='none' - xmlns='http://www.w3.org/2000/svg' - className={`${ - params ? 'rotate-180' : 'rotate-0' - }`} - > - <path - fillRule='evenodd' - clipRule='evenodd' - d='M0.614851 0.615358C1.00866 0.221668 1.54271 0.000505666 2.09955 0.000505642C2.6564 0.000505617 3.19044 0.221668 3.58425 0.615357L10.4996 7.53066L17.4149 0.615357C17.8109 0.232825 18.3414 0.0211567 18.892 0.0259414C19.4426 0.0307261 19.9693 0.25158 20.3587 0.640937C20.748 1.03029 20.9689 1.557 20.9737 2.10761C20.9784 2.65823 20.7668 3.18869 20.3842 3.58476L11.9843 11.9848C11.5904 12.3784 11.0564 12.5996 10.4996 12.5996C9.94271 12.5996 9.40866 12.3784 9.01485 11.9848L0.614851 3.58476C0.221162 3.19095 -4.3461e-07 2.6569 -4.5895e-07 2.10006C-4.8329e-07 1.54321 0.221162 1.00917 0.614851 0.615358Z' - fill='#7f7df3' - /> - </svg> - </Box> - )} - - {botParams && botParams.parameters?.length > 0 ? ( - <Collapse - in={params} - orientation='vertical' - collapsedSize={0} - sx={{}} - > - <BotParamsMap - currentVersion={version} - params={botParams?.parameters} - /> - <ResetFilters - desktop={desktop} - closeDrawer={hideMobileSettings} - reset={resetParams} - /> - </Collapse> - ) : ( - <Typography - sx={{ - color: '#6e6e6e', - fontSize: '15px', - fontWeight: '500', - }} - > - Параметры отсутствуют - </Typography> - )} - </Stack> - )} - {!desktop && ( - <DrawerCustom - open={openFiltersMobile} - onClose={hideMobileSettings} - > - <Stack spacing={1} padding={2.4}> - {botParams?.versions && botParams.versions?.length !== 0 && ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - margin: '20px 0px 0px !important', - }} - > - ВЕРСИИ - </Typography> - <ChatSelect - setDefaultParams={setDefaultParams} - value={version} - list={botParams.versions} - setValue={setVersion} - /> - </> - )} - {botParams && botParams.parameters?.length > 0 ? ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - margin: '20px 0px 0px !important', - }} - > - ПАРАМЕТРЫ - </Typography> - <BotParamsMap - currentVersion={version} - params={botParams?.parameters} - /> - <ResetFilters - closeDrawer={hideMobileSettings} - desktop={desktop} - reset={resetParams} - /> - </> - ) : ( - <Typography - sx={{ - color: '#6e6e6e', - fontSize: '15px', - fontWeight: '500', - }} - > - Параметры отсутствуют - </Typography> - )} - </Stack> - </DrawerCustom> - )} - </Box> - <Error error={error} open={Boolean(error)} /> - </Box> - </TutorialContext.Provider> - </Layout> - ) -} - -export default Page +export default ChatBotPage @@ -1,78 +1,6 @@ -import * as React from 'react' -import { useEffect, useState } from 'react' -import { Box, Typography } from '@mui/material' -import CircularProgress from '@mui/material/CircularProgress' -import { useSession } from 'next-auth/react' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { ChatBotsPage } from "#/views/chat-bot"; +import { getDefaultLayout } from "#/widgets/layouts"; -import ChatCard from '@/src/main/components/chat_bot_card' -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import model_api from '@/src/shared/api/models/api' -import { IShortModel } from '@/src/shared/api/models/models' -import { getTypeDevice } from '@/src/shared/lib/helpers' +ChatBotsPage.getLayout = getDefaultLayout({ titlePage: 'Чат-боты' }); -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const deviceType = getTypeDevice(context) - - return { - props: { - ...(await serverSideTranslations(context.locale, ['common'])), - deviceType, - }, - } -} - -const Page: React.FC<any> = ({ deviceType }) => { - const [bots, setBots] = useState<IShortModel[] | null>(null) - const { data } = useSession() - const user = useAppSelector((state) => state.user) - - useEffect(() => { - if (!data?.access) { - return - } - model_api - .getBots(data?.access) - .then((res) => { - setBots(res) - }) - .catch((err) => {}) - }, [data?.access]) - - return ( - <Layout titlePage={'Чат-боты'} device={deviceType}> - <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Чат-боты</Typography> - <Box display='flex' flexWrap='wrap' width='100%' justifyContent='start'> - {bots ? ( - bots.map((item, idx) => ( - <ChatCard - accessed_models={user.payment_plan.plan.accessed_models} - uid={item.uid} - key={idx} - title={item.title} - icon={item.image} - text={item.description} - slug={item.slug} - /> - )) - ) : ( - <CircularProgress - size={50} - thickness={3} - sx={{ - color: '#7F7DF3', - position: 'absolute', - top: '40%', - left: 0, - right: 0, - margin: '0 auto', - }} - /> - )} - </Box> - </Layout> - ) -} - -export default Page +export default ChatBotsPage \ No newline at end of file @@ -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<IDalleProps> = ({ 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<IDalleProps> = ({ device, token, favorites }) => { > <Typography className='title-struct'>{el.content.slice(0, 60)}...</Typography> <TooltipCustom title={'Скопировать ответ'}> - <Image onClick={() => copy(el.content)} src={'/svg/copy.svg'} width={22} height={22} alt={'copy'} /> + <Image + onClick={() => copy(el.content)} + src={'/svg/copy.svg'} + width={22} + height={22} + alt={'copy'} + /> </TooltipCustom> </Box> )) @@ -147,19 +155,31 @@ const Create: React.FC<IDalleProps> = ({ device, token, favorites }) => { } return ( - <Layout isLoader={currentTemplate === null} titlePage={'Копирайтинг'} device={device}> - <Title title={showGeneration ? 'Мои генерации' : theme ? theme : 'Пустой шаблон'} type={'Копирайтинг'} linkBack={'/copy'} /> + <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,552 +1,6 @@ -import * as React from 'react' -import { useRef } from 'react' -import { useDispatch } from 'react-redux' -import { Collapse, Typography } from '@mui/material' -import Box from '@mui/material/Box' -import Stack from '@mui/material/Stack' -import { useRouter } from 'next/router' -import { useSession } from 'next-auth/react' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { ImageModelPage } from '#/views/images' +import { getDefaultLayout } from '#/widgets/layouts' -import { Setting } from '@/src/domains/images-bots/babes/types' -import BotParamsMap from '@/src/features/bot-params/bot-params-map' -import Title from '@/src/features/title/title' -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 { Layout } from '@/src/main/layout' -import { setParams as setParametres } from '@/src/main/store/model-parametres-store' -import { useAppSelector } from '@/src/main/store/store' -import { DrawerCustom, Error } from '@/src/shared' -import model_api from '@/src/shared/api/models/api' -import { useModelImages } from '@/src/shared/api/models/endpoints' -import { IModel } from '@/src/shared/api/models/models' -import { getTypeDevice } from '@/src/shared/lib/helpers' -import { getDeviceOs } from '@/src/shared/lib/helpers/get-type-device' -import { useShowData } from '@/src/shared/lib/hooks' -import { ArrowDownScroll } from '@/src/shared/ui/icon-components/scroll-down-arrow' -import { ImageMessagesList } from '@/src/widgets/messages/image-messages-list' +ImageModelPage.getLayout = getDefaultLayout() -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const deviceType = getTypeDevice(context) - const deviceOs = getDeviceOs(context) - - return { - props: { - ...(await serverSideTranslations(context.locale, ['common'])), - deviceType, - deviceOs, - }, - } -} - -const Images: React.FC<any> = ({ deviceType, deviceOs }) => { - const [image, setImage] = React.useState<File | null>(null) - const desktop = deviceType === 'desktop' - const ios = deviceOs === 'ios' - const { error, showError } = useShowData() - const [openFiltersMobile, setOpenFiltersMobile] = React.useState<boolean>(false) - const router = useRouter() - const { data } = useSession() - const [botParams, setBotParams] = React.useState<IModel | null>(null) - const [version, setVersion] = React.useState<string>('') - const [modelType, setModelType] = React.useState<string>('') - const [params, setParams] = React.useState<boolean>(false) - const includeParams = useAppSelector((state) => state.params.params) - const dispatch = useDispatch() - - const { messages, loading, createImage, isComplete, getMessagesPagination } = useModelImages<Setting>(showError, modelType, deviceType) - - const [chatScrollHeight, setChatScrollHeight] = React.useState(0) - const [scrollBottom, setScrollBottom] = React.useState(0) - const refScrollMobile = useRef<any>() - const [isPaginating, setIsPaginating] = React.useState(false) - - React.useEffect(() => { - model_api - .getBotParams(router.asPath.split('/')[2], data?.access) - .then((res) => { - setBotParams(res) - setModelType(res.slug) - if (res.versions.length !== 0) { - setVersion(res.versions[0].slug) - dispatch( - setParametres( - res.parameters.reduce( - (a, v) => - v.versions.includes(res.versions[0].slug) - ? { ...a, [v.key]: v.values.default } - : { ...a }, - {} - ) - ) - ) - } else { - setVersion('') - dispatch( - setParametres( - res.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) - } - }) - .catch((err) => {}) - }, [data?.access, router.query]) - - const onLoadImage = (event: React.ChangeEvent<HTMLInputElement>) => { - if (event.target.files) { - setImage(event.target.files[0]) - // showError('Файл успешно загружен, можете отправлять его!') - } - } - - const viewMobileSettings = () => { - setOpenFiltersMobile(true) - } - - const hideMobileSettings = () => { - setOpenFiltersMobile(false) - } - - const resetParams = () => { - if (botParams) { - dispatch(setParametres({})) - if (botParams.versions.length !== 0) { - setVersion(botParams.versions[0].slug) - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => - v.versions.includes(botParams.versions[0].slug) - ? { ...a, [v.key]: v.values.default } - : { ...a }, - {} - ) - ) - ) - } else { - setVersion(botParams.slug) - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) - } - } - } - - const setDefaultParams = () => { - if (botParams) { - dispatch(setParametres({})) - if (version !== '') { - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => - v.versions.includes(version) - ? { ...a, [v.key]: v.values.default } - : { ...a }, - {} - ) - ) - ) - } else { - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) - } - } - } - - const onCreateImage = (input: string, required: (string | null)[]) => { - if (required.includes('text') && (input === '' || input === null)) { - showError('Введите сообщение!') - return false - } - if (required.includes('image') && image === null) { - showError('Прикрепите изображение!') - return false - } - if (required.includes('zip') && image === null) { - showError('Прикрепите архив!') - return false - } - let data = {} - if (version === '') { - data = { - ...includeParams, - } - } else { - data = { - version: version, - ...includeParams, - } - } - - createImage({ - content: input, - file: image, - info: { - ...data, - }, - }) - return true - } - - React.useEffect(() => { - if (isComplete) { - if (!desktop) { - const block = refScrollMobile.current - if (block) { - //@ts-ignore - block.scrollTop = block.scrollHeight - } - } else { - window.scroll(0, 0) - } - } - }, [isComplete]) - - const handleMobileScroll = () => { - setScrollBottom(refScrollMobile.current?.scrollHeight - refScrollMobile.current?.scrollTop - refScrollMobile.current?.clientHeight) - - if (refScrollMobile.current && messages?.length !== 0) { - const { scrollTop, scrollHeight, clientHeight } = refScrollMobile.current - if (scrollTop === 0) { - if (getMessagesPagination) { - setIsPaginating(true) - getMessagesPagination(deviceType) - } - } - } - } - - const handleScroll = () => { - if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight) { - setIsPaginating(true) - getMessagesPagination(deviceType) - } - } - - React.useEffect(() => { - window.addEventListener('scroll', handleScroll) - return () => { - window.removeEventListener('scroll', handleScroll) - } - }, []) - - React.useEffect(() => { - const block = deviceType === 'desktop' ? window : refScrollMobile.current - if (block) { - if (messages != undefined && !isPaginating) { - setChatScrollHeight(block.scrollHeight) - const time = setTimeout(() => { - //@ts-ignore - block.scrollTo({ - top: block.scrollHeight, - behavior: 'smooth', // добавляем плавную прокрутку - }) - }, 350) - return () => clearTimeout(time) - } else if (messages != undefined && isPaginating) { - //@ts-ignore - block.scrollTop = block.scrollHeight - chatScrollHeight - - setChatScrollHeight(block.scrollHeight) - } - } - setIsPaginating(false) - }, [messages]) - - return ( - <Layout titlePage={botParams?.title ? botParams?.title : ''} device={deviceType}> - <Title title={botParams?.title} type={'Изображения'} linkBack={'/images'} /> - <Box - display={'flex'} - justifyContent='space-between' - alignItems='start' - flexDirection={desktop ? 'row' : 'column-reverse'} - sx={{ - marginBottom: desktop ? 0 : 3, - width: desktop ? '97%' : '100%', - marginTop: desktop ? 3 : '15px', - }} - > - <Box - sx={{ - width: desktop ? '73%' : '100%', - marginLeft: 0, - display: 'flex', - flexDirection: desktop ? 'column' : 'column-reverse', - }} - > - {desktop ? ( - <> - <Stack alignItems='center' sx={{ marginBottom: '15px' }}> - {botParams && ( - <UniqInput - currentVersion={version} - styles={'images'} - input_types={botParams.inputs} - image={image} - value={prompt} - desktop={desktop} - loading={loading} - imageLoad={onLoadImage} - sendMessage={onCreateImage} - unpinImage={() => setImage(null)} - viewMobileSettings={viewMobileSettings} - /> - )} - </Stack> - <Box - className={'bg-color-block border-radius-main'} - sx={{ padding: '30px' }} - > - <ImageMessagesList - isComplete={isComplete} - device={deviceType} - images={messages} - /> - </Box> - </> - ) : ( - <Box className={'bg-color-block border-radius-main'}> - {scrollBottom > 500 && ( - <Box - sx={{ - position: 'absolute', - left: 0, - right: 0, - width: 'fit-content', - cursor: 'pointer', - margin: '0 auto', - bottom: '100px', - zIndex: 10, - }} - onClick={() => { - const block = refScrollMobile.current - - block.scrollTo({ - top: block.scrollHeight, - behavior: 'smooth', // добавляем плавную прокрутку - }) - }} - > - <ArrowDownScroll /> - </Box> - )} - <Box - ref={refScrollMobile} - sx={{ - padding: '30px', - height: ios - ? 'calc(200px + (400 - 200) * ((100vh - 400px) / (600 - 400)) - 100px)' - : 'calc(200px + (400 - 200) * ((100vh - 400px) / (600 - 400)))', - overflowY: 'scroll', - overflowX: 'hidden', - }} - className={'smallScroll'} - onScroll={handleMobileScroll} - > - <ImageMessagesList - isComplete={isComplete} - device={deviceType} - images={messages} - /> - </Box> - <Stack alignItems='center'> - {botParams && ( - <UniqInput - currentVersion={version} - styles={'images'} - input_types={botParams.inputs} - image={image} - value={prompt} - desktop={desktop} - loading={loading} - imageLoad={onLoadImage} - sendMessage={onCreateImage} - unpinImage={() => setImage(null)} - viewMobileSettings={viewMobileSettings} - /> - )} - </Stack> - </Box> - )} - </Box> - {desktop && ( - <Stack - spacing={2} - className='pd-30 bg-color-block border-radius-main' - sx={{ width: '25.5%', height: 'auto' }} - > - {botParams?.versions && botParams.versions.length !== 0 ? ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - }} - > - ВЕРСИИ - </Typography> - <ChatSelect - setDefaultParams={setDefaultParams} - value={version} - list={botParams.versions} - setValue={setVersion} - /> - </> - ) : ( - <></> - )} - {botParams && botParams.parameters?.length > 0 && ( - <Box - display={'flex'} - alignItems={'center'} - gap={'5px'} - sx={{ cursor: 'pointer' }} - onClick={() => { - setParams(!params) - }} - > - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - }} - > - ПАРАМЕТРЫ - </Typography> - <svg - width='14' - height='20' - viewBox='0 0 21 13' - fill='none' - xmlns='http://www.w3.org/2000/svg' - className={`${params ? 'rotate-180' : 'rotate-0'}`} - > - <path - fillRule='evenodd' - clipRule='evenodd' - d='M0.614851 0.615358C1.00866 0.221668 1.54271 0.000505666 2.09955 0.000505642C2.6564 0.000505617 3.19044 0.221668 3.58425 0.615357L10.4996 7.53066L17.4149 0.615357C17.8109 0.232825 18.3414 0.0211567 18.892 0.0259414C19.4426 0.0307261 19.9693 0.25158 20.3587 0.640937C20.748 1.03029 20.9689 1.557 20.9737 2.10761C20.9784 2.65823 20.7668 3.18869 20.3842 3.58476L11.9843 11.9848C11.5904 12.3784 11.0564 12.5996 10.4996 12.5996C9.94271 12.5996 9.40866 12.3784 9.01485 11.9848L0.614851 3.58476C0.221162 3.19095 -4.3461e-07 2.6569 -4.5895e-07 2.10006C-4.8329e-07 1.54321 0.221162 1.00917 0.614851 0.615358Z' - fill='#7f7df3' - /> - </svg> - </Box> - )} - - {botParams && botParams.parameters?.length > 0 ? ( - <Collapse - in={params} - orientation='vertical' - collapsedSize={0} - sx={{}} - > - <BotParamsMap - currentVersion={version} - params={botParams?.parameters} - /> - <ResetFilters - desktop={desktop} - closeDrawer={hideMobileSettings} - reset={resetParams} - /> - </Collapse> - ) : ( - <Typography - sx={{ - color: '#6e6e6e', - fontSize: '15px', - fontWeight: '500', - }} - > - Параметры отсутствуют - </Typography> - )} - </Stack> - )} - <DrawerCustom open={openFiltersMobile} onClose={hideMobileSettings}> - <Stack spacing={1} padding={2.4}> - {botParams?.versions && botParams.versions.length !== 0 ? ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - margin: '20px 0px 0px !important', - }} - > - ВЕРСИИ - </Typography> - <ChatSelect - setDefaultParams={setDefaultParams} - value={version} - list={botParams.versions} - setValue={setVersion} - /> - </> - ) : ( - <></> - )} - {botParams && botParams.parameters?.length > 0 ? ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - margin: '20px 0px 0px !important', - }} - > - ПАРАМЕТРЫ - </Typography> - <BotParamsMap - currentVersion={version} - params={botParams?.parameters} - /> - <ResetFilters - closeDrawer={hideMobileSettings} - desktop={desktop} - reset={resetParams} - /> - </> - ) : ( - <Typography - sx={{ - color: '#6e6e6e', - fontSize: '15px', - fontWeight: '500', - }} - > - Параметры отсутствуют - </Typography> - )} - </Stack> - </DrawerCustom> - <Error error={error} open={Boolean(error)} /> - </Box> - </Layout> - ) -} - -export default Images +export default ImageModelPage @@ -1,76 +1,6 @@ -import * as React from 'react' -import { useEffect, useState } from 'react' -import { Box, Typography } from '@mui/material' -import CircularProgress from '@mui/material/CircularProgress' -import { useSession } from 'next-auth/react' -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { ImageModelsPage } from "#/views/images"; +import { getDefaultLayout } from "#/widgets/layouts"; -import { Layout } from '@/src/main/layout' -import { useAppSelector } from '@/src/main/store/store' -import model_api from '@/src/shared/api/models/api' -import { IShortModel } from '@/src/shared/api/models/models' -import { Card } from '@/src/shared/card/card' -import { getTypeDevice } from '@/src/shared/lib/helpers' +ImageModelsPage.getLayout = getDefaultLayout({ titlePage: 'Изображения' }) -export async function getServerSideProps(context: any): Promise<{ props: any }> { - const deviceType = getTypeDevice(context) - - return { - props: { - ...(await serverSideTranslations(context.locale, ['common'])), - deviceType, - }, - } -} - -export const Images: React.FC<any> = ({ deviceType }) => { - const [bots, setBots] = useState<IShortModel[] | null>(null) - const user = useAppSelector((state) => state.user) - const { data } = useSession() - - useEffect(() => { - if (!data?.access) { - return - } - - model_api.getImages(data?.access).then((res) => setBots(res)) - }, [data?.access]) - - return ( - <Layout titlePage={'Изображения'} device={deviceType}> - <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Изображения</Typography> - <Box display='flex' flexWrap='wrap'> - {bots ? ( - bots.map((item, idx) => { - return ( - <Card - accessed_models={user.payment_plan.plan.accessed_models} - uid={item.uid} - key={idx} - slug={item.slug} - text={item.description} - title={item.title} - icon={item.image} - /> - ) - }) - ) : ( - <CircularProgress - size={50} - thickness={3} - sx={{ - color: '#7F7DF3', - position: 'absolute', - top: '40%', - left: 0, - right: 0, - margin: '0 auto', - }} - /> - )} - </Box> - </Layout> - ) -} - -export default Images +export default ImageModelsPage \ No newline at end of file @@ -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 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,12 +8,14 @@ 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 '#/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, @@ -21,7 +23,17 @@ axios.defaults.httpsAgent = new https.Agent({ const inter = Raleway({ subsets: ['latin'] }) -function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { +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 ( <> @@ -35,7 +47,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> @@ -27,7 +27,11 @@ export default function Document() { <body> <noscript> <div> - <img src='https://mc.yandex.ru/watch/93475901' style={{ position: 'absolute', left: '-9999px' }} alt='' /> + <img + src='https://mc.yandex.ru/watch/93475901' + style={{ position: 'absolute', left: '-9999px' }} + alt='' + /> </div> </noscript> <Main /> @@ -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 @@ -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 { IModel, IShortModel } from '@/src/shared/api/models/models' -import { API_URL } from '@/src/shared/lib/constants' +import { IModel, IShortModel } from '#/shared/api/models/models' +import { API_URL } from '#/shared/lib/constants' 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 { Message, MessageSend } from '@/src/shared/lib/types/model' -import { IMessageRequest } from '@/src/shared/lib/types/types-gpt' +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 { Message, MessageSend } from '#/shared/lib/types/model' +import { IMessageRequest } from '#/shared/lib/types/types-gpt' const formDataHelper = (file: File, dataForSend: MessageSend<any>): FormData => { const FD = new FormData() @@ -23,11 +23,14 @@ const formDataHelper = (file: File, dataForSend: MessageSend<any>): FormData => export const ModelsWithChatsEndpoints = { getData: async (chatUid: string, offset: number, token?: string) => { try { - const { data } = await axios.get<Message[]>(API_URL + `/chats/${chatUid}/messages/?limit=10&offset=${offset}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.get<Message[]>( + API_URL + `/chats/${chatUid}/messages/?limit=10&offset=${offset}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return data } catch (err: any) { return { @@ -41,13 +44,17 @@ export const ModelsWithChatsEndpoints = { const HeaderDataType = dataForSend instanceof FormData ? 'multipart/form-data' : 'application/json' try { - const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>(API_URL + `/chats/${chatUid}/messages/`, dataForSend, { - withCredentials: true, - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': HeaderDataType, - }, - }) + const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>( + API_URL + `/chats/${chatUid}/messages/`, + dataForSend, + { + withCredentials: true, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': HeaderDataType, + }, + } + ) return data } catch (err: any) { @@ -165,7 +172,7 @@ export function useModel( setMessages((prev) => [...prev!, userMessage]) try { //@ts-ignore - const message = (result.details as AxiosError).response.data.trim() ?? 'Ошибка отправки сообщения' + const message = (result.details as AxiosError).response!.data.trim() ?? 'Ошибка отправки сообщения' showError(message) return } catch (e) { @@ -212,13 +219,17 @@ export const ModelsWithImagesEndpoints = { const HeaderDataType = dataForSend instanceof FormData ? 'multipart/form-data' : 'application/json' try { - const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>(API_URL + `/media/image/${type}`, dataForSend, { - withCredentials: true, - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': HeaderDataType, - }, - }) + const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>( + API_URL + `/media/image/${type}`, + dataForSend, + { + withCredentials: true, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': HeaderDataType, + }, + } + ) return data } catch (err: any) { @@ -375,12 +386,16 @@ export const ModelsMediaApi = { }, sendData: async <T>(type: string | null, dataForSend: MessageSend<T> | FormData, token?: string) => { try { - const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>(API_URL + `/media/audio/${type}`, dataForSend, { - withCredentials: true, - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const { data } = await axios.post<IMessageRequest, AxiosResponse<Message[]>>( + API_URL + `/media/audio/${type}`, + dataForSend, + { + withCredentials: true, + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) return data } catch (err: any) { @@ -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,32 +1,30 @@ .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; + 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; + @media (max-width: 420px) { + width: 92vw; + height: 300px; + } - .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); - } - } + .description { + padding: 15px 20px; - -} \ No newline at end of file + .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); + } + } +} @@ -16,9 +16,7 @@ interface IProps { export const Card = ({ text, icon, title, uid, slug, accessed_models }: IProps) => { const link = useMemo(() => { - return accessed_models && !accessed_models.includes(slug) - ? '/account?scope=subscribe' - : `/images/${slug}` + return accessed_models && !accessed_models.includes(slug) ? '/account?scope=subscribe' : `/images/${slug}` }, [accessed_models]) return ( @@ -63,11 +61,7 @@ export const Card = ({ text, icon, title, uid, slug, accessed_models }: IProps) textAlign: 'center', }} > - <Button - disabled - className='btn-classic' - sx={{ width: '86%', top: '10%' }} - > + <Button disabled className='btn-classic' sx={{ width: '86%', top: '10%' }}> <Image style={{ marginRight: '7px', marginBottom: '2px' }} src={'/star-white.svg'} @@ -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, @@ -0,0 +1,3 @@ +export const getRandomImage = (): string => { + return String(Math.floor(Math.random() * 29) + 1) + '.png' +} @@ -1,7 +1,10 @@ import { GetServerSidePropsContext } from 'next' -import { Device, DeviceOs } from '@/src/shared/lib/types/entities' +import { Device, DeviceOs } from '#/shared/lib/types/entities' +/** + * @deprecated + */ export const getTypeDevice = (context: GetServerSidePropsContext): Device => { const UA = context.req.headers['user-agent'] @@ -14,6 +17,22 @@ export const getTypeDevice = (context: GetServerSidePropsContext): Device => { return isMobile ? 'mobile' : 'desktop' } +export const getDeviceType = (): Device => { + const UA = navigator.userAgent + + if (!UA) { + return 'desktop' + } + + const isMobile = Boolean(UA.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i)) + + return isMobile ? 'mobile' : 'desktop' +} + + +/** + * @deprecated + */ export const getDeviceOs = (context: GetServerSidePropsContext): DeviceOs => { const UA = context.req.headers['user-agent'] @@ -25,3 +44,15 @@ export const getDeviceOs = (context: GetServerSidePropsContext): DeviceOs => { return isIos ? 'ios' : 'android' } + +export const getOs = (): DeviceOs => { + const UA = navigator.userAgent + + if (!UA) { + return 'android' + } + + const isIos = UA.includes('iPhone') || UA.includes('iPad') + + return isIos ? 'ios' : 'android' +} @@ -12,7 +12,20 @@ export function getCurrentTimeAndGreeting(userName: string) { greeting = 'Добрый вечер' } - const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'] + const months = [ + 'января', + 'февраля', + 'марта', + 'апреля', + 'мая', + 'июня', + 'июля', + 'августа', + 'сентября', + 'октября', + 'ноября', + 'декабря', + ] const formattedDate = `Сегодня, ${day} ${months[monthIndex]} ${year}` const greetingMessage = `${greeting}, ${userName}! 👋` @@ -1,3 +1,5 @@ export { getAccessToken } from './get-token' export { getTypeDevice } from './get-type-device' export { useConcat } from './reactive-concat' +export * from './get-type-device' +export * from './get-random-image' @@ -7,7 +7,7 @@ export class LocalStorageHelper { this.modelTitle = key.toLowerCase() } - public readonly getSettingFromLS = (key: string, defaultValue: any, availableModels?:any[]) => { + public readonly getSettingFromLS = (key: string, defaultValue: any, availableModels?: any[]) => { const finalKey = this.modelTitle + ': ' + key if (typeof localStorage !== 'undefined') { @@ -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' interface IThemeAndDevice { theme: 'dark' | 'light' @@ -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 @@ -49,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,44 +1,44 @@ .main { - width: 100%; - display: flex; - justify-content: flex-start; - gap: 20px; - margin-bottom: 24px; + width: 100%; + display: flex; + justify-content: flex-start; + gap: 20px; + margin-bottom: 24px; - .chatWindow { - width: 70%; - } + .chatWindow { + width: 70%; + } - .settings { - width: 25%; - } + .settings { + width: 25%; + } - @media (max-width:768px) { - width: 100%; - display: flex; - - flex-direction: column-reverse; - - .chatWindow { - width: 100%; - } + @media (max-width: 768px) { + width: 100%; + display: flex; - .settings { - width: 100%; - } - } + 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; - } - } +.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; + } + } } @@ -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,5 +1,5 @@ .button { - background-color: #8280FF; + background-color: #8280ff; border-radius: 13px; text-transform: none; color: white; @@ -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,7 +1,7 @@ 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 { @@ -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> @@ -27,5 +27,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,586 @@ +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 device = getDeviceType() + + const { + email, + is_subscribed_to_emails, + account_type, + first_name, + last_name, + username, + profile_picture_link, + is_social, + status: userInfoLoaded, + } = 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] = useState<string>('') + + const [newPassword1, setNewPassword1] = useState<string>('') + + const [newPassword2, setNewPassword2] = useState<string>('') + + const [promocode, setPromocode] = useState<string>('') + + const [success, setSuccess] = 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, [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 ${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 (!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 () => { + 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) + }, [first_name, last_name]) + + 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 () => { + 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 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> + ) +} + +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' @@ -0,0 +1,42 @@ +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 '#/shared/card/card' +import { AudioModel } from '#/app/types/model' +import { getAudio } from '#/entities/audio-model' +import { NextPageWithLayout } from '#/pages/_app' + +export const AudioModelsPage: NextPageWithLayout = () => { + const [bots, setBots] = useState<AudioModel[] | null>(null) + + const { data, status } = useSession() + + useEffect(() => { + if (!data) return + + getAudio(data.access).then((res) => setBots(res)) + }, [status]) + + return ( + <> + <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Аудио</Typography> + <Box display='flex' flexWrap='wrap'> + {bots?.map((item, idx) => { + return ( + <Card + uid={item.uid} + key={item.uid} + title={item.title} + icon={item.image_link} + text={item.description} + slug={item.slug} + /> + ) + })} + </Box> + </> + ) +} + +export default AudioModelsPage @@ -0,0 +1 @@ +export { default as AudioModelsPage } from './audio-models' @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -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 @@ -1,35 +1,23 @@ -import React from 'react' +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 Head from 'next/head' 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' -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' +const ChangePassword: NextPageWithLayout = () => { + const device = getDeviceType() -export async function getServerSideProps(context: any): Promise<{ props: TDeviceProp }> { - const device = getTypeDevice(context) + const [password1, setPassword1] = useState<string>('') - return { - props: { - device, - }, - } -} - -const ChangePassword: React.FC<TDeviceProp> = ({ device }) => { - const [password1, setPassword1] = React.useState<string>('') + const [password2, setPassword2] = useState<string>('') - const [password2, setPassword2] = React.useState<string>('') - - const [isChange, setIsChange] = React.useState(false) + const [isChange, setIsChange] = useState(false) const { query, push } = useRouter() @@ -54,18 +42,12 @@ const ChangePassword: React.FC<TDeviceProp> = ({ device }) => { const desktop = device === 'desktop' - const refImage = React.useRef(getRandomImage()) + const refImage = useRef(getRandomImage()) const theme = useAppSelector((state) => state.theme.theme) return ( - <Layout titlePage={'Авторизация'} isAuthPage={true}> - <Head> - <title>Восстановление аккаунта - - - - + <> = ({ device }) => { {desktop && ( - + = ({ device }) => { )} - + ) } @@ -0,0 +1 @@ +export { default as ChangePasswordPage } from './change-password' @@ -0,0 +1 @@ +export * from './ui' @@ -0,0 +1,380 @@ +import React, { useCallback, useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { Box, Collapse, Stack, Typography } from '@mui/material' +import { useRouter } from 'next/router' +import { useSession } from 'next-auth/react' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +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 { AllChatWindow } from '#/app/components/chat_window' +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 { IModel } from '#/shared/api/models/models' +import styles from '#/shared/styles/chats-bot-pages.module.scss' +import { Chats } from '#/widgets/chats' +import { getDeviceType, getOs } from '#/shared/lib/helpers' +import { NextPageWithLayout } from '#/pages/_app' +import { getDefaultLayoutDynamic } from '#/widgets/layouts' +import Head from 'next/head' + +export interface ChatBotPageProps {} + +const Page: NextPageWithLayout = () => { + const [botParams, setBotParams] = React.useState(null) + const [modelType, setModelType] = React.useState('') + const [openFiltersMobile, setOpenFiltersMobile] = React.useState(false) + const [version, setVersion] = React.useState('') + const [step, setStep] = React.useState(0) + + const [params, setParams] = React.useState(false) + const [file, setFile] = React.useState(null) + const { error, showError } = useShowData() + + const deviceType = getDeviceType() + const deviceOs = getOs() + + const desktop = deviceType === 'desktop' + const { data } = useSession() + const router = useRouter() + + const { + chats, + currentChat, + chatSetting, + setChatSetting, + setChat, + removeChat, + createNewChat, + handleClickChatSetting, + isTryRename, + setIsTryRename, + } = useChats(modelType) + + const { messages, sendMessage, loading, getMessagesPagination, deleteMessage } = useModel( + currentChat, + showError, + modelType + ) + + const includeParams = useAppSelector((state) => state.params.params) + const dispatch = useDispatch() + + const deleteMessageMemo = useCallback(deleteMessage, [currentChat, messages]) + + React.useEffect(() => { + if (data?.access) { + model_api.getBotParams(router.asPath.split('/')[2], data.access).then((res) => { + setBotParams(res) + setModelType(res.slug) + if (res.versions.length !== 0) { + setVersion(res.versions[0].slug) + dispatch( + setParametres( + res.parameters.reduce( + (a, v) => + v.versions.includes(res.versions[0].slug) + ? { ...a, [v.key]: v.values.default } + : { ...a }, + {} + ) + ) + ) + } else { + setVersion('') + dispatch( + setParametres(res.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) + ) + } + }) + } + }, [data?.access, router.query]) + + const resetParams = () => { + if (botParams) { + dispatch(setParametres({})) + if (botParams.versions?.length !== 0) { + setVersion(botParams.versions[0].slug) + dispatch( + setParametres( + botParams.parameters.reduce( + (a, v) => + v.versions.includes(botParams.versions[0].slug) + ? { ...a, [v.key]: v.values.default } + : { ...a }, + {} + ) + ) + ) + } else { + setVersion(botParams.slug) + dispatch( + setParametres(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) + ) + } + } + } + + const setDefaultParams = () => { + if (botParams) { + dispatch(setParametres({})) + if (version !== '') { + dispatch( + setParametres( + botParams.parameters.reduce( + (a, v) => + v.versions.includes(version) ? { ...a, [v.key]: v.values.default } : { ...a }, + {} + ) + ) + ) + } else { + dispatch( + setParametres(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) + ) + } + } + } + + const viewMobileSettings = () => { + setOpenFiltersMobile(true) + } + + const hideMobileSettings = () => { + setOpenFiltersMobile(false) + } + + const onSendMessage = (input: string, required: (string | null)[]) => { + if (required.includes('text') && (input === '' || input === null)) { + showError('Введите сообщение!') + return false + } + if (required.includes('image') && file === null) { + showError('Прикрепите изображение!') + return false + } + + let data = {} + if (version === '') { + data = { + ...includeParams, + } + } else { + data = { + version: version, + ...includeParams, + } + } + + sendMessage({ + content: input, + file: file, + info: { + ...data, + }, + }) + return true + } + + return ( + <> + + {botParams ? botParams.title : 'Загрузка...'} + + + + <Box className={styles.main}> + <Box className={styles.chatWindow}> + <AllChatWindow + deviceOs={deviceOs} + input_types={botParams?.inputs} + setFile={setFile} + file={file} + getMessagesPagination={getMessagesPagination} + sendMessage={onSendMessage} + deleteMessage={deleteMessageMemo} + openMobileFilters={viewMobileSettings} + isCalculating={true} + device={deviceType} + loading={loading} + messages={messages || []} + modelType={modelType} + modelTitle={botParams?.title} + currentVersion={version} + /> + </Box> + <Box className={styles.settings}> + <Chats + desktop={desktop} + isTryRename={isTryRename} + setIsTryRename={setIsTryRename} + currentChat={currentChat} + setChat={setChat} + chats={chats} + chatSetting={chatSetting} + setChatSetting={setChatSetting} + removeChat={removeChat} + createNewChat={createNewChat} + handleClickChatSetting={handleClickChatSetting} + /> + {desktop && ( + <Stack className='pd-30 bg-color-block border-radius-main' spacing={2}> + {botParams?.versions && botParams.versions?.length !== 0 && ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + }} + > + ВЕРСИИ + </Typography> + <ChatSelect + setDefaultParams={setDefaultParams} + value={version} + list={botParams.versions} + setValue={setVersion} + /> + </> + )} + {botParams && botParams.parameters?.length > 0 && ( + <Box + display={'flex'} + alignItems={'center'} + gap={'5px'} + sx={{ cursor: 'pointer' }} + onClick={() => { + setParams(!params) + }} + > + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + }} + > + ПАРАМЕТРЫ + </Typography> + <svg + width='14' + height='20' + viewBox='0 0 21 13' + fill='none' + xmlns='http://www.w3.org/2000/svg' + className={`${params ? 'rotate-180' : 'rotate-0'}`} + > + <path + fillRule='evenodd' + clipRule='evenodd' + d='M0.614851 0.615358C1.00866 0.221668 1.54271 0.000505666 2.09955 0.000505642C2.6564 0.000505617 3.19044 0.221668 3.58425 0.615357L10.4996 7.53066L17.4149 0.615357C17.8109 0.232825 18.3414 0.0211567 18.892 0.0259414C19.4426 0.0307261 19.9693 0.25158 20.3587 0.640937C20.748 1.03029 20.9689 1.557 20.9737 2.10761C20.9784 2.65823 20.7668 3.18869 20.3842 3.58476L11.9843 11.9848C11.5904 12.3784 11.0564 12.5996 10.4996 12.5996C9.94271 12.5996 9.40866 12.3784 9.01485 11.9848L0.614851 3.58476C0.221162 3.19095 -4.3461e-07 2.6569 -4.5895e-07 2.10006C-4.8329e-07 1.54321 0.221162 1.00917 0.614851 0.615358Z' + fill='#7f7df3' + /> + </svg> + </Box> + )} + + {botParams && botParams.parameters?.length > 0 ? ( + <Collapse in={params} orientation='vertical' collapsedSize={0} sx={{}}> + <BotParamsMap + currentVersion={version} + params={botParams?.parameters} + /> + <ResetFilters + desktop={desktop} + closeDrawer={hideMobileSettings} + reset={resetParams} + /> + </Collapse> + ) : ( + <Typography + sx={{ + color: '#6e6e6e', + fontSize: '15px', + fontWeight: '500', + }} + > + Параметры отсутствуют + </Typography> + )} + </Stack> + )} + {!desktop && ( + <DrawerCustom open={openFiltersMobile} onClose={hideMobileSettings}> + <Stack spacing={1} padding={2.4}> + {botParams?.versions && botParams.versions?.length !== 0 && ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + margin: '20px 0px 0px !important', + }} + > + ВЕРСИИ + </Typography> + <ChatSelect + setDefaultParams={setDefaultParams} + value={version} + list={botParams.versions} + setValue={setVersion} + /> + </> + )} + {botParams && botParams.parameters?.length > 0 ? ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + margin: '20px 0px 0px !important', + }} + > + ПАРАМЕТРЫ + </Typography> + <BotParamsMap + currentVersion={version} + params={botParams?.parameters} + /> + <ResetFilters + closeDrawer={hideMobileSettings} + desktop={desktop} + reset={resetParams} + /> + </> + ) : ( + <Typography + sx={{ + color: '#6e6e6e', + fontSize: '15px', + fontWeight: '500', + }} + > + Параметры отсутствуют + </Typography> + )} + </Stack> + </DrawerCustom> + )} + </Box> + <Error error={error} open={Boolean(error)} /> + </Box> + </TutorialContext.Provider> + </> + ) +} + +export default Page @@ -0,0 +1,65 @@ +import * as React from 'react' +import { useEffect, useState } from 'react' +import { Box, Typography } from '@mui/material' +import CircularProgress from '@mui/material/CircularProgress' +import { useSession } from 'next-auth/react' + +import ChatCard from '#/app/components/chat_bot_card' +import { useAppSelector } from '#/app/store/store' +import model_api from '#/shared/api/models/api' +import { IShortModel } from '#/shared/api/models/models' +import { NextPageWithLayout } from '#/pages/_app' + +const Page: NextPageWithLayout = () => { + const [bots, setBots] = useState<IShortModel[] | null>(null) + const { data } = useSession() + const user = useAppSelector((state) => state.user) + + useEffect(() => { + if (!data?.access) { + return + } + model_api + .getBots(data?.access) + .then((res) => { + setBots(res) + }) + .catch((err) => {}) + }, [data?.access]) + + return ( + <> + <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Чат-боты</Typography> + <Box display='flex' flexWrap='wrap' width='100%' justifyContent='start'> + {bots ? ( + bots.map((item, idx) => ( + <ChatCard + accessed_models={user.payment_plan.plan.accessed_models} + uid={item.uid} + key={idx} + title={item.title} + icon={item.image} + text={item.description} + slug={item.slug} + /> + )) + ) : ( + <CircularProgress + size={50} + thickness={3} + sx={{ + color: '#7F7DF3', + position: 'absolute', + top: '40%', + left: 0, + right: 0, + margin: '0 auto', + }} + /> + )} + </Box> + </> + ) +} + +export default Page @@ -0,0 +1,2 @@ +export { default as ChatBotsPage } from './chat-bots' +export { default as ChatBotPage } from './chat-bot' @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -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' @@ -0,0 +1,523 @@ +import * as React from 'react' +import { useRef } from 'react' +import { useDispatch } from 'react-redux' +import { Collapse, Typography } from '@mui/material' +import Box from '@mui/material/Box' +import Stack from '@mui/material/Stack' +import { useRouter } from 'next/router' +import { useSession } from 'next-auth/react' + +import { Setting } from '#/domains/images-bots/babes/types' +import BotParamsMap from '#/features/bot-params/bot-params-map' +import Title from '#/features/title/title' +import { ChatSelect } from '#/app/components/chat_select' +import { ResetFilters } from '#/app/components/filters/reset_filters' +import { UniqInput } from '#/app/components/uniq_input' +import { setParams as setParametres } from '#/app/store/model-parametres-store' +import { useAppSelector } from '#/app/store/store' +import { DrawerCustom, Error } from '#/shared' +import model_api from '#/shared/api/models/api' +import { useModelImages } from '#/shared/api/models/endpoints' +import { IModel } from '#/shared/api/models/models' +import { useShowData } from '#/shared/lib/hooks' +import { ArrowDownScroll } from '#/shared/ui/icon-components/scroll-down-arrow' +import { ImageMessagesList } from '#/widgets/messages/image-messages-list' +import { getDeviceType, getOs } from '#/shared/lib/helpers' +import Head from 'next/head' +import { NextPageWithLayout } from '#/pages/_app' + +const ImageModelPage: NextPageWithLayout = () => { + const [image, setImage] = React.useState<File | null>(null) + + const deviceType = getDeviceType() + const deviceOs = getOs() + + const desktop = deviceType === 'desktop' + const ios = deviceOs === 'ios' + + const { error, showError } = useShowData() + const [openFiltersMobile, setOpenFiltersMobile] = React.useState<boolean>(false) + const router = useRouter() + const { data } = useSession() + const [botParams, setBotParams] = React.useState<IModel | null>(null) + const [version, setVersion] = React.useState<string>('') + const [modelType, setModelType] = React.useState<string>('') + const [params, setParams] = React.useState<boolean>(false) + const includeParams = useAppSelector((state) => state.params.params) + const dispatch = useDispatch() + + const { messages, loading, createImage, isComplete, getMessagesPagination } = useModelImages<Setting>( + showError, + modelType, + deviceType + ) + + const [chatScrollHeight, setChatScrollHeight] = React.useState(0) + const [scrollBottom, setScrollBottom] = React.useState(0) + const refScrollMobile = useRef<any>() + const [isPaginating, setIsPaginating] = React.useState(false) + + React.useEffect(() => { + model_api + .getBotParams(router.asPath.split('/')[2], data?.access) + .then((res) => { + setBotParams(res) + setModelType(res.slug) + if (res.versions.length !== 0) { + setVersion(res.versions[0].slug) + dispatch( + setParametres( + res.parameters.reduce( + (a, v) => + v.versions.includes(res.versions[0].slug) + ? { ...a, [v.key]: v.values.default } + : { ...a }, + {} + ) + ) + ) + } else { + setVersion('') + dispatch( + setParametres(res.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) + ) + } + }) + .catch((err) => {}) + }, [data?.access, router.query]) + + const onLoadImage = (event: React.ChangeEvent<HTMLInputElement>) => { + if (event.target.files) { + setImage(event.target.files[0]) + // showError('Файл успешно загружен, можете отправлять его!') + } + } + + const viewMobileSettings = () => { + setOpenFiltersMobile(true) + } + + const hideMobileSettings = () => { + setOpenFiltersMobile(false) + } + + const resetParams = () => { + if (botParams) { + dispatch(setParametres({})) + if (botParams.versions.length !== 0) { + setVersion(botParams.versions[0].slug) + dispatch( + setParametres( + botParams.parameters.reduce( + (a, v) => + v.versions.includes(botParams.versions[0].slug) + ? { ...a, [v.key]: v.values.default } + : { ...a }, + {} + ) + ) + ) + } else { + setVersion(botParams.slug) + dispatch( + setParametres(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) + ) + } + } + } + + const setDefaultParams = () => { + if (botParams) { + dispatch(setParametres({})) + if (version !== '') { + dispatch( + setParametres( + botParams.parameters.reduce( + (a, v) => + v.versions.includes(version) ? { ...a, [v.key]: v.values.default } : { ...a }, + {} + ) + ) + ) + } else { + dispatch( + setParametres(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {})) + ) + } + } + } + + const onCreateImage = (input: string, required: (string | null)[]) => { + if (required.includes('text') && (input === '' || input === null)) { + showError('Введите сообщение!') + return false + } + if (required.includes('image') && image === null) { + showError('Прикрепите изображение!') + return false + } + if (required.includes('zip') && image === null) { + showError('Прикрепите архив!') + return false + } + let data = {} + if (version === '') { + data = { + ...includeParams, + } + } else { + data = { + version: version, + ...includeParams, + } + } + + createImage({ + content: input, + file: image, + info: { + ...data, + }, + }) + return true + } + + React.useEffect(() => { + if (isComplete) { + if (!desktop) { + const block = refScrollMobile.current + if (block) { + //@ts-ignore + block.scrollTop = block.scrollHeight + } + } else { + window.scroll(0, 0) + } + } + }, [isComplete]) + + const handleMobileScroll = () => { + setScrollBottom( + refScrollMobile.current?.scrollHeight - + refScrollMobile.current?.scrollTop - + refScrollMobile.current?.clientHeight + ) + + if (refScrollMobile.current && messages?.length !== 0) { + const { scrollTop, scrollHeight, clientHeight } = refScrollMobile.current + if (scrollTop === 0) { + if (getMessagesPagination) { + setIsPaginating(true) + getMessagesPagination(deviceType) + } + } + } + } + + const handleScroll = () => { + if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight) { + setIsPaginating(true) + getMessagesPagination(deviceType) + } + } + + React.useEffect(() => { + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + React.useEffect(() => { + const block = deviceType === 'desktop' ? window : refScrollMobile.current + if (block) { + if (messages != undefined && !isPaginating) { + setChatScrollHeight(block.scrollHeight) + const time = setTimeout(() => { + //@ts-ignore + block.scrollTo({ + top: block.scrollHeight, + behavior: 'smooth', // добавляем плавную прокрутку + }) + }, 350) + return () => clearTimeout(time) + } else if (messages != undefined && isPaginating) { + //@ts-ignore + block.scrollTop = block.scrollHeight - chatScrollHeight + + setChatScrollHeight(block.scrollHeight) + } + } + setIsPaginating(false) + }, [messages]) + + return ( + <> + <Head> + <title>{botParams ? botParams.title : 'Загрузка...'} + + + <Box + display={'flex'} + justifyContent='space-between' + alignItems='start' + flexDirection={desktop ? 'row' : 'column-reverse'} + sx={{ + marginBottom: desktop ? 0 : 3, + width: desktop ? '97%' : '100%', + marginTop: desktop ? 3 : '15px', + }} + > + <Box + sx={{ + width: desktop ? '73%' : '100%', + marginLeft: 0, + display: 'flex', + flexDirection: desktop ? 'column' : 'column-reverse', + }} + > + {desktop ? ( + <> + <Stack alignItems='center' sx={{ marginBottom: '15px' }}> + {botParams && ( + <UniqInput + currentVersion={version} + styles={'images'} + input_types={botParams.inputs} + image={image} + value={prompt} + desktop={desktop} + loading={loading} + imageLoad={onLoadImage} + sendMessage={onCreateImage} + unpinImage={() => setImage(null)} + viewMobileSettings={viewMobileSettings} + /> + )} + </Stack> + <Box className={'bg-color-block border-radius-main'} sx={{ padding: '30px' }}> + <ImageMessagesList + isComplete={isComplete} + device={deviceType} + images={messages} + /> + </Box> + </> + ) : ( + <Box className={'bg-color-block border-radius-main'}> + {scrollBottom > 500 && ( + <Box + sx={{ + position: 'absolute', + left: 0, + right: 0, + width: 'fit-content', + cursor: 'pointer', + margin: '0 auto', + bottom: '100px', + zIndex: 10, + }} + onClick={() => { + const block = refScrollMobile.current + + block.scrollTo({ + top: block.scrollHeight, + behavior: 'smooth', // добавляем плавную прокрутку + }) + }} + > + <ArrowDownScroll /> + </Box> + )} + <Box + ref={refScrollMobile} + sx={{ + padding: '30px', + height: ios + ? 'calc(200px + (400 - 200) * ((100vh - 400px) / (600 - 400)) - 100px)' + : 'calc(200px + (400 - 200) * ((100vh - 400px) / (600 - 400)))', + overflowY: 'scroll', + overflowX: 'hidden', + }} + className={'smallScroll'} + onScroll={handleMobileScroll} + > + <ImageMessagesList + isComplete={isComplete} + device={deviceType} + images={messages} + /> + </Box> + <Stack alignItems='center'> + {botParams && ( + <UniqInput + currentVersion={version} + styles={'images'} + input_types={botParams.inputs} + image={image} + value={prompt} + desktop={desktop} + loading={loading} + imageLoad={onLoadImage} + sendMessage={onCreateImage} + unpinImage={() => setImage(null)} + viewMobileSettings={viewMobileSettings} + /> + )} + </Stack> + </Box> + )} + </Box> + {desktop && ( + <Stack + spacing={2} + className='pd-30 bg-color-block border-radius-main' + sx={{ width: '25.5%', height: 'auto' }} + > + {botParams?.versions && botParams.versions.length !== 0 ? ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + }} + > + ВЕРСИИ + </Typography> + <ChatSelect + setDefaultParams={setDefaultParams} + value={version} + list={botParams.versions} + setValue={setVersion} + /> + </> + ) : ( + <></> + )} + {botParams && botParams.parameters?.length > 0 && ( + <Box + display={'flex'} + alignItems={'center'} + gap={'5px'} + sx={{ cursor: 'pointer' }} + onClick={() => { + setParams(!params) + }} + > + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + }} + > + ПАРАМЕТРЫ + </Typography> + <svg + width='14' + height='20' + viewBox='0 0 21 13' + fill='none' + xmlns='http://www.w3.org/2000/svg' + className={`${params ? 'rotate-180' : 'rotate-0'}`} + > + <path + fillRule='evenodd' + clipRule='evenodd' + d='M0.614851 0.615358C1.00866 0.221668 1.54271 0.000505666 2.09955 0.000505642C2.6564 0.000505617 3.19044 0.221668 3.58425 0.615357L10.4996 7.53066L17.4149 0.615357C17.8109 0.232825 18.3414 0.0211567 18.892 0.0259414C19.4426 0.0307261 19.9693 0.25158 20.3587 0.640937C20.748 1.03029 20.9689 1.557 20.9737 2.10761C20.9784 2.65823 20.7668 3.18869 20.3842 3.58476L11.9843 11.9848C11.5904 12.3784 11.0564 12.5996 10.4996 12.5996C9.94271 12.5996 9.40866 12.3784 9.01485 11.9848L0.614851 3.58476C0.221162 3.19095 -4.3461e-07 2.6569 -4.5895e-07 2.10006C-4.8329e-07 1.54321 0.221162 1.00917 0.614851 0.615358Z' + fill='#7f7df3' + /> + </svg> + </Box> + )} + + {botParams && botParams.parameters?.length > 0 ? ( + <Collapse in={params} orientation='vertical' collapsedSize={0} sx={{}}> + <BotParamsMap currentVersion={version} params={botParams?.parameters} /> + <ResetFilters + desktop={desktop} + closeDrawer={hideMobileSettings} + reset={resetParams} + /> + </Collapse> + ) : ( + <Typography + sx={{ + color: '#6e6e6e', + fontSize: '15px', + fontWeight: '500', + }} + > + Параметры отсутствуют + </Typography> + )} + </Stack> + )} + <DrawerCustom open={openFiltersMobile} onClose={hideMobileSettings}> + <Stack spacing={1} padding={2.4}> + {botParams?.versions && botParams.versions.length !== 0 ? ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + margin: '20px 0px 0px !important', + }} + > + ВЕРСИИ + </Typography> + <ChatSelect + setDefaultParams={setDefaultParams} + value={version} + list={botParams.versions} + setValue={setVersion} + /> + </> + ) : ( + <></> + )} + {botParams && botParams.parameters?.length > 0 ? ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + margin: '20px 0px 0px !important', + }} + > + ПАРАМЕТРЫ + </Typography> + <BotParamsMap currentVersion={version} params={botParams?.parameters} /> + <ResetFilters + closeDrawer={hideMobileSettings} + desktop={desktop} + reset={resetParams} + /> + </> + ) : ( + <Typography + sx={{ + color: '#6e6e6e', + fontSize: '15px', + fontWeight: '500', + }} + > + Параметры отсутствуют + </Typography> + )} + </Stack> + </DrawerCustom> + <Error error={error} open={Boolean(error)} /> + </Box> + </> + ) +} + +export default ImageModelPage @@ -0,0 +1,61 @@ +import * as React from 'react' +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 '#/app/store/store' +import model_api from '#/shared/api/models/api' +import { IShortModel } from '#/shared/api/models/models' +import { Card } from '#/shared/card/card' +import { NextPageWithLayout } from '#/pages/_app' + +export const ImageModelsPage: NextPageWithLayout = () => { + const [bots, setBots] = useState<IShortModel[] | null>(null) + const user = useAppSelector((state) => state.user) + const { data, status } = useSession() + + useEffect(() => { + if (!data) return + + model_api.getImages(data.access).then((res) => setBots(res)) + }, [status]) + + return ( + <> + <Typography sx={{ fontSize: 24, fontWeight: 'bold', marginTop: '25px' }}>Изображения</Typography> + <Box display='flex' flexWrap='wrap'> + {bots ? ( + bots.map((item, idx) => { + return ( + <Card + accessed_models={user.payment_plan.plan.accessed_models} + uid={item.uid} + key={idx} + slug={item.slug} + text={item.description} + title={item.title} + icon={item.image} + /> + ) + }) + ) : ( + <CircularProgress + size={50} + thickness={3} + sx={{ + color: '#7F7DF3', + position: 'absolute', + top: '40%', + left: 0, + right: 0, + margin: '0 auto', + }} + /> + )} + </Box> + </> + ) +} + +export default ImageModelsPage @@ -0,0 +1,2 @@ +export { default as ImageModelsPage } from './image-models' +export { default as ImageModelPage } from './image-model' @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -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,352 @@ +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' + +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 res = await signIn('credentials', { + username: data.email, + password: data.password, + redirect: false, + callbackUrl: '/', + }) + 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 + 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 + 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 + 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,201 @@ +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' + +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 + 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 + 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 @@ -1,15 +1,25 @@ import React from 'react' -import { Box, Input, Menu, MenuItem, Popover, TextField, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material' +import { + Box, + Input, + Menu, + MenuItem, + Popover, + TextField, + ToggleButton, + ToggleButtonGroup, + Typography, +} from '@mui/material' 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 chatsStyles from '@/src/shared/styles/chats-menu.module.scss' +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 chatsStyles from '#/shared/styles/chats-menu.module.scss' export const Chats = ({ chats, @@ -60,7 +70,10 @@ export const Chats = ({ const renameChat = () => { if (newTitle !== '' && newTitle !== 'Новый чат') { 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))) + if (res.data && chats) + setLocalChats( + chats.filter((el) => (el.uid === res.data.uid ? (el.title = res.data.title) : el)) + ) setNewTitle('') }) } @@ -69,7 +82,16 @@ export const Chats = ({ return ( <Box className={chatsStyles.wrap} sx={{ margin: desktop ? '0 0 15px 0' : '15px 0 0' }}> <Box display='flex' alignItems='center' justifyContent='space-between'> - <Typography sx={{ color: '#A4AAB5', fontWeight: '600', fontSize: '14px', letterSpacing: '0.1px' }}>ЧАТЫ</Typography> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + }} + > + ЧАТЫ + </Typography> <TooltipCustom title='Создать чат'> <Box onClick={createNewChat} @@ -185,9 +207,19 @@ export const Chats = ({ }} /> ) : ( - <Box className={el?.uid === currentChat ? chatsStyles.main_active : chatsStyles.main}> + <Box + className={ + el?.uid === currentChat + ? chatsStyles.main_active + : chatsStyles.main + } + > <Typography - className={el?.uid === currentChat ? styles2.toggle_text_active_chat : styles2.toggle_text} + className={ + el?.uid === currentChat + ? styles2.toggle_text_active_chat + : styles2.toggle_text + } > {el?.title} </Typography> @@ -195,7 +227,9 @@ export const Chats = ({ {el?.uid === currentChat && ( <Image onClick={handleClick} - aria-describedby={Boolean(chatSetting) ? 'simple-popover' : undefined} + aria-describedby={ + Boolean(chatSetting) ? 'simple-popover' : undefined + } src={'/svg/chat-setting.svg'} alt={'Настройка чата'} width={18} @@ -211,12 +245,14 @@ export const Chats = ({ onClick={handleClose} sx={{ '& .MuiMenu-list': { - backgroundColor: theme === 'dark' ? '#303035' : '#EFF0F2', + backgroundColor: + theme === 'dark' ? '#303035' : '#EFF0F2', color: '#8280FF', borderRadius: '15px', }, '& .MuiPopover-paper': { - backgroundColor: theme === 'dark' ? '#303035' : '#EFF0F2', + backgroundColor: + 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,154 @@ +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 '#/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' + +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 [isLoading, setIsLoading] = useState(true) + + const desktop = device === 'desktop' + + const [sidemenuDefaultOpen, setSidemenuDefaultOpen] = useState(true) + + useTheme() + + const { data, status } = useSession() + + const router = useRouter() + + async function getUserData(access: string) { + setIsLoading(true) + await dispatch(getUserBalance(access)) + await dispatch(getAllInfo(access)) + setIsLoading(false) + } + + React.useEffect(() => { + if (!data) return + getUserData(data.access) + }, [data]) + + React.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 (isLoading || isLoader || status === 'loading') { + return ( + <Box width='100vw' height='100vh' display='flex' alignItems='center' justifyContent='center'> + <Loader size={50} /> + </Box> + ) + } + + return ( + <> + <Head> + <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' /> - +