@@ -0,0 +1,3 @@ + + + @@ -0,0 +1,3 @@ + + + @@ -23,6 +23,7 @@ --new-ui-btn-danger-bg: #ff23721a; --new-ui-ctrl-f-button-bg: #f9f9fc; --new-ui-ctrl-f-button-border: 1px solid #c4cbd8; + --new-ui-table-cell-text: #97989f; } :root[data-theme='dark'] { @@ -50,6 +51,7 @@ --new-ui-btn-danger-bg: #ff23721a; --new-ui-ctrl-f-button-bg: #242428; --new-ui-ctrl-f-button-border: 1px solid #303035; + --new-ui-table-cell-text: #97989f; } * { @@ -171,10 +173,8 @@ p { /* Изменение цвета активного элемента в выпадающем списке */ .MuiAutocomplete-option.Mui-selected { - background-color: var(--new-ui-main-color); - /* Замените #your-selected-color на цвет активного элемента */ - color: var(--new-ui-main-color); - /* Замените #your-selected-text-color на цвет текста активного элемента */ + background-color: var(--new-ui-main-color); /* Замените #your-selected-color на цвет активного элемента */ + color: var(--new-ui-main-color); /* Замените #your-selected-text-color на цвет текста активного элемента */ } .introjs-tooltiptext { @@ -440,6 +440,26 @@ textarea { transition-duration: 250ms; } +button { + padding: 0; + border: none; + font: inherit; + color: inherit; + background-color: transparent; + text-align: left; + cursor: pointer; +} + +button { + padding: 0; + border: none; + font: inherit; + color: inherit; + background-color: transparent; + text-align: left; + cursor: pointer; +} + .my-node-enter { opacity: 0; } @@ -0,0 +1,4 @@ + + + + \ No newline at end of file @@ -0,0 +1,8 @@ + + + + \ No newline at end of file @@ -150,6 +150,7 @@ background-color: rgba(0, 0, 0, 0.7); border-radius: 15px; padding-right: 20px; + z-index: 1; span { color: white; @@ -9,7 +9,6 @@ import { useAppSelector } from '#/app/store/store' import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal } from '#/shared' import { accountApi } from '#/shared/api/account-endpoints' import { API_URL } from '#/shared/lib/constants' -import { useShowData } from '#/shared/lib/hooks' import { DateInput } from '#/shared/ui/date-input/date-input' import styles from '../invite-person-in-business/ui/invite-modal.module.scss' @@ -29,8 +28,6 @@ export const ApiKeyModal = ({ const [title, setTitle] = useState('') const theme = useAppSelector((state) => state.theme.theme) - const { error, showError } = useShowData() - const [isLoading, setIsLoading] = useState(false) const { data, status } = useSession() @@ -101,7 +98,6 @@ export const ApiKeyModal = ({ )} - ) } @@ -3,6 +3,8 @@ export type PersonInBusiness = 'business_host' | 'business_account' | 'business_ export type AcceptanceStatus = 'pending' | 'accepted' | 'rejected' | 'cancelled' export type ResponseGetPersons = { + detail?: string + uid: string email: string account_type: PersonInBusiness acceptance_status: AcceptanceStatus @@ -22,6 +24,7 @@ export type ResponseGetLogsList = { } export type ResponseGetBusinessGroups = { + detail?: string uid: string title: string token_limit: string @@ -9,10 +9,10 @@ import { ResponseGetBusinessGroups } from '#/features/business-group/model/types import { invitePerson } from '#/features/invite-person-in-business/api/invite-person' import { IEmailForms } from '#/features/register-by-email/model/types' import { useAppSelector } from '#/app/store/store' -import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '#/shared' -import { useShowData } from '#/shared/lib/hooks' +import { ButtonGray, ButtonUI, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '#/shared' import styles from './add-business-group.module.scss' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' interface InviteModalProps extends ModalProps { // showNewPersons: (persons: ResponseGetPersons) => void @@ -29,7 +29,7 @@ export const AddBusinessGroup: FC = ({ open, onClose, company_ const [limit, setLimit] = useState('0') const [title, setTitle] = useState('') const theme = useAppSelector((state) => state.theme.theme) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const { data: session } = useSession() const onSubmit = async (data: any) => { @@ -37,7 +37,7 @@ export const AddBusinessGroup: FC = ({ open, onClose, company_ const res = await addBusinessGroup(title, limit, company_uid, session?.access) if (res === null) { - showError('Что-то пошло не так') + showMessage('Что-то пошло не так') setIsLoading(false) return } @@ -52,11 +52,9 @@ export const AddBusinessGroup: FC = ({ open, onClose, company_ onClose() reset() - - // showNewPersons(res) } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const handleEmailChange = (event: any) => { @@ -111,7 +109,6 @@ export const AddBusinessGroup: FC = ({ open, onClose, company_ )} - ) } @@ -16,9 +16,9 @@ import { IEmailForms } from '#/features/register-by-email/model/types' import { useAppSelector } from '#/app/store/store' import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal, ModalProps } from '#/shared' import { API_URL } from '#/shared/lib/constants' -import { useShowData } from '#/shared/lib/hooks' import styles from './add-business-group.module.scss' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' interface InviteModalProps extends ModalProps { // showNewPersons: (persons: ResponseGetPersons) => void @@ -30,14 +30,7 @@ interface InviteModalProps extends ModalProps { current_group: ResponseGetBusinessGroups } -export const ChangeBusinessGroup: FC = ({ - open, - onClose, - company_uid, - current_group, - setChanges, - changes, -}) => { +export const ChangeBusinessGroup: FC = ({ open, onClose, company_uid, current_group, setChanges, changes }) => { const { handleSubmit, register, reset, setValue, getValues } = useForm() const [isLoading, setIsLoading] = useState(false) const [limit, setLimit] = useState('') @@ -45,7 +38,7 @@ export const ChangeBusinessGroup: FC = ({ const [accounts, setAccounts] = useState() const [addUsers, setAddUsers] = useState() const theme = useAppSelector((state) => state.theme.theme) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const { data: session } = useSession() useEffect(() => { @@ -53,7 +46,7 @@ export const ChangeBusinessGroup: FC = ({ if (res !== null) { setTitle(res.title) setLimit(res.token_limit) - setAccounts(res.accounts) + setAccounts(res.accounts as any) } }) }, [current_group, session?.access]) @@ -93,7 +86,7 @@ export const ChangeBusinessGroup: FC = ({ const res = await changeBusinessGroup(title, limit, current_group.uid, session?.access) if (res === null) { - showError('Что-то пошло не так') + showMessage('Что-то пошло не так') setIsLoading(false) return } else { @@ -113,7 +106,7 @@ export const ChangeBusinessGroup: FC = ({ reset() } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } return ( @@ -211,7 +204,6 @@ export const ChangeBusinessGroup: FC = ({ )} - ) } @@ -7,23 +7,14 @@ import { useSession } from 'next-auth/react' import { ButtonGray, ButtonUI, Error, Loader, Modal } from '#/shared' import { API_URL } from '#/shared/lib/constants' -import { useShowData } from '#/shared/lib/hooks' import { DateInput } from '#/shared/ui/date-input/date-input' import styles from './invite-modal.module.scss' -export const DownloadModal = ({ - open, - setOpen, -}: { - open: boolean - setOpen: React.Dispatch> -}) => { +export const DownloadModal = ({ open, setOpen }: { open: boolean; setOpen: React.Dispatch> }) => { const [endDate, setEndDate] = useState() const [startDate, setStartDate] = useState() - const { error, showError } = useShowData() - const [isLoading, setIsLoading] = useState(false) const { data: session } = useSession() @@ -32,9 +23,7 @@ export const DownloadModal = ({ setIsLoading(true) axios.get( API_URL + - `/auth/business-security/download-report?${startDate ? `start_date=${startDate}` : ''}${ - endDate ? `&end_date=${endDate}` : '' - }`, + `/auth/business-security/download-report?${startDate ? `start_date=${startDate}` : ''}${endDate ? `&end_date=${endDate}` : ''}`, { responseType: 'arraybuffer', headers: { Authorization: `Bearer ${session?.access}` }, @@ -76,7 +65,6 @@ export const DownloadModal = ({ )} - ) } @@ -0,0 +1,18 @@ +import axios from 'axios' + +import { API_URL } from '#/shared/lib/constants' + +export const changePassword = async (email: string, password: string, token?: string) => { + return await axios.patch( + API_URL + `/auth/business-host/account/change-pass/${email}`, + { + password_1: password, + password_2: password, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) +} @@ -0,0 +1,42 @@ +import { useSession } from 'next-auth/react' +import { changePassword } from '../api/change-password' +import { useState } from 'react' +import { getModalById, PLATE_CHANGE_PASSWORD } from '#/features/modals' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' +import { useForm } from 'react-hook-form' + +interface ChangePasswordForm { + password: string +} + +export const useChangePassword = () => { + const modal = getModalById(PLATE_CHANGE_PASSWORD) + const { + register, + formState: { errors }, + reset, + trigger, + watch, + handleSubmit, + setValue, + } = useForm() + const { showMessage } = useShowDataStore() + const { data: session } = useSession() + + const onSubmit = handleSubmit(async (data, event) => { + if (!session) return + + const respose = await changePassword(modal.getStoreProperty('email')!, data.password, session.access) + + if (respose.status == 200) { + showMessage('Пароль изменен успешно!', 'success') + modal.setState(false) + } else { + showMessage(respose.data, 'error') + } + + reset() + }) + + return { onSubmit, register, watch, errors, trigger, setValue } +} @@ -0,0 +1,4 @@ +interface ResponseChangePassword { + data: any + status: number +} @@ -1,7 +1,20 @@ -.modalContainer { - min-width: 300px; - min-height: 200px; - background: var(--background-color-additional); - border-radius: 15px; - padding: 15px; -} \ No newline at end of file +.container { + padding-bottom: 0; +} + +.modal { + min-width: 450px; + width: 100%; + + &__buttons { + display: flex; + margin-top: 45px; + gap: 10px; + } + &__header { + margin-bottom: 35px; + font-size: 30px; + font-weight: 600; + letter-spacing: -0.02em; + } +} @@ -0,0 +1,49 @@ +import { getModalById, PLATE_CHANGE_PASSWORD, PlateTemplate } from '#/features/modals' +import styles from './change-password.module.scss' +import React, { useEffect } from 'react' +import { CommonButton } from '#/shared/ui/button' +import { CommonInput } from '#/shared/ui/common-input' +import { useChangePassword } from '../lib/use-change-password' + +export const ChangePasswordPlate = () => { + const modal = getModalById(PLATE_CHANGE_PASSWORD) + const { onSubmit, register, errors, trigger, watch, setValue } = useChangePassword() + + return ( + + + Смена пароля + { + setValue('password', e.target.value) + trigger('password') + }} + label='Новый пароль' + variant='outline' + placeholder='Пароль' + error={errors.password} + // onInput={() => trigger('password')} + /> + + + Сменить пароль + + { + modal.setState(false) + }} + type='button' + > + Отмена + + + + + ) +} @@ -0,0 +1 @@ +export * from './change-password' \ No newline at end of file @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -1,4 +1,4 @@ -import { useShowData } from '#/shared' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' import { MessageSend } from '#/shared/lib/types/model' import { useState, ChangeEvent } from 'react' @@ -9,7 +9,7 @@ export function useImagesUniqInput( ) { const [image, setImage] = useState(null) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() function onLoadImage(event: ChangeEvent) { if (event.target.files) { @@ -20,15 +20,15 @@ export function useImagesUniqInput( function onCreateImage(input: string, required: (string | null)[]) { // про switch не слышали люди)) if (required.includes('text') && (input === '' || input === null)) { - showError('Введите сообщение!') + showMessage('Введите сообщение!') return false } if (required.includes('image') && image === null) { - showError('Прикрепите изображение!') + showMessage('Прикрепите изображение!') return false } if (required.includes('zip') && image === null) { - showError('Прикрепите архив!') + showMessage('Прикрепите архив!') return false } @@ -1,6 +1,5 @@ import { useAppSelector } from '#/app/store/store' import { getImagesBySlug, Message } from '#/entities/message' -import { useShowData } from '#/shared' import { Device } from '#/shared/lib/types/entities' import { getImagesGalery } from '#/widgets/messages' import { useMediaQuery } from '@mui/material' @@ -8,6 +7,7 @@ import { useSession } from 'next-auth/react' import { useEffect, useRef, useState } from 'react' import { LimitSize, Limit } from '../types' import { useRouter } from 'next/router' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export function useImageBotPagination(deviceType: Device) { const refScrollMobile = useRef(null) @@ -24,7 +24,7 @@ export function useImageBotPagination(deviceType: Device) { const [loading, setLoading] = useState(true) - const { showError } = useShowData() + const { showMessage } = useShowDataStore() const { data } = useSession() @@ -59,7 +59,7 @@ export function useImageBotPagination(deviceType: Device) { ) if (response.status >= 400 || !Array.isArray(answer)) - return showError('Ошибка загрузки чата') + return showMessage('Ошибка загрузки чата') if (deviceType === 'desktop') { setMessages((prev) => [...prev, ...answer]) @@ -10,24 +10,18 @@ export const invitePerson = async ( email: string, group?: string, token?: string -): Promise => { - try { - const { data } = await axios.post<{ email: string; token_limit: string }, AxiosResponse>( - API_URL + '/auth/business-host', - { - parent_company: group === '' ? null : group, - email, - account_privileges: Object.entries(inviteRoles).find(([key, value]) => value === role)![0], +) => { + return await axios.post<{ email: string; token_limit: string }, AxiosResponse>( + API_URL + '/auth/business-host', + { + parent_company: group === '' ? null : group, + email, + account_privileges: Object.entries(inviteRoles).find(([key, value]) => value === role)![0], + }, + { + headers: { + Authorization: `Bearer ${token}`, }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) - - return data - } catch (err) { - return null - } + } + ) } @@ -3,6 +3,8 @@ export type PersonInBusiness = 'business_host' | 'business_account' | 'business_ export type AcceptanceStatus = 'pending' | 'accepted' | 'rejected' | 'cancelled' export type ResponseGetPersons = { + detail?: string + uid: string email: string account_type: PersonInBusiness acceptance_status: AcceptanceStatus @@ -22,6 +24,7 @@ export type ResponseGetLogsList = { } export type ResponseGetBusinessGroups = { + detail?: string uid: string title: string token_limit: string @@ -20,13 +20,13 @@ import { onlyNumbersOption, Select, } 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' import styles from './invite-modal.module.scss' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' interface InviteModalProps extends ModalProps { showNewPersons: (persons: ResponseGetPersons) => void @@ -34,10 +34,10 @@ interface InviteModalProps extends ModalProps { export const InviteModal: FC = ({ open, onClose, showNewPersons }) => { const [role, setRole] = useState('Сотрудник') const { handleSubmit, register, reset, setValue, getValues } = useForm() - const { error, showError } = useShowData() const [isLoading, setIsLoading] = useState(false) const [businessGroups, setBusinessGroups] = useState() const [currentGroup, setCurrentGroup] = useState('') + const { showMessage } = useShowDataStore() const { data: session } = useSession() const onSubmit = async (data: any) => { @@ -46,20 +46,26 @@ export const InviteModal: FC = ({ open, onClose, showNewPerson const group: string[] | undefined = businessGroups?.map((el) => (el.title === currentGroup ? el.uid : '')) const res = await invitePerson(role, data.limit, data.email, group && group[0], session?.access) + if (res && res.data.detail && res.status >= 300) { + showMessage(res.data.detail) + setIsLoading(false) + return + } if (res === null) { - showError('Что-то пошло не так') + showMessage('Что-то пошло не так') setIsLoading(false) return } + setIsLoading(false) onClose() reset() - showNewPersons(res) + showNewPersons(res.data) } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const handleEmailChange = (event: any) => { @@ -85,11 +91,7 @@ export const InviteModal: FC = ({ open, onClose, showNewPerson {/* Поле с выбором роли сотрудника */} Выберите роль - setRole(e.target.value as InviteRoles)} - value={role} - > + setRole(e.target.value as InviteRoles)} value={role}> {/* Поле с выбором группы */} Выберите бизнес-группу @@ -125,7 +127,6 @@ export const InviteModal: FC = ({ open, onClose, showNewPerson )} - ) } @@ -0,0 +1 @@ +export * from './modals.config' \ No newline at end of file @@ -0,0 +1,2 @@ +export const PLATE_CHANGE_PASSWORD = 'plate-change-password' +export const RESEND_INVATION_PASSWORD = 'resend-invation-password' @@ -0,0 +1 @@ +export * from './store.modals' \ No newline at end of file @@ -0,0 +1,78 @@ +import { create } from "zustand"; + +type Set = { + (partial: (state: PlatesStore) => Partial): void; +}; + +type Get = () => PlatesStore; + +function makeModalInstance(set: Set, get: Get, key: string) { + const ModalInstance: Modal = { + id: key, + state: false, + store: {}, + setState(state, store) { + const modals = get().modals; + + store = store ?? {}; + + set(() => ({ + modals: { ...modals, [this.id]: { ...modals[key], state, store } }, + })); + }, + setStoreProperty(key, value) { + const modals = get().modals; + + this.store[key] = value; + + set(() => ({ + modals: { ...modals, [this.id]: { ...modals[key], store: this.store } }, + })); + }, + getStoreProperty(key) { + return this.store[key]; + }, + }; + + return ModalInstance; +} + +export interface Modal { + id: string; + state: boolean; + store: Record; + setState: (state: boolean, store?: Record) => void; + setStoreProperty: (key: string, value: any) => void; + getStoreProperty: (key: string) => T | undefined; +} + +export interface PlatesStore { + modals: Record; + setModal: (key: string) => void; + getModal: (key: string) => Modal; +} + +export const usePlatesStore = create((set, get) => { + const modals: Record = {}; + + function setModal(key: string) { + set((state) => ({ + modals: { + ...state.modals, + [key]: Object.assign({}, makeModalInstance(set, get, key)), + }, + })); + } + + function getModal(key: string) { + return ( + get().modals[key] ?? Object.assign({}, makeModalInstance(set, get, key)) + ); + } + + return { + modals, + setModal, + getModal, + }; +}); @@ -0,0 +1 @@ +export { default as PlateTemplate } from "./template"; \ No newline at end of file @@ -0,0 +1,105 @@ +.template { + position: fixed; + width: 100%; + max-width: calc(100dvw); + height: 100dvh; + background-color: rgba(0, 0, 0, 0.4); + top: 0; + left: 0; + transition: all 300ms ease-in-out; + display: flex; + overflow: hidden; + opacity: 0; + visibility: hidden; + + &_visible { + opacity: 1; + visibility: visible; + z-index: 9999; + } + + &_invisible { + opacity: 0; + visibility: hidden; + z-index: -1; + } + + &_align-x-left { + justify-content: flex-start; + + > * { + margin-left: 10px; + } + } + + &_align-x-center { + justify-content: center; + } + + &_align-x-right { + justify-content: flex-end; + + > * { + margin-right: 10px; + } + } + + &_align-y-top { + align-items: flex-start; + + > * { + margin-top: 10px; + } + } + + &_align-y-center { + align-items: center; + } + + &_align-y-bottom { + align-items: flex-end; + + > * { + margin-bottom: 10px; + } + } + + &__content { + background-color: var(--new-ui-main-color); + border-radius: 20px; + position: relative; + overflow: hidden; + height: fit-content; + transition: all 0.5s ease-in-out; + } + + &__header { + display: flex; + justify-content: space-between; + padding: 25px; + } + + &__close-button { + position: relative; + z-index: 1; + transform: rotate(45deg); + } + + &__content-body { + transition: all 0.5s ease-in-out; + padding: 0px 32px 32px 32px; + overflow: hidden; + + @media screen and (max-width: 1000px) { + padding: 0px 10px 10px 10px; + } + } + + &__animation { + transition: all 0.3s ease-in-out; + } + + &__animation-behavior { + transform: translateX(50px); + } +} @@ -0,0 +1,78 @@ +'use client' +import { useEffect } from 'react' +import { c } from '#/shared' +import { usePlatesStore } from '../model' +import styles from './template.module.scss' + +import PlusIcon from '#/assets/svg/plus.svg?react' +import { useThemeAndDevice } from '#/shared/lib/hooks' + +export type AlignX = 'left' | 'center' | 'right' +export type AlignY = 'top' | 'center' | 'bottom' + +export interface PlatesTemplateProps { + id: string + children: React.ReactNode + title?: string + closeModal?: () => void + hasTemplate?: boolean + hasBg?: boolean + alignX?: AlignX + alignY?: AlignY + animationClass?: string + animationBehaviorClass?: string + headerClassName?: string +} + +export default function PlatesTemplate({ + id, + title, + children, + alignX = 'center', + alignY = 'center', + hasTemplate = true, + hasBg = true, + animationClass = styles['template__animation'], + animationBehaviorClass = styles['template__animation-behavior'], + headerClassName, +}: PlatesTemplateProps) { + const { setModal, getModal } = usePlatesStore() + + const modal = getModal(id) + + const { theme } = useThemeAndDevice() + + useEffect(() => setModal(id), []) + + return ( + { + if (hasBg) modal.setState(false) + }} + > + {hasTemplate ? ( + e.stopPropagation()} + > + + {title} + modal.setState(false)}> + + + + {children} + + ) : ( + {children} + )} + + ) +} @@ -0,0 +1,9 @@ +import { usePlatesStore } from './model'; + +export function getModalById(id: string) { + const { getModal } = usePlatesStore(); + return getModal(id); +} + +export * from './ui' +export * from './config' \ No newline at end of file @@ -11,15 +11,15 @@ 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' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export const StepContact = () => { const { phone, email, name, job_title } = useAppSelector((state) => state.stepper.dataForCreate) const theme = useAppSelector((state) => state.theme.theme) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const methods = useForm({ defaultValues: { phone, @@ -31,7 +31,7 @@ export const StepContact = () => { const dispatch = useAppDispatch() const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const { data: session } = useSession() @@ -78,7 +78,6 @@ export const StepContact = () => { - ) } @@ -4,28 +4,23 @@ 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 '#/features/register-business/lib/constants-step-information' +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' import styles from './step-information.module.scss' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export const StepInformation = () => { const dispatch = useAppDispatch() const { frequency, fieldActivity, numberStuff } = useAppSelector((state) => state.stepper.dataForCreate) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const methods = useForm({ mode: 'onSubmit', @@ -41,7 +36,7 @@ export const StepInformation = () => { } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const theme = useAppSelector((state) => state.theme.theme) @@ -81,7 +76,6 @@ export const StepInformation = () => { - ) @@ -10,7 +10,6 @@ import styles from '#/features/register-business/ui/step-information/step-inform 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' @@ -39,7 +38,7 @@ export const StepLegalInformative = () => { const [rulesData, isIP] = useCheckTypeCompany(typeof selected === 'string' ? selected : selected?.value) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const onSubmit = (data: any, e: any) => { e.preventDefault() dispatch(setCompanyName(data.companyName)) @@ -54,7 +53,7 @@ export const StepLegalInformative = () => { const checkError: SubmitErrorHandler = (data, event) => { event!.preventDefault() - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } return ( @@ -104,7 +103,6 @@ export const StepLegalInformative = () => { - ) } @@ -7,7 +7,6 @@ import { useRouter } from 'next/router' 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' @@ -16,6 +15,7 @@ import { StepInformation } from '../step-information/step-information' import { StepLegalInformative } from '../step-legal-informative/step-legal-informative' import styles from './stepper.module.scss' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export const Stepper = () => { const activeStep = useAppSelector((state) => state.stepper.activeStep) @@ -23,7 +23,7 @@ export const Stepper = () => { const dispatch = useAppDispatch() - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const processCreate = useAppSelector((state) => state.stepper.loadingCreate) @@ -31,7 +31,7 @@ export const Stepper = () => { useEffect(() => { if (processCreate === 'failed') { - showError('Ошибка создания аккаунта') + showMessage('Ошибка создания аккаунта') } if (processCreate === 'succeeded') { @@ -112,7 +112,6 @@ export const Stepper = () => { )} - )} > @@ -18,10 +18,10 @@ 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' import OpenedEyeSvg from '#/assets/svg/opened-eye.svg?react' import ClosedEyeSvg from '#/assets/svg/closed-eye.svg?react' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' interface IRegisterEmailFormProps { successLogin: () => void @@ -34,7 +34,7 @@ export const RegisterEmailForm: React.FC = ({ successLo const referral = useAppSelector((state) => state.user.referral) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const { push } = useRouter() @@ -57,7 +57,7 @@ export const RegisterEmailForm: React.FC = ({ successLo setIsEmailWhite(true) } } catch (e) { - showError('Примите пользовательское соглашение', true) + showMessage('Примите пользовательское соглашение') setLoading(false) return } @@ -96,12 +96,12 @@ export const RegisterEmailForm: React.FC = ({ successLo } } catch (err: any) { setLoading(false) - showError(err.response.data.detail) + showMessage(err.response.data.detail) } } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const handleInputChangeTrim = (event: any) => { @@ -298,7 +298,6 @@ export const RegisterEmailForm: React.FC = ({ successLo - ) } @@ -1,27 +1,13 @@ -import axios, { AxiosResponse } from 'axios' - -import { ResponseGetPersons } from '#/features/invite-person-in-business' +import axios from 'axios' import { API_URL } from '#/shared/lib/constants' -export const removePerson = async (email: string, token?: string): Promise => { - if (!token) { - return null - } - - try { - const { data } = await axios.put<{ status: 'cancelled' }, AxiosResponse>( - API_URL + `/auth/business-host/accounts/${email}`, - { - status: 'cancelled', - }, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) - return data - } catch (err) { - return null - } +export const removePerson = async (person_uid?: string, token?: string) => { + return await axios.delete(API_URL + `/auth/business-host`, { + data: { + uid: person_uid, + }, + headers: { + Authorization: `Bearer ${token}`, + }, + }) } @@ -1,27 +1,31 @@ -import { Dispatch, SetStateAction, useState } from 'react' +import { Dispatch, SetStateAction, useState, useEffect } from 'react' import { useSession } from 'next-auth/react' import { ResponseGetPersons } from '#/features/invite-person-in-business' import { removePerson } from '../api/remove' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export const useRemove = ( - updateList: (persons: ResponseGetPersons) => void, - email: string -): [open: boolean, setOpen: Dispatch>, removeFn: () => void] => { + updateList: (persons: ResponseGetPersons | null) => void, + person: ResponseGetPersons | null +): [open: boolean, setOpen: Dispatch>, remove: () => void] => { const [open, setOpen] = useState(false) - + const { showMessage } = useShowDataStore() const { data } = useSession() const remove = async () => { - const result = await removePerson(email, data?.access) + const response = await removePerson(person?.uid, data?.access) - if (result === null) { + if (response.status === 200) { + showMessage('Cотрудник удален', 'success') + setOpen(false) + updateList(person) return [open, setOpen] + } else { + showMessage(response.data.detail) } - setOpen(false) - updateList(result) } return [open, setOpen, remove] -} +} \ No newline at end of file @@ -6,13 +6,13 @@ import { ButtonGray, ButtonUI, Modal } from '#/shared' import { useRemove } from '../../model/remove' export const XMark = ({ - email, + person, setNewPersons, }: { - email: string - setNewPersons: (persons: ResponseGetPersons) => void + person: ResponseGetPersons | null + setNewPersons: (persons: ResponseGetPersons | null) => void }) => { - const [open, setOpen, removeFn] = useRemove(setNewPersons, email) + const [open, setOpen, remove] = useRemove(setNewPersons, person) return ( e.stopPropagation()} sx={{ marginTop: 0.5, marginLeft: 0.8 }}> @@ -21,9 +21,9 @@ export const XMark = ({ e.stopPropagation() setOpen(true) }} - src={'/x-mark.svg'} - width={15} - height={15} + src={'/svg/trash-outline.svg'} + width={16} + height={16} alt={'Удалить'} /> - Вы дейсвительно хотите удалить пользователя {email} ? + Вы дейсвительно хотите удалить пользователя {person?.email} ? { + onClick={(e: any) => { e.stopPropagation() - await removeFn() + remove() }} text={'Удалить'} /> @@ -0,0 +1,14 @@ +import { API_URL } from '#/shared/lib/constants' +import axios from 'axios' + +export const resendInvation = async (email: string, token?: string) => { + return await axios.post( + API_URL + `/auth/business-host/re-invite/${email}`, + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) +} @@ -0,0 +1,23 @@ +import { useSession } from 'next-auth/react' +import { resendInvation } from '../api/resend-invation' +import { getModalById, RESEND_INVATION_PASSWORD } from '#/features/modals' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' + +export const useResendInvation = () => { + const modal = getModalById(RESEND_INVATION_PASSWORD) + const { showMessage } = useShowDataStore() + const { data } = useSession() + + const resend = async () => { + const response = await resendInvation(modal.getStoreProperty('email')!, data?.access) + + if (response.status == 200) { + showMessage('Приглашение переотправлено!', 'success') + modal.setState(false) + } else { + showMessage(response.data) + } + } + + return [resend] +} @@ -0,0 +1,4 @@ +interface ResponseResendInvation { + data: any, + status: number +} \ No newline at end of file @@ -0,0 +1 @@ +export * from './resend-invation' \ No newline at end of file @@ -0,0 +1,25 @@ +.container { + padding-bottom: 0; +} + +.modal { + min-width: 450px; + width: 100%; + + &__buttons { + display: flex; + margin-top: 45px; + gap: 10px; + } + &__header { + margin-bottom: 15px; + font-size: 30px; + font-weight: 600; + letter-spacing: -0.02em; + } + &__message { + font-size: 15px; + color: var(--text-color-main); + width: 70%; + } +} @@ -0,0 +1,32 @@ +import { getModalById, RESEND_INVATION_PASSWORD, PlateTemplate } from '#/features/modals' +import styles from './resend-invation.module.scss' +import React from 'react' +import { CommonButton } from '#/shared/ui/button' +import { CommonInput } from '#/shared/ui/common-input' +import { useResendInvation } from '../lib/use-resend-invation' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' + +interface ResendInvationPlateProps {} + +export const ResendInvationPlate = ({}: ResendInvationPlateProps) => { + const modal = getModalById(RESEND_INVATION_PASSWORD) + + const [resend] = useResendInvation() + + return ( + + + Переотправить приглашение + При переотправке приглашения у сотрудника будет заменен отправленный пароль. + + resend()}> + Переотправить + + modal.setState(false)}> + Отмена + + + + + ) +} @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -6,9 +6,9 @@ import { useSession } from 'next-auth/react' 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' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' type Languages = 'ru' | 'en' | 'it' | 'fr' type LanguagesText = 'Русский' | 'Английский' | 'Итальянский' | 'Французский' @@ -22,16 +22,7 @@ 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 @@ -71,7 +62,7 @@ export const useTemplate = (currentTemplate: Template | null): UseTemplate => { const dispatch = useAppDispatch() - const { showError } = useShowData() + const { showMessage } = useShowDataStore() const { data: session } = useSession() @@ -123,7 +114,7 @@ export const useTemplate = (currentTemplate: Template | null): UseTemplate => { } if (!session?.access) { - showError('У вас неактивный токен, попробуйте перезайти в аккаунт', true) + showMessage('У вас неактивный токен, попробуйте перезайти в аккаунт') return } @@ -18,6 +18,7 @@ import '#/app/styles/styles-pages/system.scss' import { NextPage } from 'next' import { pingFangFont } from '#/shared/lib/constants/font/font' import { useBlockTelegram } from '#/shared/lib/hooks/use-block-telegram' +import { Error } from '#/shared' axios.defaults.httpsAgent = new https.Agent({ rejectUnauthorized: false, @@ -67,6 +68,7 @@ function App({ Component, pageProps: { session, ...pageProps } }: AppPropsWithLa {getLayout()} + @@ -10,6 +10,7 @@ import { API_URL } from '#/shared/lib/constants' import { Device } from '#/shared/lib/types/entities' import { IMessageRequest } from '#/shared/lib/types/types-gpt' import { Message, MessageSend } from '#/entities/message' +import { Variant } from '#/shared/lib/hooks/use-show-data' const formDataHelper = (file: File, dataForSend: MessageSend): FormData => { const FD = new FormData() @@ -23,14 +24,11 @@ const formDataHelper = (file: File, dataForSend: MessageSend): FormData => export const ModelsWithChatsEndpoints = { getData: async (chatUid: string, offset: number, token?: string) => { try { - const { data } = await axios.get( - API_URL + `/chats/${chatUid}/messages/?limit=10&offset=${offset}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) + const { data } = await axios.get(API_URL + `/chats/${chatUid}/messages/?limit=10&offset=${offset}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) return data } catch (err: any) { return { @@ -54,12 +52,12 @@ export const ModelsWithChatsEndpoints = { }, } ) - } + }, } export function useModel( currentChat: string | null, - showError: (message: string, isErrorMessage?: boolean) => void, + showMessage: (message: string, variant?: Variant) => void, modelType: string, clearInput?: () => void ) { @@ -72,19 +70,19 @@ export function useModel( useEffect(() => { if (currentChat) { setMessages([]) - ; (async () => { - setLoading(true) - const answer = await ModelsWithChatsEndpoints.getData(currentChat, 0, data?.access) - setLoading(false) + ;(async () => { + setLoading(true) + const answer = await ModelsWithChatsEndpoints.getData(currentChat, 0, data?.access) + setLoading(false) - if (Array.isArray(answer)) { - setMessages(answer.reverse()) - setOffset(answer.length) - } else { - showError('Ошибка загрузки чата') - return - } - })() + if (Array.isArray(answer)) { + setMessages(answer.reverse()) + setOffset(answer.length) + } else { + showMessage('Ошибка загрузки чата') + return + } + })() } }, [currentChat]) @@ -99,7 +97,7 @@ export function useModel( setMessages([...newMessages, ...messages]) setOffset((prev) => prev + answer.length) } else { - showError('Ошибка загрузки сообщений') + showMessage('Ошибка загрузки сообщений') return } } @@ -117,11 +115,7 @@ export function useModel( info: dataForSend.info as any, is_sent: true, model: '', - file: dataForSend.file - ? ((URL.createObjectURL(dataForSend.file) + - '?type=.' + - dataForSend.file.name.split('.')[1]) as string) - : null, + file: dataForSend.file ? ((URL.createObjectURL(dataForSend.file) + '?type=.' + dataForSend.file.name.split('.')[1]) as string) : null, from_model: false, uid: 'new-send', elapsed_time: '', @@ -140,7 +134,7 @@ export function useModel( setLoading(true) // let timeout = setTimeout(() => { - // showError('Не покидайте страницу, генерация подготавливается!', false) + // showMessage('Не покидайте страницу, генерация подготавливается!', false) // }, 5000) setMessages((prev) => [...prev!, userMessage, modelMessageAboutStartGeneration]) @@ -168,10 +162,9 @@ export function useModel( if (status >= 400) { userMessage.is_sent = false setMessages((prev) => [...prev!, userMessage]) - const error = (result as { detail: string }) - if (error.detail) - return showError(error.detail) - showError("Непредвиденная ошибка, попробуйте еще раз") + const error = result as { detail: string } + if (error.detail) return showMessage(error.detail) + showMessage('Непредвиденная ошибка, попробуйте еще раз') } else { setMessages((prev) => [...prev!, ...(result as Message[])]) } @@ -213,17 +206,13 @@ export const ModelsWithImagesEndpoints = { const HeaderDataType = dataForSend instanceof FormData ? 'multipart/form-data' : 'application/json' try { - const { data } = await axios.post>( - API_URL + `/media/image/${type}`, - dataForSend, - { - withCredentials: true, - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': HeaderDataType, - }, - } - ) + const { data } = await axios.post>(API_URL + `/media/image/${type}`, dataForSend, { + withCredentials: true, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': HeaderDataType, + }, + }) return data } catch (err: any) { @@ -236,7 +225,7 @@ export const ModelsWithImagesEndpoints = { }, } -export function useModelImages(showError: (message: string) => void, type: string, device: Device) { +export function useModelImages(showMessage: (message: string) => void, type: string, device: Device) { const { data } = useSession() const { getData, sendData } = ModelsWithImagesEndpoints @@ -271,12 +260,12 @@ export function useModelImages(showError: (message: string) => void, type: st useEffect(() => { if (data?.access && type !== '' && type !== undefined) { - ; (async () => { + ;(async () => { setLoading(true) const answer = await getData(type, offset, data?.access) setLoading(false) if ('error' in answer) { - showError('Ошибка загрузки чата') + showMessage('Ошибка загрузки чата') return } @@ -315,7 +304,7 @@ export function useModelImages(showError: (message: string) => void, type: st } else { console.log('err') - showError('Ошибка загрузки сообщений') + showMessage('Ошибка загрузки сообщений') return } } @@ -338,7 +327,7 @@ export function useModelImages(showError: (message: string) => void, type: st if (result.hasOwnProperty('error')) { //@ts-ignore const message = (result.details as AxiosError).response.data.trim() ?? 'Ошибка отправки сообщения' - showError(message) + showMessage(message) return } dispatch(getUserBalance(data?.access)) @@ -380,16 +369,12 @@ export const ModelsMediaApi = { }, sendData: async (type: string | null, dataForSend: MessageSend | FormData, token?: string) => { try { - const { data } = await axios.post>( - API_URL + `/media/audio/${type}`, - dataForSend, - { - withCredentials: true, - headers: { - Authorization: `Bearer ${token}`, - }, - } - ) + const { data } = await axios.post>(API_URL + `/media/audio/${type}`, dataForSend, { + withCredentials: true, + headers: { + Authorization: `Bearer ${token}`, + }, + }) return data } catch (err: any) { @@ -402,7 +387,7 @@ export const ModelsMediaApi = { }, } -export function useMedia(showError: (message: string) => void, modelType: string) { +export function useMedia(showMessage: (message: string) => void, modelType: string) { const { data } = useSession() const [messages, setMessages] = useState(null) @@ -415,12 +400,12 @@ export function useMedia(showError: (message: string) => void, modelType: string useEffect(() => { if (data?.access) { - ; (async () => { + ;(async () => { setLoading(true) const answer = await ModelsMediaApi.getData(modelType, data?.access) setLoading(false) if ('error' in answer) { - showError('Ошибка загрузки') + showMessage('Ошибка загрузки') return } @@ -431,7 +416,7 @@ export function useMedia(showError: (message: string) => void, modelType: string const sendMessage = async (dataForSend: MessageSend) => { if (input.trim() === '') { - showError('Введите запрос!') + showMessage('Введите запрос!') return } @@ -472,7 +457,7 @@ export function useMedia(showError: (message: string) => void, modelType: string try { //@ts-ignore const message = (result.details as AxiosError).response.data.trim() ?? 'Ошибка отправки сообщения' - showError(message) + showMessage(message) return } catch (e) { return @@ -1,3 +1,2 @@ export { useAutoScroll } from './use-auto-scroll' -export { useShowData } from './use-show-data' export { useThemeAndDevice } from './use-theme-and-device' @@ -1,19 +1,45 @@ import React, { useState } from 'react' +import { create } from 'zustand' -export const useShowData = (): { - error: string - showError: (message: string, isErrorMessage?: boolean) => void - isError: boolean -} => { - const [message, setError] = React.useState('') +export type Variant = 'error' | 'success' - const [isError, setIsError] = useState(false) +interface ShowDataStore { + message: string + variant: Variant + isOpened: boolean + setOpened: (isOpened: boolean) => void + setMessage: (message: string) => void + setVariant: (variant: Variant) => void + showMessage: (message: string, variant?: Variant) => void +} - function showError(message: string = 'Произошла ошибка', isErrorMessage: boolean = false) { - setIsError(isErrorMessage) - setError(message) - setTimeout(() => setError(''), 5000) +export const useShowDataStore = create((set, get) => { + function setVariant(variant: Variant = 'error') { + set({ ...get(), variant }) } - return { error: message, showError, isError } -} + function setMessage(message: string) { + set({ ...get(), message }) + } + + function setOpened(isOpened: boolean) { + set({ ...get(), isOpened }) + } + + function showMessage(message: string = 'Произошла ошибка', variant: Variant = 'error') { + setVariant(variant) + setOpened(true) + setMessage(message) + setTimeout(() => setOpened(false), 5000) + } + + return { + message: '', + variant: 'error', + isOpened: false, + setOpened, + setVariant, + setMessage, + showMessage, + } +}) @@ -4,12 +4,11 @@ background-color: transparent; padding: 15px 25px; cursor: pointer; - box-sizing: content-box; &_outline { border-radius: 15px; - border: 0.15rem solid var(--new-ui-text-color); - color: var(--new-ui-text-color); + border: 1px solid white; + color: white; font-weight: 600; transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; font-size: 15px; @@ -45,14 +44,34 @@ &_primary { background-color: var(--air-color); - color: var(--new-ui-text-color); + color: white; border-radius: 12px; font-weight: 500; font-size: 15px; transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; &:hover { background-color: var(--text-color-purple); - color: var(--new-ui-text-color); + &:hover { + opacity: 0.7; + } + } + } + + &_gray { + background-color: var(--bg-color-button-gray); + color: var(--air-color); + border-radius: 12px; + font-weight: 500; + font-size: 15px; + transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; + &:hover { + opacity: 0.7; } } } + +body[data-theme='dark'] { + .button_gray { + color: white; + } +} @@ -1,7 +1,7 @@ import { c } from '#/shared/lib/helpers' import styles from './common-button.module.scss' -export type ButtonVariant = 'outline' | 'primary' | 'primary-outline' +export type ButtonVariant = 'outline' | 'primary' | 'primary-outline' | 'gray' export interface CommonButtonProps extends React.ButtonHTMLAttributes { children?: React.ReactNode @@ -0,0 +1,61 @@ +.box { + display: flex; + flex-direction: column; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 10px; + + &__error { + transform: translateY(16px); + font-weight: bold; + color: red; + opacity: 0; + transition: all 300ms; + + &_visible { + opacity: 1; + transform: translateY(0); + } + } +} + +.label { + color: #97989f; + font-size: 14px; + margin-bottom: 8px; +} + +.input { + background-color: transparent; + font-size: 16px; + &:focus { + border: none; + outline: none; + } + + &_outline { + color: var(--text-color-main); + padding: 20px; + border: 2px solid var(--background-color-table); + border-radius: 13px; + &::placeholder { + color: #97989f; + } + &:focus { + border: 2px solid var(--background-color-table); + } + } +} + +html[data-theme='light'] { + .input_outline { + border: 2px solid #f2f2f8; + &:focus { + border: 2px solid #f2f2f8; + } + } +} @@ -0,0 +1,44 @@ +import { c } from '#/shared/lib/helpers' +import { FieldError } from 'react-hook-form' +import styles from './common-input.module.scss' +import { useEffect } from 'react' + +export type InputVariant = 'outline' + +export interface CommonInputProps extends React.InputHTMLAttributes { + children?: React.ReactNode + variant?: InputVariant + label?: string + error?: FieldError +} + +export const CommonInput = ({ children, variant = 'outline', label, error, className, disabled, ...props }: CommonInputProps) => { + return ( + + + {label && {label}} + {error ? error?.message : ''} + + + + ) +} + +{ + /* + {label && {label}} + + {error ? error?.message : ""} + + */ +} @@ -0,0 +1 @@ +export * from './common-input' \ No newline at end of file @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -1,55 +1,59 @@ -import React, { useRef } from 'react' +import React, { useEffect, useRef } from 'react' import { Alert, Slide, Snackbar, Typography } from '@mui/material' import Image from 'next/image' import { useThemeAndDevice } from '#/shared/lib/hooks' import { CSSTransition } from 'react-transition-group' +import { useShowDataStore } from '../lib/hooks/use-show-data' +import SuccessSvg from '#/assets/svg/success.svg?react' export interface IError { - error: string - open: boolean handleClose?: () => void } const ErrorIcon = () => { const { theme } = useThemeAndDevice() - return ( - - ) + return } -export const Error: React.FC = ({ error, handleClose, open }) => { +export const Error: React.FC = ({ handleClose }) => { + const { message, variant, isOpened } = useShowDataStore() const { theme } = useThemeAndDevice() const nodeRef = useRef(null) return ( - - - } - severity='error' - sx={{ - backgroundColor: theme === 'light' ? 'white' : '#404040', - border: '1px solid #F15179', - color: theme === 'light' ? '#666666' : '#C7C7C7', - borderRadius: '12px', - }} - > - {error} - + + + {variant == 'error' ? ( + } + severity='error' + sx={{ + backgroundColor: theme === 'light' ? 'white' : '#404040', + border: '1px solid #F15179', + color: theme === 'light' ? '#666666' : '#C7C7C7', + borderRadius: '12px', + }} + > + {message} + + ) : ( + } + severity='error' + sx={{ + backgroundColor: theme === 'light' ? 'white' : '#404040', + border: '1px solid #5ef151', + color: theme === 'light' ? '#666666' : '#C7C7C7', + borderRadius: '12px', + }} + > + {message} + + )} ) @@ -1,60 +0,0 @@ -import React, { useMemo } from 'react' -import { Alert, Slide, Snackbar, Typography } from '@mui/material' -import Image from 'next/image' - -import { useThemeAndDevice } from '#/shared/lib/hooks' - -interface ISuccess { - message: string - open: boolean - handleClose?: () => void - isError?: boolean -} -export const Success: React.FC = ({ message, handleClose, open, isError = false }) => { - const { theme } = useThemeAndDevice() - - const WidgetIcon = useMemo(() => { - if (isError) { - return theme === 'dark' ? '/error2.svg' : '/error2_white.svg' - } - - return '/svg/alert/success.svg' - }, [theme, isError]) - - return ( - - - - ), - }} - severity='success' - sx={ - !isError - ? { - backgroundColor: theme === 'light' ? 'white' : '#294239', - border: '2px solid #22B47F', - color: theme === 'light' ? '#666666' : '#C7C7C7', - borderRadius: '12px', - } - : { - backgroundColor: theme === 'light' ? 'white' : '#404040', - border: '1px solid #F15179', - color: theme === 'light' ? '#666666' : '#C7C7C7', - borderRadius: '12px', - } - } - > - {message} - - - - ) -} @@ -1,6 +1,5 @@ export { emailOptions, onlyNumbersOption } from './lib/constants/hook-form-options' export { translateTypeModel } from './lib/helpers/model-helpers' -export { useShowData } from './lib/hooks' export { useAutoLoad } from './lib/hooks/use-auto-load' export { AccountMenu } from './ui/account-menu' export { Balance } from './ui/balance' @@ -25,7 +24,6 @@ export { ReferralBlock } from './ui/referral-block' export { Search } from './ui/search/search' export { SelectUI as Select } from './ui/select' export { Slider } from './ui/slider/slider' -export { Success } from './ui/success' export { SwitchCustom } from './ui/switch/switch' export { TooltipCustom } from './ui/tooltip/tooltip' export { TooltipFreeTokens } from './ui/tooltip-free-tokens' @@ -12,7 +12,7 @@ import { getAllInfo, unfollowEmail } from '#/entities/user-account/model/user-ty 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 { ButtonUI, Error, Input, Loader, Modal, SwitchCustom } from '#/shared' import { accountApi } from '#/shared/api/account-endpoints' import { API_URL } from '#/shared/lib/constants/constants' import { getDeviceType } from '#/shared/lib/helpers' @@ -24,6 +24,7 @@ import { Referral } from '#/widgets/referral' import { NextPageWithLayout } from '#/pages/_app' import { DownloadModal } from '#/features/business-security-download' import { scopes } from '../config' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' const Account: NextPageWithLayout = () => { const { @@ -61,7 +62,7 @@ const Account: NextPageWithLayout = () => { const dispatch = useAppDispatch() - const { error, showError, isError } = useShowData() + const { showMessage } = useShowDataStore() const handleFileChange = async (event: any) => { const formData = new FormData() @@ -76,11 +77,11 @@ const Account: NextPageWithLayout = () => { }, }) dispatch(getAllInfo(data?.access)) - showError('Изображение успешно загружено!') + showMessage('Изображение успешно загружено!') setLoading(false) } catch (e) { setLoading(false) - showError('Ошибка загрузки изображения на сервере!', true) + showMessage('Ошибка загрузки изображения на сервере!', 'error') } } @@ -129,13 +130,13 @@ const Account: NextPageWithLayout = () => { const changePassword = async () => { if (!data) return if (newPassword1 !== newPassword2) { - showError('Укажите одинаковые новы пароли!', true) + showMessage('Укажите одинаковые новы пароли!') return } const status = await accountApi.changePassword(data.access, newPassword1, newPassword2, currentPassword) if (status === 200) { - showError('Пароль успешно изменён!') + showMessage('Пароль успешно изменён!') setNewPassword1('') setNewPassword2('') setCurrentPassword('') @@ -143,7 +144,7 @@ const Account: NextPageWithLayout = () => { return } - showError('К сожалению, произошла ошибка', true) + showMessage('К сожалению, произошла ошибка') } function body() { @@ -155,11 +156,7 @@ const Account: NextPageWithLayout = () => { ) } if (type === 'business_account') { - return ( - - Информация о корп.аккаунте доступна только владельцу и администраторам. - - ) + return Информация о корп.аккаунте доступна только владельцу и администраторам. } if (type === 'business_host') { return @@ -191,15 +188,12 @@ const Account: NextPageWithLayout = () => { } } - const isUserDataChange = useMemo( - () => name !== first_name || last_name !== lastName || username !== userName, - [name, lastName, userName] - ) + const isUserDataChange = useMemo(() => name !== first_name || last_name !== lastName || username !== userName, [name, lastName, userName]) const promocodeActivate = async () => { - if (!data) return + if (!data) return if (!promocode.trim()) { - showError('Введите корректный промокод', true) + showMessage('Введите корректный промокод') return } let resStatus = 404 @@ -213,26 +207,26 @@ const Account: NextPageWithLayout = () => { } catch (e) {} if (resStatus === 200) { - showError('Промокод успешно активирован! Токены уже зачислены!') + showMessage('Промокод успешно активирован! Токены уже зачислены!') dispatch(getUserBalance(data?.access)) return } if (resStatus === 403) { - showError('Промокод уже был активирован!', true) + showMessage('Промокод уже был активирован!') return } if (resStatus === 404) { - showError('Промокод не найден!', true) + showMessage('Промокод не найден!') return } } async function changeUserData() { - if (!data) return + if (!data) return if (!isUserDataChange) { - showError('Вы не изменили данные', true) + showMessage('Вы не изменили данные', 'success') return } @@ -248,13 +242,13 @@ const Account: NextPageWithLayout = () => { { headers: { Authorization: `Bearer ${data.access}` } } ) - showError('Данные успешно изменены!') + showMessage('Данные успешно изменены!') dispatch(getAllInfo(data.access)) } catch (e) {} } const deleteAccount = async () => { - if (!data) return + if (!data) return try { const { status } = await axios.delete(API_URL + '/auth/remove', { headers: { @@ -318,11 +312,7 @@ const Account: NextPageWithLayout = () => { disableRipple onClick={() => changeScope(el.scope)} key={el.title} - className={ - scope === el.scope - ? styles2.wrap_toggle_button_active - : styles2.wrap_toggle_button - } + className={scope === el.scope ? styles2.wrap_toggle_button_active : styles2.wrap_toggle_button} value={el.scope} label={el.title} /> @@ -358,12 +348,7 @@ const Account: NextPageWithLayout = () => { }} > {!loading || !(userInfoLoaded === 'succeeded') ? ( - + ) : ( )} @@ -400,25 +385,15 @@ const Account: NextPageWithLayout = () => { /> - - - Email - - + + Email + { - if (!data) return + if (!data) return await dispatch(unfollowEmail(data.access)) - showError('Данные изменены!') + showMessage('Данные изменены!') }} /> { fullWidth /> - + {/*referral_code.code.trim() && ( Ваша реферальная ссылка: @@ -483,9 +454,7 @@ const Account: NextPageWithLayout = () => { Изменить пароль - - Текущий пароль - + Текущий пароль { }} > - - Новый пароль - + Новый пароль { /> - - Подтвердить пароль - + Подтвердить пароль { )} Удаление аккаунта - - Удаление аккаунта приведет к потере всех настроек - + Удаление аккаунта приведет к потере всех настроек setConfirmDeleteModal(true)} @@ -554,15 +517,8 @@ const Account: NextPageWithLayout = () => { setConfirmDeleteModal(false)}> Удаление аккаунта - - Вы действительно хотите удалить ваш аккаунт? - - + Вы действительно хотите удалить ваш аккаунт? + Удалить @@ -591,7 +547,6 @@ const Account: NextPageWithLayout = () => { ) : ( <>> )} - @@ -1,5 +1,14 @@ import React, { useEffect, useState } from 'react' -import { Box, Stack, Table, TableBody, TableHead, TableRow, TextField, Typography } from '@mui/material' +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' @@ -78,9 +87,9 @@ const ApiKeys: NextPageWithLayout = () => { - API-ключ — это инструмент, который идентифицирует пользователя или программу, - запрашивающих доступ к API платформы. С помощью ключа можно отслеживать, кто и когда - пользуется API, рассчитывать оплату. + API-ключ — это инструмент, который идентифицирует пользователя или + программу, запрашивающих доступ к API платформы. С помощью ключа можно + отслеживать, кто и когда пользуется API, рассчитывать оплату. { {keys && keys.length !== 0 ? ( Мои ключи - + { ) : ( - + - У вас пока нет ключей 😞 - Создайте первый ключ + + У вас пока нет ключей 😞{' '} + + + Создайте первый ключ + )} @@ -172,7 +194,7 @@ const KeyRow = (props: any) => { if (limit !== props.limit) { axios.patch( API_URL + '/public/api-key', - { token_limit: Number(limit), name: props.name }, + { token_limit: limit == '' ? null : Number(limit), name: props.name }, { headers: { Authorization: `Bearer ${session?.access}` } } ) } @@ -188,13 +210,22 @@ const KeyRow = (props: any) => { {props.keyValue} - + setLimit(e.target.value)} - sx={theme === 'light' ? { ...InputStyleSmallLight } : { ...InputStyleSmallDark }} + sx={ + theme === 'light' + ? { ...InputStyleSmallLight } + : { ...InputStyleSmallDark } + } /> @@ -13,7 +13,7 @@ import { ChatSelect } from '#/app/components/chat_select' import { ResetFilters } from '#/app/components/filters/reset_filters' import { setParams as setParametres } from '#/app/store/model-parametres-store' import { useAppSelector } from '#/app/store/store' -import { DrawerCustom, Error, useModel, useShowData } from '#/shared' +import { DrawerCustom, Error, useModel } from '#/shared' import model_api from '#/shared/api/models/api' import styles from './chats-bot.module.scss' import { Chats } from '#/widgets/chats' @@ -28,6 +28,7 @@ import { IModelTag, IModel } from '#/entities/model-entity' import { useThemeAndDevice } from '#/shared/lib/hooks' import { SvgIcon } from '#/shared/ui/svg' import { AllChatWindow } from '#/widgets/chat-window' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export interface ChatBotPageProps {} @@ -40,7 +41,7 @@ const Page: NextPageWithLayout = () => { const [params, setParams] = React.useState(false) const [file, setFile] = React.useState(null) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const deviceType = getDeviceType() const deviceOs = getOs() @@ -62,11 +63,7 @@ const Page: NextPageWithLayout = () => { setIsTryRename, } = useChats(modelType) - const { messages, sendMessage, loading, getMessagesPagination, deleteMessage } = useModel( - currentChat, - showError, - modelType - ) + const { messages, sendMessage, loading, getMessagesPagination, deleteMessage } = useModel(currentChat, showMessage, modelType) const includeParams = useAppSelector((state) => state.params.params) const dispatch = useDispatch() @@ -86,24 +83,14 @@ const Page: NextPageWithLayout = () => { dispatch( setParametres( res.parameters.reduce( - (a, v) => - v.versions.includes(res.versions[0].slug) - ? { ...a, [v.key]: v.values.default } - : { ...a }, + (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 }), - {} - ) - ) - ) + dispatch(setParametres(res.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {}))) } }) } @@ -117,24 +104,14 @@ const Page: NextPageWithLayout = () => { dispatch( setParametres( botParams.parameters.reduce( - (a, v) => - v.versions.includes(botParams.versions[0].slug) - ? { ...a, [v.key]: v.values.default } - : { ...a }, + (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 }), - {} - ) - ) - ) + dispatch(setParametres(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {}))) } } } @@ -146,23 +123,13 @@ const Page: NextPageWithLayout = () => { dispatch( setParametres( botParams.parameters.reduce( - (a, v) => - v.versions.includes(version) - ? { ...a, [v.key]: v.values.default } - : { ...a }, + (a, v) => (v.versions.includes(version) ? { ...a, [v.key]: v.values.default } : { ...a }), {} ) ) ) } else { - dispatch( - setParametres( - botParams.parameters.reduce( - (a, v) => ({ ...a, [v.key]: v.values.default }), - {} - ) - ) - ) + dispatch(setParametres(botParams.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {}))) } } } @@ -177,11 +144,11 @@ const Page: NextPageWithLayout = () => { const onSendMessage = (input: string, required: (string | null)[]) => { if (required.includes('text') && (input === '' || input === null)) { - showError('Введите сообщение!') + showMessage('Введите сообщение!') return false } if (required.includes('image') && file === null) { - showError('Прикрепите изображение!') + showMessage('Прикрепите изображение!') return false } @@ -223,16 +190,9 @@ const Page: NextPageWithLayout = () => { {botParams && botParams.tags.map((tag, index) => ( - + - - {tag.title} - + {tag.title} ))} @@ -285,31 +245,27 @@ const Page: NextPageWithLayout = () => { handleClickChatSetting={handleClickChatSetting} /> {desktop && ( - - {botParams?.versions && - botParams.versions?.length !== 0 && ( - <> - - ВЕРСИИ - - - > - )} + + {botParams?.versions && botParams.versions?.length !== 0 && ( + <> + + ВЕРСИИ + + + > + )} {botParams && botParams.parameters?.length > 0 && ( = () => { viewBox='0 0 21 13' fill='none' xmlns='http://www.w3.org/2000/svg' - className={`${ - params ? 'rotate-180' : 'rotate-0' - }`} + className={`${params ? 'rotate-180' : 'rotate-0'}`} > = () => { )} {botParams && botParams.parameters?.length > 0 ? ( - - - + + + ) : ( = () => { )} {!desktop && ( - + - {botParams?.versions && - botParams.versions?.length !== 0 && ( - <> - - ВЕРСИИ - - - > - )} + {botParams?.versions && botParams.versions?.length !== 0 && ( + <> + + ВЕРСИИ + + + > + )} {botParams && botParams.parameters?.length > 0 ? ( <> = () => { > ПАРАМЕТРЫ - - + + > ) : ( = () => { )} - > @@ -1,5 +1,5 @@ import { useEffect, useMemo } from 'react' -import { Box, Collapse, Stack,Typography } from '@mui/material' +import { Box, Collapse, Stack, Typography } from '@mui/material' import Head from 'next/head' import { useRouter } from 'next/router' import { useSession } from 'next-auth/react' @@ -19,67 +19,40 @@ import { useImagesUniqInput } from '#/features/image-bot-input' import { useImageBotPagination } from '#/features/image-bot-pagination' import Title from '#/features/title/title' import { NextPageWithLayout } from '#/pages/_app' -import { DrawerCustom, Error, Loader,useShowData } from '#/shared' +import { DrawerCustom, Error, Loader } from '#/shared' import { c, getDeviceType, getOs } from '#/shared/lib/helpers' import { useThemeAndDevice } from '#/shared/lib/hooks' import { Device, DeviceOs } from '#/shared/lib/types/entities' import { ArrowDownScroll } from '#/shared/ui/icon-components/scroll-down-arrow' import { SvgIcon } from '#/shared/ui/svg' -import { ImageMessagesList,useImagesPagination } from '#/widgets/messages' +import { ImageMessagesList, useImagesPagination } from '#/widgets/messages' import { ModelInput } from '#/features/model-input' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' const ImageModelPage: NextPageWithLayout = () => { const { query } = useRouter() - const { - botParams, - version, - modelType, - fetchBotParams, - resetParams, - setDefaultParams, - setVersion, - } = useImageBot(query.slug as string) + const { botParams, version, modelType, fetchBotParams, resetParams, setDefaultParams, setVersion } = useImageBot(query.slug as string) const deviceType = getDeviceType() const deviceOs = getOs() const { ios, desktop } = useThemeAndDevice(deviceType, deviceOs) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const router = useRouter() const { data: session } = useSession() - const { openFiltersMobile, setOpenFiltersMobile, params, setParams, includeParams } = - useImagesBotFilters() + const { openFiltersMobile, setOpenFiltersMobile, params, setParams, includeParams } = useImagesBotFilters() - const { - refScrollMobile, - refScrollDesktop, - mobileScrollContainer, - onObserverMounted, - setMessages, - fetchMessages, - loading, - offset, - messages, - } = useImageBotPagination(deviceType) + const { refScrollMobile, refScrollDesktop, mobileScrollContainer, onObserverMounted, setMessages, fetchMessages, loading, offset, messages } = + useImageBotPagination(deviceType) - const { createImage, isComplete, createLoading } = useImageBotCreateImage( - showError, - modelType, - deviceType, - setMessages, - mobileScrollContainer - ) + const { createImage, isComplete, createLoading } = useImageBotCreateImage(showMessage, modelType, deviceType, setMessages, mobileScrollContainer) - const { onCreateImage, onLoadImage, image, setImage } = useImagesUniqInput( - version, - includeParams, - createImage - ) + const { onCreateImage, onLoadImage, image, setImage } = useImagesUniqInput(version, includeParams, createImage) async function onFetch() { await Promise.all([fetchBotParams()]) @@ -108,16 +81,9 @@ const ImageModelPage: NextPageWithLayout = () => { {botParams && botParams.tags.map((tag, index) => ( - + - - {tag.title} - + {tag.title} ))} @@ -166,21 +132,13 @@ const ImageModelPage: NextPageWithLayout = () => { imageLoad={onLoadImage} sendMessage={onCreateImage} unpinImage={() => setImage(null)} - viewMobileSettings={() => - setOpenFiltersMobile(true) - } + viewMobileSettings={() => setOpenFiltersMobile(true)} /> )} {botParams?.blocked && ( - - - Модель недоступна - + + Модель недоступна )} @@ -210,18 +168,8 @@ const ImageModelPage: NextPageWithLayout = () => { > ) : ( - - + + { { ref={mobileScrollContainer} className={'smallScroll'} > - + { getMessagesPagination={fetchMessages} /> - + {botParams && ( { imageLoad={onLoadImage} sendMessage={onCreateImage} unpinImage={() => setImage(null)} - viewMobileSettings={() => - setOpenFiltersMobile(true) - } + viewMobileSettings={() => setOpenFiltersMobile(true)} /> )} {botParams?.blocked && ( - - - Модель недоступна - + + Модель недоступна )} @@ -297,11 +229,7 @@ const ImageModelPage: NextPageWithLayout = () => { )} {desktop && ( - + {botParams?.versions && botParams.versions.length !== 0 ? ( <> { )} {botParams && botParams.parameters?.length > 0 ? ( - - - setOpenFiltersMobile(false)} - reset={resetParams} - /> + + + setOpenFiltersMobile(false)} reset={resetParams} /> ) : ( { )} )} - setOpenFiltersMobile(false)} - > + setOpenFiltersMobile(false)}> {botParams?.versions && botParams.versions.length !== 0 ? ( <> @@ -433,15 +346,8 @@ const ImageModelPage: NextPageWithLayout = () => { > ПАРАМЕТРЫ - - setOpenFiltersMobile(false)} - desktop={desktop} - reset={resetParams} - /> + + setOpenFiltersMobile(false)} desktop={desktop} reset={resetParams} /> > ) : ( { )} - > ) @@ -14,7 +14,6 @@ import { IEmailForms } from '#/features/register-by-email/model/types' import { useAppSelector } from '#/app/store/store' import { emailOptions, Error } from '#/shared' import { getDeviceType } from '#/shared/lib/helpers' -import { useShowData } from '#/shared/lib/hooks' import { InputStyleDark, InputStyleLight } from '#/shared/ui/input' import { NextPageWithLayout } from '#/pages/_app' import styles from './login.module.scss' @@ -23,6 +22,7 @@ import OpenedEyeSvg from '#/assets/svg/opened-eye.svg?react' import ClosedEyeSvg from '#/assets/svg/closed-eye.svg?react' import * as Sentry from '@sentry/nextjs' import { useEffect } from 'react' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' const Login: NextPageWithLayout = () => { const device = getDeviceType() @@ -35,7 +35,7 @@ const Login: NextPageWithLayout = () => { Object.entries(query).forEach(([key, value]) => setCookie(key, value)) }, []) - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const [loading, setLoading] = React.useState(false) @@ -53,7 +53,7 @@ const Login: NextPageWithLayout = () => { if (!error || error === '') return const expectedError = ERRROR_YANDEX_TRANSLATE_MAPPING[error] - showError(expectedError ?? 'Непредвиденная ошибка') + showMessage(expectedError ?? 'Непредвиденная ошибка') if (!expectedError) Sentry.captureMessage(error) }, []) @@ -77,8 +77,9 @@ const Login: NextPageWithLayout = () => { (prev, curr) => ({ ...prev, [ERROR_MAPPING[curr]]: curr }), {} as Record )[resp.error] - showError(expectedError ?? 'Непредвиденная ошибка') - if (!expectedError) Sentry.captureMessage(resp.error) + showMessage(expectedError ?? 'Непредвиденная ошибка') + if (!expectedError) + Sentry.captureMessage(resp.error) setLoading(false) } else { push('/') @@ -86,7 +87,7 @@ const Login: NextPageWithLayout = () => { } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const handleKeyDown = (event: any) => { @@ -376,7 +377,6 @@ const Login: NextPageWithLayout = () => { - @@ -11,13 +11,14 @@ 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' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' const Reset: NextPageWithLayout = () => { const device = getDeviceType() const [input_email, set_Email] = useState('') const [isSend, setIsSend] = useState(false) - const [isError, setIsError] = React.useState(false) + const {showMessage, } = useShowDataStore() const sendMail = () => { axios.post(API_URL + '/auth/update-pass', { email: input_email, @@ -29,8 +30,7 @@ const Reset: NextPageWithLayout = () => { } }) .catch(function (error) { - setIsError(true) - setTimeout(() => setIsError(false), 4000) + showMessage('Аккаунт с такой почтой не найден') }) } @@ -141,7 +141,6 @@ const Reset: NextPageWithLayout = () => { Восстановить аккаунт - ) : ( @@ -9,7 +9,7 @@ import { getAll, ResponseAllInfo } from '#/entities/user-account/model/user-type 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 { Error, 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' @@ -23,6 +23,7 @@ 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' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' type ListType = 'personal' | 'security' @@ -35,6 +36,7 @@ export default function BusinessHost() { const [mailing, setMailing] = useState() const [toDate, setToDate] = useState('') const { data } = useSession() + const { message, isOpened } = useShowDataStore() useEffect(() => { getPersons(data?.access, true).then((res) => setSecurityList(res ? res?.reverse() : null)) @@ -63,13 +65,10 @@ 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', }) @@ -83,11 +82,7 @@ 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 ( @@ -102,11 +97,7 @@ export default function BusinessHost() { Затраты - setShowPersons((prev) => !prev)} - className={styles2.arrow} - /> + setShowPersons((prev) => !prev)} className={styles2.arrow} /> {showPersons && ( @@ -150,20 +141,12 @@ const MailingBlock = ({ info, mailing }: { info: InfoBusiness | null | undefined const addMailingUser = async () => { if (newMail !== '') { - if (allMails && allMails?.length !== 0) { - setAllMails([...allMails, newMail]) - } else { - setAllMails([newMail]) - } + setAllMails((emails) => [...(emails ?? []), newMail]) } } 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 ( @@ -190,11 +173,7 @@ 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 }} /> @@ -10,3 +10,23 @@ export const translateEmailStatus = (status: AcceptanceStatus) => { return 'Приглашен' } } + +export const formatDateStatus = (dateString: string) => { + if (!dateString) return '' + + const date = new Date(dateString) + + const day = date.getDate().toString().padStart(2, '0') + const month = (date.getMonth() + 1).toString().padStart(2, '0') + const year = date.getFullYear().toString().slice(-2) + + return `${day}.${month}.${year}` +} + +export const formatEmail = (email: string | undefined) => { + if (email !== undefined && email.length > 24) { + return email.slice(0, 22) + '...' + } else { + return email + } +} @@ -11,7 +11,7 @@ import { useSession } from 'next-auth/react' 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 { Search } 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' @@ -1,6 +1,138 @@ -.modalWrap { - text-align: center; - @media (max-width: 800px) { - width: 85vw; +.wrap { + margin-top: 40px; + font-size: 16px; + .title { + display: flex; + align-items: center; + .count { + margin-left: 5px; + font-weight: 600; + } + p { + color: var(--text-color-main); + font-size: 20px; + font-weight: 500; + } + .arrow { + margin-top: 3px; + cursor: pointer; + color: #9b9b9b; + } } + .listWrap { + margin-top: 15px; + border-radius: 15px; + padding: 20px; + background-color: var(--new-ui-main-color); + border: 1px solid var(--border-color2); + .search { + margin: 5px 15px; + } + } + .table { + background-color: inherit; + box-shadow: none; + color: var(--new-ui-table-cell-text); + .tableEmail { + color: var(--text-color-main); + font-weight: 600; + } + .change { + color: var(--air-color); + max-width: 100px; + button:hover { + text-decoration: underline; + } + } + .tableLimit { + cursor: pointer; + + color: var(--text-color-purple); + .change { + text-decoration: none; + margin-left: 5px; + } + &:hover { + text-decoration: underline; + } + } + td { + color: var(--new-ui-table-cell-text); + border-color: var(--border-color2); + vertical-align: top; + padding: 16px 12px; + } + th { + color: var(--new-ui-table-cell-text); + font-weight: 400; + border-bottom: none; + } + .tableSwitch { + div { + display: flex; + flex-direction: column; + justify-content: center; + max-width: 50px; + margin: auto; + } + } + } + + .title2 { + display: flex; + justify-content: space-between; + align-items: flex-end; + p { + color: var(--text-color-purple); + font-size: 16px; + cursor: pointer; + } + div { + @extend .title; + margin-top: 15px; + @media (max-width: 1000px) { + margin-top: 0px; + } + } + } + .zero_person { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + p { + margin-top: 40px; + margin-bottom: 20px; + font-size: 17px; + color: var(--text-color-additional-two); + } + } + .titleModalLimit:global { + border: 1px solid red; + margin-bottom: 10px; + } +} + +.logsList { + margin-top: 15px; + display: flex; + justify-content: flex-start; + align-items: flex-end; + flex-wrap: wrap; + gap: 15px; +} +.hint { + margin-top: 15px; + font-size: 15px !important; + font-weight: 500 !important; + margin-bottom: 5px; +} + +.refreshStatusButton { + font-size: 14px; + display: flex; + gap: 1.5px; + align-items: center; + color: var(--air-color); + cursor: pointer; } @@ -8,18 +8,22 @@ import TableContainer from '@mui/material/TableContainer' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import { useSession } from 'next-auth/react' +import Image from 'next/image' 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 { Error, formatDate, Search } from '#/shared' +import styles from './persons-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' +import { translateEmailStatus, formatDateStatus, formatEmail } from '../../lib/lib' +import { getModalById, PLATE_CHANGE_PASSWORD, RESEND_INVATION_PASSWORD } from '#/features/modals' +import { ResponseGetBusinessGroups } from '#/features/invite-person-in-business/model/types' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export const PersonsList = ({ personsList, @@ -34,7 +38,10 @@ export const PersonsList = ({ const [searchPersons, setSearchPersons] = useState(persons) const [inviteModal, setInviteModal] = useState(false) const [currentPerson, setCurrentPerson] = useState(null) - // const { data } = useSession() + + const passChangeModal = getModalById(PLATE_CHANGE_PASSWORD) + const passResendModal = getModalById(RESEND_INVATION_PASSWORD) + const { data: session } = useSession() useEffect(() => { setPersons(personsList) @@ -49,14 +56,14 @@ export const PersonsList = ({ return [data] }) - showError('Пользователь успешно приглашен!') + showMessage('Пользователь успешно приглашен!') } const [search, setSearch] = useState('') const [showPersons, setShowPersons] = useState(true) - const { error: message, showError, isError } = useShowData() + const { showMessage } = useShowDataStore() const updateLimit = (limit: string, email?: string) => { setPersons((prev) => @@ -70,7 +77,7 @@ export const PersonsList = ({ }) ) setCurrentPerson(null) - showError(`Лимит пользователя ${email} успешно изменён!`) + showMessage(`Лимит пользователя ${email} успешно изменён!`) } useEffect(() => { @@ -99,11 +106,7 @@ export const PersonsList = ({ Сотрудники {persons?.length !== 0 && {persons?.length}} - setShowPersons((prev) => !prev)} - className={styles.arrow} - /> + setShowPersons((prev) => !prev)} className={styles.arrow} /> setInviteModal(true)}>Добавить сотрудника @@ -128,15 +131,17 @@ export const PersonsList = ({ Пользователь - Роль - Статус - Лимит токенов - - + Роль + Статус + Лимит токенов + + + - {searchPersons?.map((person) => { + {searchPersons?.map((person: ResponseGetPersons) => { + const statusPerson = translateEmailStatus(person.acceptance_status) return ( - {person.email} - - - {RoleSelect[person.account_type]} + {formatEmail(person.email)} - - {translateEmailStatus(person.acceptance_status)} + {RoleSelect[person.account_type]} + + + {statusPerson + ' '} + {statusPerson === 'Приглашен' ? formatDateStatus(person.created_at) : null} + + {statusPerson === 'Приглашен' ? ( + { + passResendModal.setState(true, { + email: person.email, + }) + }} + > + + Отправить повторно + + ) : null} - - {Math.floor(+person.token_limit)} + {Math.floor(+person.token_limit)} + + { + passChangeModal.setState(true, { + email: person.email, + }) + }} + > + Сменить пароль + - setCurrentPerson( - persons?.find( - (el) => el.email === person.email - ) || null - ) + setCurrentPerson(persons?.find((el) => el.email === person.email) || null) } className={styles.tableLimit} align='center' > Изменить - + + setNewPersons={(person: ResponseGetPersons | null) => setPersons( (prev) => - prev?.filter( - (el) => - el.email !== - persons.email - ) || null + prev?.filter((el) => el.email !== person?.email) || null ) } - email={person.email} + person={person || null} /> @@ -201,7 +220,6 @@ export const PersonsList = ({ )} )} - setInviteModal(false)} showNewPersons={showNewPerson} /> {currentPerson && ( (persons) const [inviteModal, setInviteModal] = useState(false) const [currentPerson, setCurrentPerson] = useState(null) - // const { data } = useSession() + + const passChangeModal = getModalById(PLATE_CHANGE_PASSWORD) + const passResendModal = getModalById(RESEND_INVATION_PASSWORD) useEffect(() => { setPersons(securityList) @@ -71,14 +77,14 @@ export const SecurityList = ({ return [data] }) - showError('Пользователь успешно приглашен!') + showMessage('Пользователь успешно приглашен!') } const [search, setSearch] = useState('') const [showPersons, setShowPersons] = useState(true) - const { error: message, showError, isError } = useShowData() + const { showMessage } = useShowDataStore() const { data } = useSession() const updateLimit = (limit: string, email?: string) => { @@ -93,7 +99,7 @@ export const SecurityList = ({ }) ) setCurrentPerson(null) - showError(`Лимит пользователя ${email} успешно изменён!`) + showMessage(`Лимит пользователя ${email} успешно изменён!`) } useEffect(() => { @@ -121,11 +127,7 @@ export const SecurityList = ({ Сотрудники безопасности {persons?.length !== 0 && {persons?.length}} - setShowPersons((prev) => !prev)} - className={styles.arrow} - /> + setShowPersons((prev) => !prev)} className={styles.arrow} /> setInviteModal(true)}>Добавить сотрудника @@ -150,15 +152,17 @@ export const SecurityList = ({ Пользователь - Роль - Статус - Лимит токенов - - + Роль + Статус + Лимит токенов + + + - {searchPersons?.map((person) => { + {searchPersons?.map((person: ResponseGetPersons) => { + const statusPerson = translateEmailStatus(person.acceptance_status) return ( - {person.email} - - - {RoleSelect[person.account_type]} + {formatEmail(person.email)} - - {translateEmailStatus(person.acceptance_status)} + {RoleSelect[person.account_type]} + + + {statusPerson + ' '} + {statusPerson === 'Приглашен' ? formatDateStatus(person.created_at) : null} + + {statusPerson === 'Приглашен' ? ( + { + passResendModal.setState(true, { + callback: () => { + console.log('Смена пароля') + }, + }) + }} + > + + Отправить повторно + + ) : null} - - {Math.floor(+person.token_limit)} + {Math.floor(+person.token_limit)} + + { + passChangeModal.setState(true, { + callback: () => { + console.log('Смена пароля') + }, + }) + }} + > + Сменить пароль + - setCurrentPerson( - persons?.find( - (el) => el.email === person.email - ) || null - ) + setCurrentPerson(persons?.find((el) => el.email === person.email) || null) } className={styles.tableLimit} align='center' > Изменить - - { - axios.put< - { - token_limit: string - role: string - }, - AxiosResponse - >( - 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 - ) - ) - addList(res.data, 'personal') - }) - }} - src={'/x-mark.svg'} - width={15} - height={15} - alt={'Удалить'} + + + setPersons( + (prev) => + prev?.filter((el) => el.email !== persons?.email) || null + ) + } + person={person} /> @@ -246,12 +245,8 @@ export const SecurityList = ({ )} )} - - setInviteModal(false)} - showNewPersons={showNewPerson} - /> + + setInviteModal(false)} showNewPersons={showNewPerson} /> {currentPerson && ( = ({ open, onClose, showNewPersons }) => { const [role, setRole] = useState('Сотрудник безопасности') const { handleSubmit, register, reset, setValue, getValues } = useForm() - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const [isLoading, setIsLoading] = useState(false) const [businessGroups, setBusinessGroups] = useState() const [currentGroup, setCurrentGroup] = useState('') @@ -286,7 +281,7 @@ export const InviteSecurityModal: FC = ({ open, onClose, showN const res = await invitePerson(role, data.limit, data.email, group && group[0], session?.access) if (res === null) { - showError('Что-то пошло не так') + showMessage('Что-то пошло не так') setIsLoading(false) return } @@ -295,10 +290,10 @@ export const InviteSecurityModal: FC = ({ open, onClose, showN reset() - showNewPersons(res) + showNewPersons(res.data) } const checkError: SubmitErrorHandler = (data) => { - showError(Object.values(data)[0].message || 'Неверные данные') + showMessage(Object.values(data)[0].message || 'Неверные данные') } const handleEmailChange = (event: any) => { @@ -324,11 +319,7 @@ export const InviteSecurityModal: FC = ({ open, onClose, showN {/* Поле с выбором роли сотрудника */} Выберите роль - setRole(e.target.value as InviteRoles)} - value={role} - > + setRole(e.target.value as InviteRoles)} value={role}> {/* Поле с выбором группы */} Выберите бизнес-группу @@ -364,7 +355,6 @@ export const InviteSecurityModal: FC = ({ open, onClose, showN )} - ) } @@ -21,16 +21,12 @@ import { SideMenu } from '#/widgets/side-menu' import { getDeviceType } from '#/shared/lib/helpers' import { LayoutProps } from '../types' import { change } from '#/features/pending' +import { ChangePasswordPlate } from '#/features/change-password-corp' +import { ResendInvationPlate } from '#/features/resend-invation-corp ' const freeRoutes = ['/login', '/register', '/reset', '/change-password'] -export const Layout: React.FC = ({ - children, - titlePage, - title = titlePage, - isLoader, - device = getDeviceType(), -}) => { +export const Layout: React.FC = ({ children, titlePage, title = titlePage, isLoader, device = getDeviceType() }) => { const { data: sessionData } = useSession() const appState = useAppSelector((state) => state) const dispatch = useAppDispatch() @@ -68,10 +64,7 @@ export const Layout: React.FC = ({ }, [data]) useEffect(() => { - if ( - (sessionData && !sessionStorage.getItem('firstRender')) || - (sessionData && !localStorage.getItem('global_settings')) - ) { + if ((sessionData && !sessionStorage.getItem('firstRender')) || (sessionData && !localStorage.getItem('global_settings'))) { dispatch(getUserAccountSettings(sessionData.access)) sessionStorage.setItem('firstRender', 'true') } @@ -96,22 +89,13 @@ export const Layout: React.FC = ({ targetDevice: device, targetType: 'sidemenu', }) - if (setting) - setting?.value?.sidemenu_state === 'opened' - ? setSidemenuDefaultOpen(true) - : setSidemenuDefaultOpen(false) + if (setting) setting?.value?.sidemenu_state === 'opened' ? setSidemenuDefaultOpen(true) : setSidemenuDefaultOpen(false) } }, [appState.settings.state]) if (userLoading || isLoader || status === 'loading') { return ( - + ) @@ -143,10 +127,7 @@ export const Layout: React.FC = ({ > {desktop ? ( - + = ({ )} + + + > ) @@ -1,9 +1,9 @@ -import { useShowData } from '#/shared' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' import { useState } from 'react' export function useImageIcons() { const [iconsMenu, setIconsMenu] = useState('') - const { error, showError, isError } = useShowData() + const { showMessage } = useShowDataStore() const toggleMenu = (uid: string) => { if (iconsMenu === uid) { @@ -28,10 +28,10 @@ export function useImageIcons() { link.click() }) .catch((error) => { - showError('Что-то пошло не так', true) + showMessage('Что-то пошло не так') }) } else { - showError('Изображение не найдено', true) + showMessage('Изображение не найдено') } } @@ -1,5 +1,5 @@ import { useAppSelector } from '#/app/store/store' -import { TooltipCustom, useShowData } from '#/shared' +import { TooltipCustom } from '#/shared' import { Grow, Box } from '@mui/material' import { useImageIcons } from '../model' @@ -5,7 +5,7 @@ import Image from 'next/image' import Link from 'next/link' import { useAppSelector } from '#/app/store/store' -import { ClientOnly, Success, TooltipCustom, useShowData } from '#/shared' +import { ClientOnly, Error, TooltipCustom } from '#/shared' import styles from './image-messages-list.module.scss' import { createPortal } from 'react-dom' @@ -13,6 +13,7 @@ import { useMessages } from '../model/use-messages' import { ImageIcons } from './image-icons' import { ImageModal } from '#/features/image-modal' import { Message } from '#/entities/message' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' interface MessagesList { device: 'mobile' | 'desktop' @@ -22,10 +23,9 @@ interface MessagesList { } export const ImageMessagesList: React.FC = memo(({ device, images, getMessagesPagination }) => { - const { error, showError, isError } = useShowData() + const { showMessage } = useShowDataStore() const [modal, setModal] = useState(false) const theme = useAppSelector((state) => state.theme.theme) - const { chosenImage, setChosenImage, loaded, setLoaded, computedLibraryImages } = useMessages(images) @@ -45,7 +45,6 @@ export const ImageMessagesList: React.FC = memo(({ device, images, )} - = memo(({ device, images, }} width={500} height={500} - src={ - (message.file as unknown as string) || - '' - } - alt={ - 'К сожалению, изображение не загрузилось' - } + src={(message.file as unknown as string) || ''} + alt={'К сожалению, изображение не загрузилось'} /> = memo(({ device, images, }} width={10} height={10} - src={ - (message.file as unknown as string) || - '' - } + src={(message.file as unknown as string) || ''} alt='' /> > @@ -179,10 +170,7 @@ export const ImageMessagesList: React.FC = memo(({ device, images, userSelect: 'none', objectFit: 'contain', }} - src={ - (message.file as unknown as string) || - '' - } + src={(message.file as unknown as string) || ''} alt='К сожалению, изображение не загрузилось' /> = memo(({ device, images, right: 0, filter: 'blur(10px) brightness(0.7)', }} - src={ - (message.file as unknown as string) || - '' - } + src={(message.file as unknown as string) || ''} alt='К сожалению, изображение не загрузилось' /> > @@ -213,19 +198,14 @@ export const ImageMessagesList: React.FC = memo(({ device, images, {!loaded && ( @@ -254,9 +234,7 @@ export const ImageMessagesList: React.FC = memo(({ device, images, {!loaded ? '' : message.content.length > 0 - ? message?.content - .replaceAll('"', '') - .slice(0, 30) + ? message?.content.replaceAll('"', '').slice(0, 30) : 'описание отсутствует'} {message?.content.length > 30 && loaded && '...'} @@ -5,9 +5,9 @@ import { useRouter } from 'next/router' import { useSession } from 'next-auth/react' import { useAppSelector } from '#/app/store/store' -import { Success, useShowData } from '#/shared' import { getReferral, IReferral } from '#/shared/api/endpoints' import { API_HOST } from '#/shared/lib/constants' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' interface IProps { device: 'desktop' | 'mobile' } @@ -17,7 +17,8 @@ export const Referral = ({ device }: IProps) => { const user = useAppSelector((state) => state.user) const { push } = useRouter() const url = API_HOST + '/r/' + user.email - const { error, showError, isError } = useShowData() + const { showMessage } = useShowDataStore() + const { data } = useSession() const [referrals, setReferrals] = useState(null) @@ -55,17 +56,8 @@ export const Referral = ({ device }: IProps) => { }, [data?.access]) return ( - - + + { width={100} height={100} onClick={() => { - navigator.clipboard - .writeText(url) - .then(() => showError('Ссылка скопирована!')) + navigator.clipboard.writeText(url).then(() => showMessage('Ссылка скопирована!', 'success')) }} /> - + { Регистраций - - {referrals?.registrations_count || 0} - + {referrals?.registrations_count || 0} @@ -251,9 +234,7 @@ export const Referral = ({ device }: IProps) => { Бонусов - - {referrals?.accrued_bonuses_amount || 0} - + {referrals?.accrued_bonuses_amount || 0} @@ -302,9 +283,7 @@ export const Referral = ({ device }: IProps) => { whiteSpace: 'nowrap', }} > - {el.username.length > 20 - ? el.username.slice(0, 20) + '...' - : el.username} + {el.username.length > 20 ? el.username.slice(0, 20) + '...' : el.username} { )} - ) } @@ -8,7 +8,6 @@ import Image from 'next/image' import { useSession } from 'next-auth/react' import { useAppSelector } from '#/app/store/store' -import { Error, Loader, useShowData } from '#/shared' import { API_URL } from '#/shared/lib/constants' import { decodeError } from '#/shared/lib/helpers/decode-error' import { styleInputWithoutBorderFocus } from '#/shared/ui/input' @@ -18,6 +17,7 @@ import { getAudioList } from '#/widgets/whisper/api/getAudioList' import { Card } from '../card/card' import styles2 from './whisper.module.scss' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' export interface WhisperResponse { audio_link: string @@ -64,7 +64,7 @@ export const WhisperWidget = () => { } catch (err) {} } - const { error, showError } = useShowData() + const { showMessage } = useShowDataStore() const transcript = async (e: any) => { if (!audio) { @@ -88,7 +88,7 @@ export const WhisperWidget = () => { setLoading(false) } catch (err) { setList([]) - showError(decodeError(err, 'Ошибка генерации')) + showMessage(decodeError(err, 'Ошибка генерации')) setLoading(false) } } @@ -151,9 +151,7 @@ export const WhisperWidget = () => { height={27} width={27} style={{ marginTop: 4, cursor: 'pointer' }} - src={`/svg/chatgpt/send_message${ - theme === 'light' ? '' : '_dark' - }.svg`} + src={`/svg/chatgpt/send_message${theme === 'light' ? '' : '_dark'}.svg`} alt={''} /> ) : ( @@ -175,9 +173,7 @@ export const WhisperWidget = () => { {list?.length === 0 ? ( - - У вас пока нет расшифрованных записей - + У вас пока нет расшифрованных записей ) : ( <> @@ -197,7 +193,6 @@ export const WhisperWidget = () => { > )} - > ) } @@ -26,6 +26,7 @@ ] } ], - "simple-import-sort/exports": "warn" + "simple-import-sort/exports": "off", + "react-hooks/rules-of-hooks": "off" } } @@ -5,6 +5,6 @@ "trailingComma": "es5", "jsxBracketSameLine": false, "semi": false, - "printWidth": 100, + "printWidth": 150, "jsxSingleQuote": true } @@ -55,7 +55,7 @@ "react-cookie": "^4.1.1", "react-dom": "18.2.0", "react-draft-wysiwyg": "^1.15.0", - "react-hook-form": "^7.43.9", + "react-hook-form": "^7.54.2", "react-i18next": "^13.0.3", "react-markdown": "^8.0.7", "react-redux": "^8.0.5", @@ -66,7 +66,8 @@ "sharp": "^0.34.1", "styled-components": "^5.3.9", "swiper": "^11.2.1", - "typescript": "5.1.3" + "typescript": "5.1.3", + "zustand": "^5.0.3" }, "devDependencies": { "@svgr/webpack": "^8.1.0", @@ -11044,18 +11045,19 @@ } }, "node_modules/react-hook-form": { - "version": "7.48.2", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz", - "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==", + "version": "7.54.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz", + "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==", + "license": "MIT", "engines": { - "node": ">=12.22.0" + "node": ">=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/react-hook-form" }, "peerDependencies": { - "react": "^16.8.0 || ^17 || ^18" + "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "node_modules/react-i18next": { @@ -13340,6 +13342,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -76,7 +76,7 @@ "react-cookie": "^4.1.1", "react-dom": "18.2.0", "react-draft-wysiwyg": "^1.15.0", - "react-hook-form": "^7.43.9", + "react-hook-form": "^7.54.2", "react-i18next": "^13.0.3", "react-markdown": "^8.0.7", "react-redux": "^8.0.5", @@ -87,7 +87,8 @@ "sharp": "^0.34.1", "styled-components": "^5.3.9", "swiper": "^11.2.1", - "typescript": "5.1.3" + "typescript": "5.1.3", + "zustand": "^5.0.3" }, "devDependencies": { "@svgr/webpack": "^8.1.0",
Смена пароля
{title}
Переотправить приглашение
При переотправке приглашения у сотрудника будет заменен отправленный пароль.
{error ? error?.message : ''}
+ {error ? error?.message : ""} +