@@ -111,4 +111,5 @@ margin-left: 3px; font-weight: 400; color: var(--new-ui-gray-color); + white-space: nowrap; } @@ -0,0 +1,93 @@ +.banner { + position: fixed; + z-index: 9999; + background: #1d1d21; + border: 1px solid rgba(164, 170, 181, 0.1); + border-radius: 16px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.desktop { + right: 24px; + bottom: 24px; + width: 450px; +} + +.mobile { + left: 50%; + bottom: 16px; + transform: translateX(-50%); + width: calc(100% - 32px); + max-width: 335px; +} + +.content { + display: flex; + flex-direction: column; + gap: 12px; +} + +.title { + font-size: 24px; + font-weight: 600; + color: #fff; + line-height: 1; +} + +.text { + font-size: 14px; + color: #a4aab5; + line-height: 1.4; +} + +.link { + color: #8280ff; + text-decoration: underline; +} + +.actions { + display: flex; + flex-direction: column; + gap: 10px; +} + +.row { + display: flex; + gap: 10px; +} + +.btnPrimary { + flex: 1; + height: 40px; + border-radius: 10px; + background: #fff; + color: #000; + font-size: 14px; + font-weight: 500; + cursor: pointer; +} + +.btnOutline { + width: 100%; + height: 40px; + border: 1px solid #a4aab5; + border-radius: 10px; + background: transparent; + color: #fff; + font-size: 14px; + font-weight: 500; + cursor: pointer; +} + +.mobile .row { + flex-direction: column; +} + +.mobile .btnPrimary, +.mobile .btnOutline { + width: 100%; + flex: none; +} @@ -0,0 +1,65 @@ +import React from 'react' +import Link from 'next/link' + +import { useAppSelector } from '#/app/store/store' +import { getDeviceType } from '#/shared/lib/helpers' +import { initYandexMetrika } from '#/shared/lib/yandex-metrika' + +import styles from './cookie-consent.module.scss' + +function getConsent(): string | null { + if (typeof window === 'undefined') return null + const value = localStorage.getItem('cookie-consent') + if (value === 'all' || value === 'necessary' || value === 'declined') return value + return null +} + +export function CookieConsent() { + const account_type = useAppSelector((state) => state.user.account_type) + const [consent, setConsent] = React.useState(undefined) + const desktop = getDeviceType() === 'desktop' + + React.useEffect(() => { + if (account_type !== 'regular') return + const stored = getConsent() + setConsent(stored) + if (stored === 'all') initYandexMetrika() + }, [account_type]) + + const choose = (value: string) => { + localStorage.setItem('cookie-consent', value) + setConsent(value) + if (value === 'all') initYandexMetrika() + } + + if (account_type !== 'regular' || consent !== null) return null + + return ( +
+
+

Cookies и аналитика

+

+ Мы используем технические cookies для работы сайта и аналитические cookies для улучшения + сервиса. Подробнее — в{' '} + + Политике обработки персональных данных + + . +

+
+
+
+ + +
+ +
+
+ ) +} @@ -6,6 +6,7 @@ import { SendBtn } from '#/app/components/input_components/send_button' import { IModelInputs } from '#/shared/api/models/models' import { c } from '#/shared/lib/helpers' import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' +import { ChatDisclaimer } from '#/shared/ui/chat-disclaimer/chat-disclaimer' import { PredictPrice } from './predict-price' @@ -320,9 +321,10 @@ export const ModelInput: FC = ({ )} - - + {styles === 'chats' && ( + + )} ) } @@ -1,11 +1,10 @@ -import React, { useState } from 'react' +import React from 'react' import { useCookies } from 'react-cookie' import { useForm } from 'react-hook-form' import { SubmitErrorHandler, SubmitHandler } from 'react-hook-form' -import { Button, InputAdornment, TextField } from '@mui/material' +import { Button, Checkbox, InputAdornment, TextField, Typography } from '@mui/material' import Box from '@mui/material/Box' import CircularProgress from '@mui/material/CircularProgress' -import Typography from '@mui/material/Typography' import axios from 'axios' import Link from 'next/link' import { useRouter } from 'next/router' @@ -17,8 +16,8 @@ import { IEmailForms } from '#/features/register-by-email/model/types' import { emailOptions } from '#/shared' import { InputStyleDark } from '#/shared' import { getApiUrl } from '#/shared/lib/constants' -import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' import { useDeviceType } from '#/shared/lib/hooks/use-device-type' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' import styles from './register-email-form.module.scss' @@ -39,6 +38,7 @@ export const RegisterEmailForm: React.FC = ({ successLo const [cookie] = useCookies() const [passwordShowed, showPassword] = React.useState(false) + const [subscribedToEmails, setSubscribedToEmails] = React.useState(false) const makeNewAccount: SubmitHandler = async (data) => { setLoading(true) @@ -52,6 +52,7 @@ export const RegisterEmailForm: React.FC = ({ successLo utm_term: any utm_content: any referer?: string | null + subscribed_to_emails?: boolean } = { email: data.email, password: data.password1, @@ -66,6 +67,10 @@ export const RegisterEmailForm: React.FC = ({ successLo req_data.referer = localStorage.getItem('referral') } + if (subscribedToEmails) { + req_data.subscribed_to_emails = true + } + const { status, data: result } = await axios.post(getApiUrl() + '/auth/register', req_data) if (status !== 201) { @@ -183,6 +188,21 @@ export const RegisterEmailForm: React.FC = ({ successLo })} /> + + setSubscribedToEmails(e.target.checked)} + size='medium' + sx={{ + color: '#7F7DF3', + paddingTop: 0, + '&.Mui-checked': { color: '#7F7DF3' }, + }} + /> + + Хочу получать полезные материалы про ИИ, обновления моделей и маркетинговые сообщения на email + + {loading ? ( @@ -1,16 +1,15 @@ import { Button } from '@mui/material' import { signIn } from 'next-auth/react' import Image from 'next/image' -import React from 'react' +import React, { useState } from 'react' -import styles from './yandex-auth-button.module.scss' +export const YandexAuthButton = ({ children = 'Войти с Яндекс ID' }: { children?: React.ReactNode }) => { + const [hovered, setHovered] = useState(false) -interface YandexAuthButtonProps {} - -export const YandexAuthButton = ({}: YandexAuthButtonProps) => { return ( ) } @@ -15,6 +15,7 @@ import { pingFangFont } from '#/shared/lib/constants/font/font' import { getDeviceType } from '#/shared/lib/helpers' import { useBlockTelegram } from '#/shared/lib/hooks/use-block-telegram' import { useEnv } from '#/shared/lib/hooks/use-env' +import { CookieConsent } from '#/features/cookie-consent/ui/cookie-consent' import { Providers } from '#/widgets/providers' import { setupAxios } from '#/shared/api/axios-interceptors' @@ -70,6 +71,7 @@ function AppContent({ {Component.getLayout ? Component.getLayout() : } + @@ -0,0 +1,28 @@ +export function initYandexMetrika() { + if (typeof window === 'undefined' || (window as any).__ymInited) return + ;(window as any).__ymInited = true + + ;(function (m: any, e: Document, t: string, r: string, i: string) { + m[i] = + m[i] || + function (...args: unknown[]) { + ;(m[i].a = m[i].a || []).push(args) + } + m[i].l = 1 * new Date().getTime() + for (let j = 0; j < e.scripts.length; j++) { + if (e.scripts[j].src === r) return + } + const k = e.createElement(t) as HTMLScriptElement + const a = e.getElementsByTagName(t)[0] + k.async = true + k.src = r + a.parentNode?.insertBefore(k, a) + })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js', 'ym') + + ;(window as any).ym(93475901, 'init', { + clickmap: true, + trackLinks: true, + accurateTrackBounce: true, + webvisor: true, + }) +} @@ -0,0 +1,18 @@ +.root { + width: 100%; + color: #80808e; + font-weight: 400; + text-align: center; + letter-spacing: -0.01em; + line-height: 100%; +} + +.desktop { + margin-top: 6px; + font-size: 11px; +} + +.mobile { + margin-top: 6px; + font-size: 10px; +} @@ -0,0 +1,13 @@ +import { c } from '#/shared' + +import styles from './chat-disclaimer.module.scss' + +export const ChatDisclaimer = ({ desktop }: { desktop: boolean }) => { + return ( +

+ ИИ может делать ошибки, перепроверяйте результат. + {desktop ? ' ' :
} + Не вводите личные данные других людей без их согласия. +

+ ) +} @@ -19,10 +19,10 @@ export const DateInput = ({ useEffect(() => { if (date) { - setDate(new Date().toISOString().split('T')[0]) + setDate(dayjs().format('YYYY-MM-DD')) } if (prevdate) { - setDate(dayjs(new Date().toISOString()).add(-1, 'year').format('YYYY-DD-MM')) + setDate(dayjs().subtract(1, 'year').format('YYYY-MM-DD')) } }, [date, prevdate]) @@ -51,10 +51,10 @@ export const DateInput = ({ } onChange={(date) => { if (date?.isValid()) { - setDate(JSON.stringify(date).split('"')[1].split('T')[0]) + setDate(date.format('YYYY-MM-DD')) } }} - format='MM-DD-YYYY' + format='DD.MM.YYYY' views={['year', 'month', 'day']} /> @@ -120,7 +120,7 @@ const Register: NextPageWithLayout = () => { {referral === '' && isReferral && ( <> - + Зарегистрироваться с Яндекс ID или email @@ -2,7 +2,7 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { Box, Button, TextField, Typography } from '@mui/material' import axios from 'axios' -import { Dayjs } from 'dayjs' +import dayjs, { Dayjs } from 'dayjs' import { useSession } from 'next-auth/react' import styles from '#/app/styles/business.module.scss' @@ -36,7 +36,7 @@ export default function BusinessHost() { const [mailing, setMailing] = useState() const [toDate, setToDate] = useState('') const { data } = useSession() - const { message, isOpened } = useShowDataStore() + const { showMessage } = useShowDataStore() useEffect(() => { getPersons(data?.access, true).then((res) => setSecurityList(res ? res?.reverse() : null)) @@ -65,6 +65,10 @@ export default function BusinessHost() { } const download = () => { + if (dayjs(fromDate).isAfter(toDate) || dayjs(toDate).isAfter(dayjs())) { + showMessage('Указаны неверные даты') + return + } axios.get(getApiUrl() + `/auth/business-host/download-expenses?type=employees&from_date=${fromDate}&to_date=${toDate}`, { responseType: 'arraybuffer', headers: { Authorization: `Bearer ${data?.access}` }, @@ -7,11 +7,12 @@ import TableCell from '@mui/material/TableCell' import TableContainer from '@mui/material/TableContainer' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' -import { Dayjs } from 'dayjs' +import dayjs, { Dayjs } from 'dayjs' import { useSession } from 'next-auth/react' import { ResponseGetLogsList } from '#/features/invite-person-in-business/model/types' import { Search } from '#/shared' +import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' import { DateInput } from '#/shared/ui/date-input/date-input' import styles from '#/widgets/business-models/ui/models-list/models-list.module.scss' import { getLogsList } from '#/widgets/business-persons/api/get-logsList' @@ -29,8 +30,13 @@ export const LogList = () => { const [showPersons, setShowPersons] = useState(true) const [search, setSearch] = useState('') const { data } = useSession() + const { showMessage } = useShowDataStore() useEffect(() => { + if (fromDate && toDate && (dayjs(fromDate).isAfter(toDate) || dayjs(toDate).isAfter(dayjs()))) { + showMessage('Указаны неверные даты') + return + } getLogsList(offset, 20, logType, fromDate, toDate, data?.access).then((res) => setLogs(res ? res : null)) }, [data?.access, logType, fromDate, toDate])