From 1b06404d6473e8feaf4dda61508456fbf5b77e6f Mon Sep 17 00:00:00 2001 From: Dev Server Date: Mon, 9 Mar 2026 21:25:36 +0300 Subject: [PATCH] feat: chat fixes, header cleanup, scrollbar, UI improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - chat: fix create_direct endpoint (was /chats/ → /chats/create_direct/ with user_id) - chat: fix backend NotSupportedError — remove select_for_update()+distinct() combo - chat: normalize chat objects (participant_id from other_participant.id) - chat: enrich new chats with contact data so contacts section updates correctly - chat: sendMessage — JSON when no file, FormData only with attachment - chat: show send errors in UI instead of silent catch - header: hide search, language, contacts, workspaces, account buttons - theme: thin custom scrollbar (6px, theme-aware light/dark) - settings: always high contrast, vertical nav only, purple default, Inter font - settings-button: replaced gear icon with dark/light toggle + fullscreen button Co-Authored-By: Claude Sonnet 4.6 --- backend/apps/chat/services.py | 9 +- .../src/app/auth/jwt/sign-in/layout.jsx | 9 +- front_minimal/src/app/dashboard/page.jsx | 1 - .../src/app/dashboard/post/[title]/error.jsx | 1 - .../src/app/dashboard/product/[id]/error.jsx | 1 - .../app/dashboard/product/[id]/loading.jsx | 1 - front_minimal/src/app/post/[title]/error.jsx | 1 - front_minimal/src/app/product/[id]/error.jsx | 1 - .../src/app/product/[id]/loading.jsx | 1 - .../src/auth/context/amplify/action.jsx | 1 - .../auth/context/amplify/auth-provider.jsx | 1 - .../src/auth/context/auth-context.jsx | 1 - .../src/auth/context/auth0/auth-provider.jsx | 1 - .../src/auth/context/firebase/action.js | 1 - .../auth/context/firebase/auth-provider.jsx | 1 - front_minimal/src/auth/context/jwt/action.js | 1 - .../src/auth/context/jwt/auth-provider.jsx | 1 - .../src/auth/context/supabase/action.jsx | 1 - .../auth/context/supabase/auth-provider.jsx | 1 - front_minimal/src/auth/guard/auth-guard.jsx | 31 ++- front_minimal/src/auth/guard/guest-guard.jsx | 1 - .../src/auth/guard/role-based-guard.jsx | 1 - .../src/auth/hooks/use-auth-context.js | 1 - .../src/components/animate/animate-border.jsx | 1 - .../src/components/animate/motion-lazy.jsx | 1 - .../scroll-progress/use-scroll-progress.js | 1 - .../src/components/iconify/iconify.jsx | 1 - front_minimal/src/components/label/label.jsx | 1 - front_minimal/src/components/label/styles.js | 1 - .../loading-screen/loading-screen.jsx | 1 - .../loading-screen/splash-screen.jsx | 1 - front_minimal/src/components/logo/logo.jsx | 1 - .../components/progress-bar/progress-bar.jsx | 1 - .../components/settings/config-settings.js | 8 +- .../src/components/settings/context/index.js | 2 + .../settings/context/settings-context.js | 5 + .../settings/context/settings-provider.jsx | 11 +- .../settings/context/use-settings-context.js | 3 +- .../settings/drawer/font-options.jsx | 2 +- .../settings/drawer/fullscreen-button.jsx | 1 - .../settings/drawer/nav-options.jsx | 9 +- .../settings/drawer/presets-options.jsx | 2 +- .../settings/drawer/settings-drawer.jsx | 190 +------------- .../src/components/snackbar/snackbar.jsx | 1 - .../components/walktour/walktour-tooltip.jsx | 1 - front_minimal/src/hooks/use-boolean.js | 1 - front_minimal/src/hooks/use-local-storage.js | 21 +- .../src/hooks/use-scroll-offset-top.js | 1 - .../src/layouts/auth-centered/layout.jsx | 1 - .../src/layouts/auth-split/layout.jsx | 22 +- .../src/layouts/auth-split/section.jsx | 4 +- .../src/layouts/components/account-drawer.jsx | 1 - .../layouts/components/contacts-popover.jsx | 1 - .../layouts/components/language-popover.jsx | 1 - .../src/layouts/components/nav-upgrade.jsx | 4 +- .../components/notifications-drawer/index.jsx | 1 - .../layouts/components/searchbar/index.jsx | 1 - .../layouts/components/settings-button.jsx | 108 +++++--- .../layouts/components/workspaces-popover.jsx | 1 - .../src/layouts/core/header-base.jsx | 2 +- .../src/layouts/core/layout-section.jsx | 1 - .../src/layouts/dashboard/layout.jsx | 49 ++-- front_minimal/src/layouts/dashboard/main.jsx | 7 +- front_minimal/src/layouts/main/layout.jsx | 1 - front_minimal/src/layouts/simple/layout.jsx | 1 - front_minimal/src/locales/all-langs.js | 1 - front_minimal/src/locales/i18n-provider.jsx | 1 - .../src/locales/localization-provider.jsx | 1 - front_minimal/src/locales/use-locales.js | 1 - front_minimal/src/main.jsx | 7 +- .../src/routes/components/router-link.jsx | 8 +- .../src/routes/hooks/use-search-params.js | 8 +- front_minimal/src/routes/sections.jsx | 8 +- .../_examples/extra/animate-view/view.jsx | 1 - .../_examples/extra/carousel-view/view.jsx | 1 - .../_examples/extra/chart-view/view.jsx | 1 - .../_examples/extra/dnd-view/view.jsx | 1 - .../_examples/extra/editor-view/view.jsx | 1 - .../extra/form-validation-view/view.jsx | 1 - .../_examples/extra/form-wizard-view/view.jsx | 1 - .../_examples/extra/image-view/view.jsx | 1 - .../_examples/extra/label-view/view.jsx | 1 - .../_examples/extra/lightbox-view/view.jsx | 1 - .../_examples/extra/map-view/view.jsx | 1 - .../_examples/extra/markdown-view/view.jsx | 1 - .../extra/mega-menu-view/horizontal.jsx | 1 - .../_examples/extra/mega-menu-view/mobile.jsx | 1 - .../_examples/extra/mega-menu-view/view.jsx | 1 - .../extra/multi-language-view/view.jsx | 1 - .../extra/navigation-bar-view/view.jsx | 1 - .../extra/organizational-chart-view/view.jsx | 1 - .../extra/scroll-progress-view/view.jsx | 1 - .../_examples/extra/scrollbar-view/view.jsx | 1 - .../_examples/extra/snackbar-view/view.jsx | 1 - .../_examples/extra/upload-view/view.jsx | 1 - .../utilities-view/copy-to-clipboard.jsx | 1 - .../_examples/extra/utilities-view/view.jsx | 1 - .../_examples/extra/walktour-view/view.jsx | 1 - .../_examples/foundation/colors-view/view.jsx | 1 - .../_examples/foundation/grid-view/view.jsx | 1 - .../_examples/foundation/icons-view/view.jsx | 1 - .../foundation/shadows-view/view.jsx | 1 - .../foundation/typography-view/view.jsx | 1 - .../_examples/mui/accordion-view/view.jsx | 1 - .../_examples/mui/alert-view/view.jsx | 1 - .../_examples/mui/autocomplete-view/view.jsx | 1 - .../_examples/mui/avatar-view/view.jsx | 1 - .../_examples/mui/badge-view/view.jsx | 1 - .../_examples/mui/breadcrumbs-view/view.jsx | 1 - .../_examples/mui/button-view/view.jsx | 1 - .../_examples/mui/checkbox-view/view.jsx | 1 - .../sections/_examples/mui/chip-view/view.jsx | 1 - .../_examples/mui/data-grid-view/view.jsx | 1 - .../_examples/mui/dialog-view/view.jsx | 1 - .../sections/_examples/mui/list-view/view.jsx | 1 - .../sections/_examples/mui/menu-view/view.jsx | 1 - .../_examples/mui/pagination-view/view.jsx | 1 - .../_examples/mui/picker-view/view.jsx | 1 - .../_examples/mui/popover-view/view.jsx | 1 - .../_examples/mui/progress-view/view.jsx | 1 - .../_examples/mui/radio-button-view/view.jsx | 1 - .../_examples/mui/rating-view/view.jsx | 1 - .../_examples/mui/slider-view/view.jsx | 1 - .../_examples/mui/stepper-view/view.jsx | 1 - .../_examples/mui/switch-view/view.jsx | 1 - .../_examples/mui/table-view/view.jsx | 1 - .../sections/_examples/mui/tabs-view/view.jsx | 1 - .../_examples/mui/textfield-view/view.jsx | 1 - .../_examples/mui/timeline-view/view.jsx | 1 - .../_examples/mui/tooltip-view/view.jsx | 1 - .../_examples/mui/transfer-list-view/view.jsx | 1 - .../sections/_examples/mui/tree-view/view.jsx | 1 - front_minimal/src/sections/_examples/view.jsx | 1 - .../src/sections/about/view/about-view.jsx | 1 - .../account/view/user-account-view.jsx | 1 - .../centered/centered-reset-password-view.jsx | 1 - .../centered/centered-sign-in-view.jsx | 1 - .../centered/centered-sign-up-view.jsx | 1 - .../centered-update-password-view.jsx | 1 - .../centered/centered-verify-view.jsx | 1 - .../split/split-reset-password-view.jsx | 1 - .../auth-demo/split/split-sign-in-view.jsx | 1 - .../auth-demo/split/split-sign-up-view.jsx | 1 - .../split/split-update-password-view.jsx | 1 - .../auth-demo/split/split-verify-view.jsx | 1 - .../auth/jwt/jwt-reset-password-view.jsx | 4 +- .../sections/auth/jwt/jwt-sign-in-view.jsx | 36 ++- .../sections/auth/jwt/jwt-sign-up-view.jsx | 154 ++++++++--- .../auth/jwt/jwt-verify-email-view.jsx | 2 +- front_minimal/src/sections/blank/view.jsx | 1 - .../sections/blog/view/post-create-view.jsx | 1 - .../blog/view/post-details-home-view.jsx | 1 - .../sections/blog/view/post-details-view.jsx | 1 - .../src/sections/blog/view/post-edit-view.jsx | 1 - .../blog/view/post-list-home-view.jsx | 1 - .../src/sections/blog/view/post-list-view.jsx | 1 - .../src/sections/board/view/board-view.jsx | 2 +- .../sections/calendar/view/calendar-view.jsx | 1 - .../sections/chat/view/chat-platform-view.jsx | 240 ++++++++++++++---- .../src/sections/chat/view/chat-view.jsx | 1 - .../checkout/context/checkout-provider.jsx | 1 - .../checkout/context/use-checkout-context.jsx | 1 - .../sections/checkout/view/checkout-view.jsx | 1 - .../children/view/children-progress-view.jsx | 9 +- .../src/sections/coming-soon/view.jsx | 1 - .../sections/contact/view/contact-view.jsx | 1 - front_minimal/src/sections/error/403-view.jsx | 1 - front_minimal/src/sections/error/500-view.jsx | 1 - .../src/sections/error/not-found-view.jsx | 1 - .../src/sections/faqs/view/faqs-view.jsx | 1 - .../file-manager/view/file-manager-view.jsx | 1 - .../src/sections/home/view/home-view.jsx | 1 - .../invoice/view/invoice-create-view.jsx | 1 - .../invoice/view/invoice-details-view.jsx | 1 - .../invoice/view/invoice-edit-view.jsx | 1 - .../invoice/view/invoice-list-view.jsx | 1 - .../src/sections/job/view/job-create-view.jsx | 1 - .../sections/job/view/job-details-view.jsx | 1 - .../src/sections/job/view/job-edit-view.jsx | 1 - .../src/sections/job/view/job-list-view.jsx | 1 - .../src/sections/kanban/view/kanban-view.jsx | 1 - .../src/sections/mail/view/mail-view.jsx | 1 - .../src/sections/maintenance/view.jsx | 1 - .../order/view/order-details-view.jsx | 1 - .../sections/order/view/order-list-view.jsx | 1 - .../view/overview-analytics-view.jsx | 1 - .../overview/app/view/overview-app-view.jsx | 1 - .../banking/view/overview-banking-view.jsx | 1 - .../booking/view/overview-booking-view.jsx | 1 - .../client/view/overview-client-view.jsx | 43 +++- .../course/view/overview-course-view.jsx | 156 +++++++++++- .../view/overview-ecommerce-view.jsx | 1 - .../overview/file/view/overview-file-view.jsx | 1 - .../src/sections/payment/payment-methods.jsx | 1 - .../sections/payment/view/payment-view.jsx | 1 - .../src/sections/permission/view.jsx | 1 - .../sections/pricing/view/pricing-view.jsx | 1 - .../src/sections/product/product-skeleton.jsx | 1 - .../product/view/product-create-view.jsx | 1 - .../product/view/product-details-view.jsx | 1 - .../product/view/product-edit-view.jsx | 1 - .../product/view/product-list-view.jsx | 1 - .../view/product-shop-details-view.jsx | 1 - .../product/view/product-shop-view.jsx | 1 - .../sections/students/view/students-view.jsx | 165 ++++++++---- .../sections/tour/view/tour-create-view.jsx | 1 - .../sections/tour/view/tour-details-view.jsx | 1 - .../src/sections/tour/view/tour-edit-view.jsx | 1 - .../src/sections/tour/view/tour-list-view.jsx | 1 - .../src/sections/user/profile-followers.jsx | 1 - .../src/sections/user/profile-home.jsx | 1 - .../sections/user/view/user-cards-view.jsx | 1 - .../sections/user/view/user-create-view.jsx | 1 - .../src/sections/user/view/user-edit-view.jsx | 1 - .../src/sections/user/view/user-list-view.jsx | 1 - .../sections/user/view/user-profile-view.jsx | 1 - .../video-call/view/video-call-view.jsx | 3 +- .../src/theme/color-scheme-script.js | 1 - front_minimal/src/theme/theme-provider.jsx | 36 +++ front_minimal/src/utils/chat-api.js | 54 +++- 220 files changed, 899 insertions(+), 717 deletions(-) create mode 100644 front_minimal/src/components/settings/context/settings-context.js diff --git a/backend/apps/chat/services.py b/backend/apps/chat/services.py index 27c23a6..ee88fec 100644 --- a/backend/apps/chat/services.py +++ b/backend/apps/chat/services.py @@ -37,13 +37,18 @@ class ChatService: with transaction.atomic(): # Ищем существующий чат между пользователями - # Используем select_for_update для блокировки найденных записей - existing_chat = Chat.objects.select_for_update().filter( + # select_for_update() несовместим с distinct() в PostgreSQL, + # поэтому сначала ищем без блокировки, затем лочим найденный объект + existing_chat = Chat.objects.filter( chat_type='direct', participants__user=users[0] ).filter( participants__user=users[1] ).distinct().first() + + if existing_chat: + # Лочим конкретную запись для безопасного возврата + existing_chat = Chat.objects.select_for_update().get(pk=existing_chat.pk) if existing_chat: return existing_chat, False diff --git a/front_minimal/src/app/auth/jwt/sign-in/layout.jsx b/front_minimal/src/app/auth/jwt/sign-in/layout.jsx index db0c419..33ff2ae 100644 --- a/front_minimal/src/app/auth/jwt/sign-in/layout.jsx +++ b/front_minimal/src/app/auth/jwt/sign-in/layout.jsx @@ -7,7 +7,14 @@ import { GuestGuard } from 'src/auth/guard'; export default function Layout({ children }) { return ( - {children} + + {children} + ); } diff --git a/front_minimal/src/app/dashboard/page.jsx b/front_minimal/src/app/dashboard/page.jsx index b153a2b..929223b 100644 --- a/front_minimal/src/app/dashboard/page.jsx +++ b/front_minimal/src/app/dashboard/page.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect } from 'react'; diff --git a/front_minimal/src/app/dashboard/post/[title]/error.jsx b/front_minimal/src/app/dashboard/post/[title]/error.jsx index 9fe9d59..6dbc66b 100644 --- a/front_minimal/src/app/dashboard/post/[title]/error.jsx +++ b/front_minimal/src/app/dashboard/post/[title]/error.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/app/dashboard/product/[id]/error.jsx b/front_minimal/src/app/dashboard/product/[id]/error.jsx index 7f2256e..9bb5595 100644 --- a/front_minimal/src/app/dashboard/product/[id]/error.jsx +++ b/front_minimal/src/app/dashboard/product/[id]/error.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/app/dashboard/product/[id]/loading.jsx b/front_minimal/src/app/dashboard/product/[id]/loading.jsx index cf20ad8..8ab2548 100644 --- a/front_minimal/src/app/dashboard/product/[id]/loading.jsx +++ b/front_minimal/src/app/dashboard/product/[id]/loading.jsx @@ -1,4 +1,3 @@ -'use client'; import { DashboardContent } from 'src/layouts/dashboard'; diff --git a/front_minimal/src/app/post/[title]/error.jsx b/front_minimal/src/app/post/[title]/error.jsx index dcd2ba1..d5794ce 100644 --- a/front_minimal/src/app/post/[title]/error.jsx +++ b/front_minimal/src/app/post/[title]/error.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; import Container from '@mui/material/Container'; diff --git a/front_minimal/src/app/product/[id]/error.jsx b/front_minimal/src/app/product/[id]/error.jsx index 805f4be..bb438bf 100644 --- a/front_minimal/src/app/product/[id]/error.jsx +++ b/front_minimal/src/app/product/[id]/error.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; import Container from '@mui/material/Container'; diff --git a/front_minimal/src/app/product/[id]/loading.jsx b/front_minimal/src/app/product/[id]/loading.jsx index e1a0bf0..8f1d46a 100644 --- a/front_minimal/src/app/product/[id]/loading.jsx +++ b/front_minimal/src/app/product/[id]/loading.jsx @@ -1,4 +1,3 @@ -'use client'; import Container from '@mui/material/Container'; diff --git a/front_minimal/src/auth/context/amplify/action.jsx b/front_minimal/src/auth/context/amplify/action.jsx index 40c3334..64920d3 100644 --- a/front_minimal/src/auth/context/amplify/action.jsx +++ b/front_minimal/src/auth/context/amplify/action.jsx @@ -1,4 +1,3 @@ -'use client'; import { signIn as _signIn, diff --git a/front_minimal/src/auth/context/amplify/auth-provider.jsx b/front_minimal/src/auth/context/amplify/auth-provider.jsx index 9ab2781..0ccb74b 100644 --- a/front_minimal/src/auth/context/amplify/auth-provider.jsx +++ b/front_minimal/src/auth/context/amplify/auth-provider.jsx @@ -1,4 +1,3 @@ -'use client'; import { Amplify } from 'aws-amplify'; import { useMemo, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/auth/context/auth-context.jsx b/front_minimal/src/auth/context/auth-context.jsx index d7dcce2..30f1436 100644 --- a/front_minimal/src/auth/context/auth-context.jsx +++ b/front_minimal/src/auth/context/auth-context.jsx @@ -1,4 +1,3 @@ -'use client'; import { createContext } from 'react'; diff --git a/front_minimal/src/auth/context/auth0/auth-provider.jsx b/front_minimal/src/auth/context/auth0/auth-provider.jsx index 737a7f0..76eee7a 100644 --- a/front_minimal/src/auth/context/auth0/auth-provider.jsx +++ b/front_minimal/src/auth/context/auth0/auth-provider.jsx @@ -1,4 +1,3 @@ -'use client'; import { useAuth0, Auth0Provider } from '@auth0/auth0-react'; import { useMemo, useState, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/auth/context/firebase/action.js b/front_minimal/src/auth/context/firebase/action.js index c66fc31..e065f95 100644 --- a/front_minimal/src/auth/context/firebase/action.js +++ b/front_minimal/src/auth/context/firebase/action.js @@ -1,4 +1,3 @@ -'use client'; import { doc, setDoc, collection } from 'firebase/firestore'; import { diff --git a/front_minimal/src/auth/context/firebase/auth-provider.jsx b/front_minimal/src/auth/context/firebase/auth-provider.jsx index 248a815..d69a0f1 100644 --- a/front_minimal/src/auth/context/firebase/auth-provider.jsx +++ b/front_minimal/src/auth/context/firebase/auth-provider.jsx @@ -1,4 +1,3 @@ -'use client'; import { doc, getDoc } from 'firebase/firestore'; import { onAuthStateChanged } from 'firebase/auth'; diff --git a/front_minimal/src/auth/context/jwt/action.js b/front_minimal/src/auth/context/jwt/action.js index db02f7d..f7ad38b 100644 --- a/front_minimal/src/auth/context/jwt/action.js +++ b/front_minimal/src/auth/context/jwt/action.js @@ -1,4 +1,3 @@ -'use client'; import axios, { endpoints } from 'src/utils/axios'; diff --git a/front_minimal/src/auth/context/jwt/auth-provider.jsx b/front_minimal/src/auth/context/jwt/auth-provider.jsx index ecc4506..17f3267 100644 --- a/front_minimal/src/auth/context/jwt/auth-provider.jsx +++ b/front_minimal/src/auth/context/jwt/auth-provider.jsx @@ -1,4 +1,3 @@ -'use client'; import { useMemo, useEffect, useCallback } from 'react'; import { useSetState } from 'src/hooks/use-set-state'; diff --git a/front_minimal/src/auth/context/supabase/action.jsx b/front_minimal/src/auth/context/supabase/action.jsx index 4ddf2db..c4d5d25 100644 --- a/front_minimal/src/auth/context/supabase/action.jsx +++ b/front_minimal/src/auth/context/supabase/action.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/auth/context/supabase/auth-provider.jsx b/front_minimal/src/auth/context/supabase/auth-provider.jsx index 3a2e0b9..738a952 100644 --- a/front_minimal/src/auth/context/supabase/auth-provider.jsx +++ b/front_minimal/src/auth/context/supabase/auth-provider.jsx @@ -1,4 +1,3 @@ -'use client'; import { useMemo, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/auth/guard/auth-guard.jsx b/front_minimal/src/auth/guard/auth-guard.jsx index 0e7bf6d..4c57756 100644 --- a/front_minimal/src/auth/guard/auth-guard.jsx +++ b/front_minimal/src/auth/guard/auth-guard.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect, useCallback } from 'react'; @@ -10,6 +9,21 @@ import { CONFIG } from 'src/config-global'; import { SplashScreen } from 'src/components/loading-screen'; import { useAuthContext } from '../hooks'; +import { STORAGE_KEY } from '../context/jwt/constant'; +import { isValidToken } from '../context/jwt/utils'; + +// ---------------------------------------------------------------------- + +// Synchronously check if there's a valid token in localStorage +// to avoid showing SplashScreen on every load when user is already authenticated +function hasValidStoredToken() { + try { + const token = localStorage.getItem(STORAGE_KEY); + return token ? isValidToken(token) : false; + } catch { + return false; + } +} // ---------------------------------------------------------------------- @@ -22,7 +36,8 @@ export function AuthGuard({ children }) { const { authenticated, loading } = useAuthContext(); - const [isChecking, setIsChecking] = useState(true); + // Skip splash if we already have a valid token — avoids flash on every page load + const [isChecking, setIsChecking] = useState(() => !hasValidStoredToken()); const createQueryString = useCallback( (name, value) => { @@ -40,18 +55,8 @@ export function AuthGuard({ children }) { } if (!authenticated) { - const { method } = CONFIG.auth; - - const signInPath = { - jwt: paths.auth.jwt.signIn, - auth0: paths.auth.auth0.signIn, - amplify: paths.auth.amplify.signIn, - firebase: paths.auth.firebase.signIn, - supabase: paths.auth.supabase.signIn, - }[method]; - + const signInPath = paths.auth.jwt.signIn; const href = `${signInPath}?${createQueryString('returnTo', pathname)}`; - router.replace(href); return; } diff --git a/front_minimal/src/auth/guard/guest-guard.jsx b/front_minimal/src/auth/guard/guest-guard.jsx index f8d3aa5..2d5442c 100644 --- a/front_minimal/src/auth/guard/guest-guard.jsx +++ b/front_minimal/src/auth/guard/guest-guard.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect } from 'react'; diff --git a/front_minimal/src/auth/guard/role-based-guard.jsx b/front_minimal/src/auth/guard/role-based-guard.jsx index 395423a..749b80f 100644 --- a/front_minimal/src/auth/guard/role-based-guard.jsx +++ b/front_minimal/src/auth/guard/role-based-guard.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/auth/hooks/use-auth-context.js b/front_minimal/src/auth/hooks/use-auth-context.js index 91433bb..b2e8319 100644 --- a/front_minimal/src/auth/hooks/use-auth-context.js +++ b/front_minimal/src/auth/hooks/use-auth-context.js @@ -1,4 +1,3 @@ -'use client'; import { useContext } from 'react'; diff --git a/front_minimal/src/components/animate/animate-border.jsx b/front_minimal/src/components/animate/animate-border.jsx index 6212ed6..5ed968d 100644 --- a/front_minimal/src/components/animate/animate-border.jsx +++ b/front_minimal/src/components/animate/animate-border.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; import { useRef, useState, useEffect } from 'react'; diff --git a/front_minimal/src/components/animate/motion-lazy.jsx b/front_minimal/src/components/animate/motion-lazy.jsx index 08d1583..d4236ab 100644 --- a/front_minimal/src/components/animate/motion-lazy.jsx +++ b/front_minimal/src/components/animate/motion-lazy.jsx @@ -1,4 +1,3 @@ -'use client'; import { LazyMotion } from 'framer-motion'; diff --git a/front_minimal/src/components/animate/scroll-progress/use-scroll-progress.js b/front_minimal/src/components/animate/scroll-progress/use-scroll-progress.js index e0bcda4..bc63560 100644 --- a/front_minimal/src/components/animate/scroll-progress/use-scroll-progress.js +++ b/front_minimal/src/components/animate/scroll-progress/use-scroll-progress.js @@ -1,4 +1,3 @@ -'use client'; import { useRef, useMemo } from 'react'; import { useScroll } from 'framer-motion'; diff --git a/front_minimal/src/components/iconify/iconify.jsx b/front_minimal/src/components/iconify/iconify.jsx index 754394d..393c4fe 100644 --- a/front_minimal/src/components/iconify/iconify.jsx +++ b/front_minimal/src/components/iconify/iconify.jsx @@ -1,4 +1,3 @@ -'use client'; import { forwardRef } from 'react'; import { Icon, disableCache } from '@iconify/react'; diff --git a/front_minimal/src/components/label/label.jsx b/front_minimal/src/components/label/label.jsx index 4f64efd..4ecd894 100644 --- a/front_minimal/src/components/label/label.jsx +++ b/front_minimal/src/components/label/label.jsx @@ -1,4 +1,3 @@ -'use client'; import { forwardRef } from 'react'; diff --git a/front_minimal/src/components/label/styles.js b/front_minimal/src/components/label/styles.js index edede61..52ea1bd 100644 --- a/front_minimal/src/components/label/styles.js +++ b/front_minimal/src/components/label/styles.js @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import { styled } from '@mui/material/styles'; diff --git a/front_minimal/src/components/loading-screen/loading-screen.jsx b/front_minimal/src/components/loading-screen/loading-screen.jsx index a3bbb4b..18c8444 100644 --- a/front_minimal/src/components/loading-screen/loading-screen.jsx +++ b/front_minimal/src/components/loading-screen/loading-screen.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Portal from '@mui/material/Portal'; diff --git a/front_minimal/src/components/loading-screen/splash-screen.jsx b/front_minimal/src/components/loading-screen/splash-screen.jsx index 151a28a..f2aadc0 100644 --- a/front_minimal/src/components/loading-screen/splash-screen.jsx +++ b/front_minimal/src/components/loading-screen/splash-screen.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Portal from '@mui/material/Portal'; diff --git a/front_minimal/src/components/logo/logo.jsx b/front_minimal/src/components/logo/logo.jsx index 5b78ae9..318410b 100644 --- a/front_minimal/src/components/logo/logo.jsx +++ b/front_minimal/src/components/logo/logo.jsx @@ -1,4 +1,3 @@ -'use client'; import { useId, forwardRef } from 'react'; diff --git a/front_minimal/src/components/progress-bar/progress-bar.jsx b/front_minimal/src/components/progress-bar/progress-bar.jsx index 0de0a31..375efe0 100644 --- a/front_minimal/src/components/progress-bar/progress-bar.jsx +++ b/front_minimal/src/components/progress-bar/progress-bar.jsx @@ -1,4 +1,3 @@ -'use client'; import './styles.css'; diff --git a/front_minimal/src/components/settings/config-settings.js b/front_minimal/src/components/settings/config-settings.js index e27a2a2..8166f35 100644 --- a/front_minimal/src/components/settings/config-settings.js +++ b/front_minimal/src/components/settings/config-settings.js @@ -7,10 +7,10 @@ export const STORAGE_KEY = 'app-settings'; export const defaultSettings = { colorScheme: 'light', direction: 'ltr', - contrast: 'default', + contrast: 'hight', navLayout: 'vertical', - primaryColor: 'default', + primaryColor: 'purple', navColor: 'integrate', - compactLayout: true, - fontFamily: defaultFont, + compactLayout: false, + fontFamily: 'Inter', }; diff --git a/front_minimal/src/components/settings/context/index.js b/front_minimal/src/components/settings/context/index.js index 02e1eea..b78caa8 100644 --- a/front_minimal/src/components/settings/context/index.js +++ b/front_minimal/src/components/settings/context/index.js @@ -1,3 +1,5 @@ +export * from './settings-context'; + export * from './settings-provider'; export * from './use-settings-context'; diff --git a/front_minimal/src/components/settings/context/settings-context.js b/front_minimal/src/components/settings/context/settings-context.js new file mode 100644 index 0000000..04816ed --- /dev/null +++ b/front_minimal/src/components/settings/context/settings-context.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const SettingsContext = createContext(undefined); + +export const SettingsConsumer = SettingsContext.Consumer; diff --git a/front_minimal/src/components/settings/context/settings-provider.jsx b/front_minimal/src/components/settings/context/settings-provider.jsx index a281ef1..1f9086a 100644 --- a/front_minimal/src/components/settings/context/settings-provider.jsx +++ b/front_minimal/src/components/settings/context/settings-provider.jsx @@ -1,17 +1,10 @@ -'use client'; - -import { useMemo, useState, useCallback, createContext } from 'react'; +import { useMemo, useState, useCallback } from 'react'; import { useCookies } from 'src/hooks/use-cookies'; import { useLocalStorage } from 'src/hooks/use-local-storage'; import { STORAGE_KEY, defaultSettings } from '../config-settings'; - -// ---------------------------------------------------------------------- - -export const SettingsContext = createContext(undefined); - -export const SettingsConsumer = SettingsContext.Consumer; +import { SettingsContext } from './settings-context'; // ---------------------------------------------------------------------- diff --git a/front_minimal/src/components/settings/context/use-settings-context.js b/front_minimal/src/components/settings/context/use-settings-context.js index 90c5e4b..43ccf53 100644 --- a/front_minimal/src/components/settings/context/use-settings-context.js +++ b/front_minimal/src/components/settings/context/use-settings-context.js @@ -1,8 +1,7 @@ -'use client'; import { useContext } from 'react'; -import { SettingsContext } from './settings-provider'; +import { SettingsContext } from './settings-context'; // ---------------------------------------------------------------------- diff --git a/front_minimal/src/components/settings/drawer/font-options.jsx b/front_minimal/src/components/settings/drawer/font-options.jsx index 9b9bf00..3b21762 100644 --- a/front_minimal/src/components/settings/drawer/font-options.jsx +++ b/front_minimal/src/components/settings/drawer/font-options.jsx @@ -11,7 +11,7 @@ import { SvgColor } from '../../svg-color'; export function FontOptions({ value, options, onClickOption }) { return ( - + {options.map((option) => { const selected = value === option; diff --git a/front_minimal/src/components/settings/drawer/fullscreen-button.jsx b/front_minimal/src/components/settings/drawer/fullscreen-button.jsx index 93dc889..ebd66ec 100644 --- a/front_minimal/src/components/settings/drawer/fullscreen-button.jsx +++ b/front_minimal/src/components/settings/drawer/fullscreen-button.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/components/settings/drawer/nav-options.jsx b/front_minimal/src/components/settings/drawer/nav-options.jsx index 0a02c68..15746d6 100644 --- a/front_minimal/src/components/settings/drawer/nav-options.jsx +++ b/front_minimal/src/components/settings/drawer/nav-options.jsx @@ -40,7 +40,7 @@ export function NavOptions({ options, value, onClickOption, hideNavColor, hideNa const renderLayout = (
- Layout + Расположение {options.layouts.map((option) => ( @@ -58,7 +58,7 @@ export function NavOptions({ options, value, onClickOption, hideNavColor, hideNa const renderColor = (
- Color + Цвет {options.colors.map((option) => ( @@ -74,7 +74,7 @@ export function NavOptions({ options, value, onClickOption, hideNavColor, hideNa ); return ( - + {!hideNavLayout && renderLayout} {!hideNavColor && renderColor} @@ -247,12 +247,11 @@ export function ColorOption({ option, selected, sx, ...other }) { component="span" sx={{ lineHeight: '18px', - textTransform: 'capitalize', fontWeight: 'fontWeightSemiBold', fontSize: (theme) => theme.typography.pxToRem(13), }} > - {option} + {{ integrate: 'Встроен', apparent: 'Явный' }[option] ?? option} ); diff --git a/front_minimal/src/components/settings/drawer/presets-options.jsx b/front_minimal/src/components/settings/drawer/presets-options.jsx index b5f9b8e..53ba3d8 100644 --- a/front_minimal/src/components/settings/drawer/presets-options.jsx +++ b/front_minimal/src/components/settings/drawer/presets-options.jsx @@ -11,7 +11,7 @@ import { SvgColor } from '../../svg-color'; export function PresetsOptions({ value, options, onClickOption }) { return ( - + {options.map((option) => { const selected = value === option.name; diff --git a/front_minimal/src/components/settings/drawer/settings-drawer.jsx b/front_minimal/src/components/settings/drawer/settings-drawer.jsx index 6545bd3..8215dc7 100644 --- a/front_minimal/src/components/settings/drawer/settings-drawer.jsx +++ b/front_minimal/src/components/settings/drawer/settings-drawer.jsx @@ -1,195 +1,13 @@ -'use client'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import Badge from '@mui/material/Badge'; -import Tooltip from '@mui/material/Tooltip'; -import IconButton from '@mui/material/IconButton'; -import Typography from '@mui/material/Typography'; import Drawer, { drawerClasses } from '@mui/material/Drawer'; import { useTheme, useColorScheme } from '@mui/material/styles'; -import COLORS from 'src/theme/core/colors.json'; import { paper, varAlpha } from 'src/theme/styles'; -import { defaultFont } from 'src/theme/core/typography'; -import PRIMARY_COLOR from 'src/theme/with-settings/primary-color.json'; -import { Iconify } from '../../iconify'; -import { BaseOption } from './base-option'; -import { NavOptions } from './nav-options'; -import { Scrollbar } from '../../scrollbar'; -import { FontOptions } from './font-options'; import { useSettingsContext } from '../context'; -import { PresetsOptions } from './presets-options'; -import { defaultSettings } from '../config-settings'; -import { FullScreenButton } from './fullscreen-button'; -// ---------------------------------------------------------------------- - -export function SettingsDrawer({ - sx, - hideFont, - hideCompact, - hidePresets, - hideNavColor, - hideContrast, - hideNavLayout, - hideDirection, - hideColorScheme, -}) { - const theme = useTheme(); - - const settings = useSettingsContext(); - - const { mode, setMode } = useColorScheme(); - - const renderHead = ( - - - Settings - - - - - - { - settings.onReset(); - setMode(defaultSettings.colorScheme); - }} - > - - - - - - - - - - - - - ); - - const renderMode = ( - { - settings.onUpdateField('colorScheme', mode === 'light' ? 'dark' : 'light'); - setMode(mode === 'light' ? 'dark' : 'light'); - }} - /> - ); - - const renderContrast = ( - - settings.onUpdateField('contrast', settings.contrast === 'default' ? 'hight' : 'default') - } - /> - ); - - const renderRTL = ( - - settings.onUpdateField('direction', settings.direction === 'ltr' ? 'rtl' : 'ltr') - } - /> - ); - - const renderCompact = ( - settings.onUpdateField('compactLayout', !settings.compactLayout)} - /> - ); - - const renderPresets = ( - settings.onUpdateField('primaryColor', newValue)} - options={[ - { name: 'default', value: COLORS.primary.main }, - { name: 'cyan', value: PRIMARY_COLOR.cyan.main }, - { name: 'purple', value: PRIMARY_COLOR.purple.main }, - { name: 'blue', value: PRIMARY_COLOR.blue.main }, - { name: 'orange', value: PRIMARY_COLOR.orange.main }, - { name: 'red', value: PRIMARY_COLOR.red.main }, - ]} - /> - ); - - const renderNav = ( - settings.onUpdateField('navColor', newValue), - layout: (newValue) => settings.onUpdateField('navLayout', newValue), - }} - options={{ - colors: ['integrate', 'apparent'], - layouts: ['vertical', 'horizontal', 'mini'], - }} - hideNavColor={hideNavColor} - hideNavLayout={hideNavLayout} - /> - ); - - const renderFont = ( - settings.onUpdateField('fontFamily', newValue)} - options={[defaultFont, 'Inter', 'DM Sans', 'Nunito Sans']} - /> - ); - - return ( - - {renderHead} - - - - - {!hideColorScheme && renderMode} - {!hideContrast && renderContrast} - {!hideDirection && renderRTL} - {!hideCompact && renderCompact} - - {!(hideNavLayout && hideNavColor) && renderNav} - {!hidePresets && renderPresets} - {!hideFont && renderFont} - - - - ); +// Drawer больше не нужен — все настройки зафиксированы. +// Оставляем пустой экспорт для совместимости. +export function SettingsDrawer() { + return null; } diff --git a/front_minimal/src/components/snackbar/snackbar.jsx b/front_minimal/src/components/snackbar/snackbar.jsx index cc95e49..0f8ff8c 100644 --- a/front_minimal/src/components/snackbar/snackbar.jsx +++ b/front_minimal/src/components/snackbar/snackbar.jsx @@ -1,4 +1,3 @@ -'use client'; import Portal from '@mui/material/Portal'; diff --git a/front_minimal/src/components/walktour/walktour-tooltip.jsx b/front_minimal/src/components/walktour/walktour-tooltip.jsx index 588513f..90f924e 100644 --- a/front_minimal/src/components/walktour/walktour-tooltip.jsx +++ b/front_minimal/src/components/walktour/walktour-tooltip.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/hooks/use-boolean.js b/front_minimal/src/hooks/use-boolean.js index b927874..a551ade 100644 --- a/front_minimal/src/hooks/use-boolean.js +++ b/front_minimal/src/hooks/use-boolean.js @@ -1,4 +1,3 @@ -'use client'; import { useMemo, useState, useCallback } from 'react'; diff --git a/front_minimal/src/hooks/use-local-storage.js b/front_minimal/src/hooks/use-local-storage.js index 1c6c7d7..5f0a72a 100644 --- a/front_minimal/src/hooks/use-local-storage.js +++ b/front_minimal/src/hooks/use-local-storage.js @@ -6,24 +6,17 @@ import { localStorageGetItem } from 'src/utils/storage-available'; // ---------------------------------------------------------------------- export function useLocalStorage(key, initialState) { - const [state, set] = useState(initialState); - const multiValue = initialState && typeof initialState === 'object'; + // Read localStorage synchronously on first render — no useEffect, no flash + const [state, set] = useState(() => { + const stored = getStorage(key); + if (!stored) return initialState; + return multiValue ? { ...initialState, ...stored } : stored; + }); + const canReset = !isEqual(state, initialState); - useEffect(() => { - const restoredValue = getStorage(key); - - if (restoredValue) { - if (multiValue) { - set((prevValue) => ({ ...prevValue, ...restoredValue })); - } else { - set(restoredValue); - } - } - }, [key, multiValue]); - const setState = useCallback( (updateState) => { if (multiValue) { diff --git a/front_minimal/src/hooks/use-scroll-offset-top.js b/front_minimal/src/hooks/use-scroll-offset-top.js index 727c6b8..479d48b 100644 --- a/front_minimal/src/hooks/use-scroll-offset-top.js +++ b/front_minimal/src/hooks/use-scroll-offset-top.js @@ -1,4 +1,3 @@ -'use client'; import { useScroll, useMotionValueEvent } from 'framer-motion'; import { useRef, useMemo, useState, useCallback } from 'react'; diff --git a/front_minimal/src/layouts/auth-centered/layout.jsx b/front_minimal/src/layouts/auth-centered/layout.jsx index 9116ed0..05b098f 100644 --- a/front_minimal/src/layouts/auth-centered/layout.jsx +++ b/front_minimal/src/layouts/auth-centered/layout.jsx @@ -1,4 +1,3 @@ -'use client'; import Alert from '@mui/material/Alert'; diff --git a/front_minimal/src/layouts/auth-split/layout.jsx b/front_minimal/src/layouts/auth-split/layout.jsx index d8873b3..7115d03 100644 --- a/front_minimal/src/layouts/auth-split/layout.jsx +++ b/front_minimal/src/layouts/auth-split/layout.jsx @@ -1,4 +1,3 @@ -'use client'; import Alert from '@mui/material/Alert'; @@ -38,6 +37,7 @@ export function AuthSplitLayout({ sx, section, children }) { searchbar: false, workspaces: false, menuButton: false, + helpLink: false, localization: false, notifications: false, }} @@ -77,26 +77,6 @@ export function AuthSplitLayout({ sx, section, children }) { path: paths.auth.jwt.signIn, icon: `${CONFIG.site.basePath}/assets/icons/platforms/ic-jwt.svg`, }, - { - label: 'Firebase', - path: paths.auth.firebase.signIn, - icon: `${CONFIG.site.basePath}/assets/icons/platforms/ic-firebase.svg`, - }, - { - label: 'Amplify', - path: paths.auth.amplify.signIn, - icon: `${CONFIG.site.basePath}/assets/icons/platforms/ic-amplify.svg`, - }, - { - label: 'Auth0', - path: paths.auth.auth0.signIn, - icon: `${CONFIG.site.basePath}/assets/icons/platforms/ic-auth0.svg`, - }, - { - label: 'Supabase', - path: paths.auth.supabase.signIn, - icon: `${CONFIG.site.basePath}/assets/icons/platforms/ic-supabase.svg`, - }, ]} /> {children} diff --git a/front_minimal/src/layouts/auth-split/section.jsx b/front_minimal/src/layouts/auth-split/section.jsx index 03d48e8..ffcc3db 100644 --- a/front_minimal/src/layouts/auth-split/section.jsx +++ b/front_minimal/src/layouts/auth-split/section.jsx @@ -16,9 +16,9 @@ export function Section({ method, layoutQuery, methods, - title = 'Manage the job', + title = 'Добро пожаловать', imgUrl = `${CONFIG.site.basePath}/assets/illustrations/illustration-dashboard.webp`, - subtitle = 'More effectively with optimized workflows.', + subtitle = 'Платформа для онлайн-обучения и работы с репетиторами.', ...other }) { const theme = useTheme(); diff --git a/front_minimal/src/layouts/components/account-drawer.jsx b/front_minimal/src/layouts/components/account-drawer.jsx index b8293ca..67fbd04 100644 --- a/front_minimal/src/layouts/components/account-drawer.jsx +++ b/front_minimal/src/layouts/components/account-drawer.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/layouts/components/contacts-popover.jsx b/front_minimal/src/layouts/components/contacts-popover.jsx index b0b805a..d8c513c 100644 --- a/front_minimal/src/layouts/components/contacts-popover.jsx +++ b/front_minimal/src/layouts/components/contacts-popover.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/layouts/components/language-popover.jsx b/front_minimal/src/layouts/components/language-popover.jsx index 9361de4..2bca3e5 100644 --- a/front_minimal/src/layouts/components/language-popover.jsx +++ b/front_minimal/src/layouts/components/language-popover.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; import { useCallback } from 'react'; diff --git a/front_minimal/src/layouts/components/nav-upgrade.jsx b/front_minimal/src/layouts/components/nav-upgrade.jsx index 30f7d49..5145d91 100644 --- a/front_minimal/src/layouts/components/nav-upgrade.jsx +++ b/front_minimal/src/layouts/components/nav-upgrade.jsx @@ -36,9 +36,9 @@ export function NavUpgrade({ sx, ...other }) { const [sub, setSub] = useState(undefined); // undefined = loading, null = no sub useEffect(() => { - if (!user) return; + if (!user?.id) return; fetchActiveSubscription().then(setSub); - }, [user]); + }, [user?.id]); const displayName = user ? `${user.first_name || ''} ${user.last_name || ''}`.trim() || user.email diff --git a/front_minimal/src/layouts/components/notifications-drawer/index.jsx b/front_minimal/src/layouts/components/notifications-drawer/index.jsx index 40e2fed..251303a 100644 --- a/front_minimal/src/layouts/components/notifications-drawer/index.jsx +++ b/front_minimal/src/layouts/components/notifications-drawer/index.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; import { useState, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/layouts/components/searchbar/index.jsx b/front_minimal/src/layouts/components/searchbar/index.jsx index c4477c3..0640169 100644 --- a/front_minimal/src/layouts/components/searchbar/index.jsx +++ b/front_minimal/src/layouts/components/searchbar/index.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; import parse from 'autosuggest-highlight/parse'; diff --git a/front_minimal/src/layouts/components/settings-button.jsx b/front_minimal/src/layouts/components/settings-button.jsx index 99f3ee0..9c2bd8b 100644 --- a/front_minimal/src/layouts/components/settings-button.jsx +++ b/front_minimal/src/layouts/components/settings-button.jsx @@ -1,45 +1,87 @@ -'use client'; -import { m } from 'framer-motion'; +import { useState, useCallback } from 'react'; -import Badge from '@mui/material/Badge'; -import SvgIcon from '@mui/material/SvgIcon'; +import Tooltip from '@mui/material/Tooltip'; import IconButton from '@mui/material/IconButton'; +import { useColorScheme } from '@mui/material/styles'; + +import { CONFIG } from 'src/config-global'; +import { Iconify } from 'src/components/iconify'; +import { SvgColor, svgColorClasses } from 'src/components/svg-color'; import { useSettingsContext } from 'src/components/settings/context'; // ---------------------------------------------------------------------- -export function SettingsButton({ sx, ...other }) { - const settings = useSettingsContext(); +function FullScreenButton() { + const [fullscreen, setFullscreen] = useState(false); + + const onToggle = useCallback(() => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + setFullscreen(true); + } else if (document.exitFullscreen) { + document.exitFullscreen(); + setFullscreen(false); + } + }, []); return ( - - - - {/* https://icon-sets.iconify.design/solar/settings-bold-duotone/ */} - - - - - + + + `linear-gradient(135deg, ${theme.vars.palette.grey[500]} 0%, ${theme.vars.palette.grey[600]} 100%)`, + ...(fullscreen && { + background: (theme) => + `linear-gradient(135deg, ${theme.vars.palette.primary.light} 0%, ${theme.vars.palette.primary.main} 100%)`, + }), + }, + }} + > + + + + ); +} + +// ---------------------------------------------------------------------- + +export function SettingsButton({ sx, ...other }) { + const settings = useSettingsContext(); + const { mode, setMode } = useColorScheme(); + + const isDark = mode === 'dark' || settings.colorScheme === 'dark'; + + const handleToggle = () => { + const next = isDark ? 'light' : 'dark'; + settings.onUpdateField('colorScheme', next); + setMode(next); + }; + + return ( + <> + + + + + + + ); } diff --git a/front_minimal/src/layouts/components/workspaces-popover.jsx b/front_minimal/src/layouts/components/workspaces-popover.jsx index 84ca8bb..c65ac97 100644 --- a/front_minimal/src/layouts/components/workspaces-popover.jsx +++ b/front_minimal/src/layouts/components/workspaces-popover.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/layouts/core/header-base.jsx b/front_minimal/src/layouts/core/header-base.jsx index 69407cc..2d717d4 100644 --- a/front_minimal/src/layouts/core/header-base.jsx +++ b/front_minimal/src/layouts/core/header-base.jsx @@ -131,7 +131,7 @@ export function HeaderBase({ color="inherit" sx={{ typography: 'subtitle2' }} > - Need help? + Нужна помощь? )} diff --git a/front_minimal/src/layouts/core/layout-section.jsx b/front_minimal/src/layouts/core/layout-section.jsx index b56e4c4..b7ad6dc 100644 --- a/front_minimal/src/layouts/core/layout-section.jsx +++ b/front_minimal/src/layouts/core/layout-section.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import GlobalStyles from '@mui/material/GlobalStyles'; diff --git a/front_minimal/src/layouts/dashboard/layout.jsx b/front_minimal/src/layouts/dashboard/layout.jsx index 8744d47..870c992 100644 --- a/front_minimal/src/layouts/dashboard/layout.jsx +++ b/front_minimal/src/layouts/dashboard/layout.jsx @@ -1,4 +1,3 @@ -'use client'; import { useMemo } from 'react'; @@ -42,14 +41,39 @@ export function DashboardLayout({ sx, children, data }) { const layoutQuery = 'lg'; - const navData = data?.nav ?? getNavData(user?.role); + // eslint-disable-next-line react-hooks/exhaustive-deps + const navData = useMemo(() => data?.nav ?? getNavData(user?.role), [data?.nav, user?.role]); const isNavMini = settings.navLayout === 'mini'; - - const isNavHorizontal = settings.navLayout === 'horizontal'; - + const isNavHorizontal = false; // горизонтальная навигация отключена const isNavVertical = isNavMini || settings.navLayout === 'vertical'; + const headerData = useMemo( + () => ({ + nav: navData, + langs: allLangs, + account: _account, + contacts: _contacts, + workspaces: _workspaces, + notifications: _notifications, + }), + [navData] + ); + + const headerSlotsDisplay = useMemo( + () => ({ + signIn: false, + purchase: false, + helpLink: false, + searchbar: false, + localization: false, + contacts: false, + workspaces: false, + account: false, + }), + [] + ); + return ( <> diff --git a/front_minimal/src/layouts/dashboard/main.jsx b/front_minimal/src/layouts/dashboard/main.jsx index b4f984e..9d31856 100644 --- a/front_minimal/src/layouts/dashboard/main.jsx +++ b/front_minimal/src/layouts/dashboard/main.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import { useTheme } from '@mui/material/styles'; @@ -6,8 +5,6 @@ import Container from '@mui/material/Container'; import { layoutClasses } from 'src/layouts/classes'; -import { useSettingsContext } from 'src/components/settings'; - // ---------------------------------------------------------------------- export function Main({ children, isNavHorizontal, sx, ...other }) { @@ -36,14 +33,12 @@ export function Main({ children, isNavHorizontal, sx, ...other }) { export function DashboardContent({ sx, children, disablePadding, maxWidth = 'lg', ...other }) { const theme = useTheme(); - const settings = useSettingsContext(); - const layoutQuery = 'lg'; return ( - - -); +root.render(); diff --git a/front_minimal/src/routes/components/router-link.jsx b/front_minimal/src/routes/components/router-link.jsx index 18dd053..a5d5095 100644 --- a/front_minimal/src/routes/components/router-link.jsx +++ b/front_minimal/src/routes/components/router-link.jsx @@ -1 +1,7 @@ -export { Link as RouterLink } from 'react-router-dom'; +import { forwardRef } from 'react'; +import { Link } from 'react-router-dom'; + +// Bridge component: MUI passes `href` prop, react-router-dom Link uses `to` +export const RouterLink = forwardRef(({ href, to, ...other }, ref) => ( + +)); diff --git a/front_minimal/src/routes/hooks/use-search-params.js b/front_minimal/src/routes/hooks/use-search-params.js index 7426398..0157ee5 100644 --- a/front_minimal/src/routes/hooks/use-search-params.js +++ b/front_minimal/src/routes/hooks/use-search-params.js @@ -1 +1,7 @@ -export { useSearchParams } from 'react-router-dom'; +import { useSearchParams as useRRSearchParams } from 'react-router-dom'; + +// Next.js-compatible wrapper: returns searchParams object directly (not a tuple) +export function useSearchParams() { + const [searchParams] = useRRSearchParams(); + return searchParams; +} diff --git a/front_minimal/src/routes/sections.jsx b/front_minimal/src/routes/sections.jsx index c0394e6..d7c17b4 100644 --- a/front_minimal/src/routes/sections.jsx +++ b/front_minimal/src/routes/sections.jsx @@ -7,7 +7,7 @@ import { GuestGuard } from 'src/auth/guard/guest-guard'; import { AuthSplitLayout } from 'src/layouts/auth-split'; import { DashboardLayout } from 'src/layouts/dashboard'; -import { SplashScreen } from 'src/components/loading-screen'; +import { LoadingScreen } from 'src/components/loading-screen'; // ---------------------------------------------------------------------- // Auth @@ -102,12 +102,8 @@ const Page404 = lazy(() => // ---------------------------------------------------------------------- -function Loading() { - return ; -} - function S({ children }) { - return }>{children}; + return }>{children}; } function DashboardLayoutWrapper() { diff --git a/front_minimal/src/sections/_examples/extra/animate-view/view.jsx b/front_minimal/src/sections/_examples/extra/animate-view/view.jsx index 212f11b..5cb5a11 100644 --- a/front_minimal/src/sections/_examples/extra/animate-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/animate-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; diff --git a/front_minimal/src/sections/_examples/extra/carousel-view/view.jsx b/front_minimal/src/sections/_examples/extra/carousel-view/view.jsx index 2f32200..ecd3e56 100644 --- a/front_minimal/src/sections/_examples/extra/carousel-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/carousel-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/chart-view/view.jsx b/front_minimal/src/sections/_examples/extra/chart-view/view.jsx index 130e271..eac78a4 100644 --- a/front_minimal/src/sections/_examples/extra/chart-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/chart-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/dnd-view/view.jsx b/front_minimal/src/sections/_examples/extra/dnd-view/view.jsx index c68521e..bc68770 100644 --- a/front_minimal/src/sections/_examples/extra/dnd-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/dnd-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/editor-view/view.jsx b/front_minimal/src/sections/_examples/extra/editor-view/view.jsx index e13248a..d671459 100644 --- a/front_minimal/src/sections/_examples/extra/editor-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/editor-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/extra/form-validation-view/view.jsx b/front_minimal/src/sections/_examples/extra/form-validation-view/view.jsx index f139e73..9d7cb58 100644 --- a/front_minimal/src/sections/_examples/extra/form-validation-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/form-validation-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/extra/form-wizard-view/view.jsx b/front_minimal/src/sections/_examples/extra/form-wizard-view/view.jsx index b1e7762..f035842 100644 --- a/front_minimal/src/sections/_examples/extra/form-wizard-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/form-wizard-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/image-view/view.jsx b/front_minimal/src/sections/_examples/extra/image-view/view.jsx index 59837e1..e7910a3 100644 --- a/front_minimal/src/sections/_examples/extra/image-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/image-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/_examples/extra/label-view/view.jsx b/front_minimal/src/sections/_examples/extra/label-view/view.jsx index 95888cd..09f9a60 100644 --- a/front_minimal/src/sections/_examples/extra/label-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/label-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Tooltip from '@mui/material/Tooltip'; diff --git a/front_minimal/src/sections/_examples/extra/lightbox-view/view.jsx b/front_minimal/src/sections/_examples/extra/lightbox-view/view.jsx index c122636..00553f0 100644 --- a/front_minimal/src/sections/_examples/extra/lightbox-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/lightbox-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/extra/map-view/view.jsx b/front_minimal/src/sections/_examples/extra/map-view/view.jsx index 88aef0b..c421f6e 100644 --- a/front_minimal/src/sections/_examples/extra/map-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/map-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import dynamic from 'next/dynamic'; diff --git a/front_minimal/src/sections/_examples/extra/markdown-view/view.jsx b/front_minimal/src/sections/_examples/extra/markdown-view/view.jsx index 4e733df..9331fac 100644 --- a/front_minimal/src/sections/_examples/extra/markdown-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/markdown-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/mega-menu-view/horizontal.jsx b/front_minimal/src/sections/_examples/extra/mega-menu-view/horizontal.jsx index 95d56be..ca59c7c 100644 --- a/front_minimal/src/sections/_examples/extra/mega-menu-view/horizontal.jsx +++ b/front_minimal/src/sections/_examples/extra/mega-menu-view/horizontal.jsx @@ -1,4 +1,3 @@ -'use client'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; diff --git a/front_minimal/src/sections/_examples/extra/mega-menu-view/mobile.jsx b/front_minimal/src/sections/_examples/extra/mega-menu-view/mobile.jsx index 1dbc7e9..8813e11 100644 --- a/front_minimal/src/sections/_examples/extra/mega-menu-view/mobile.jsx +++ b/front_minimal/src/sections/_examples/extra/mega-menu-view/mobile.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/sections/_examples/extra/mega-menu-view/view.jsx b/front_minimal/src/sections/_examples/extra/mega-menu-view/view.jsx index e525aaf..dfdec8e 100644 --- a/front_minimal/src/sections/_examples/extra/mega-menu-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/mega-menu-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/multi-language-view/view.jsx b/front_minimal/src/sections/_examples/extra/multi-language-view/view.jsx index ef5f6e4..e0fdaca 100644 --- a/front_minimal/src/sections/_examples/extra/multi-language-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/multi-language-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import dayjs from 'dayjs'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/extra/navigation-bar-view/view.jsx b/front_minimal/src/sections/_examples/extra/navigation-bar-view/view.jsx index d092c38..3336ce4 100644 --- a/front_minimal/src/sections/_examples/extra/navigation-bar-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/navigation-bar-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/organizational-chart-view/view.jsx b/front_minimal/src/sections/_examples/extra/organizational-chart-view/view.jsx index fd2222b..df0b99e 100644 --- a/front_minimal/src/sections/_examples/extra/organizational-chart-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/organizational-chart-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Stack from '@mui/material/Stack'; import { useTheme } from '@mui/material/styles'; diff --git a/front_minimal/src/sections/_examples/extra/scroll-progress-view/view.jsx b/front_minimal/src/sections/_examples/extra/scroll-progress-view/view.jsx index 3e3032a..9e8637f 100644 --- a/front_minimal/src/sections/_examples/extra/scroll-progress-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/scroll-progress-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; diff --git a/front_minimal/src/sections/_examples/extra/scrollbar-view/view.jsx b/front_minimal/src/sections/_examples/extra/scrollbar-view/view.jsx index 0ebabf4..f56192a 100644 --- a/front_minimal/src/sections/_examples/extra/scrollbar-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/scrollbar-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; diff --git a/front_minimal/src/sections/_examples/extra/snackbar-view/view.jsx b/front_minimal/src/sections/_examples/extra/snackbar-view/view.jsx index 620b73f..139a7f3 100644 --- a/front_minimal/src/sections/_examples/extra/snackbar-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/snackbar-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/sections/_examples/extra/upload-view/view.jsx b/front_minimal/src/sections/_examples/extra/upload-view/view.jsx index 0923250..9db6b3c 100644 --- a/front_minimal/src/sections/_examples/extra/upload-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/upload-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/extra/utilities-view/copy-to-clipboard.jsx b/front_minimal/src/sections/_examples/extra/utilities-view/copy-to-clipboard.jsx index c878302..e7b0b51 100644 --- a/front_minimal/src/sections/_examples/extra/utilities-view/copy-to-clipboard.jsx +++ b/front_minimal/src/sections/_examples/extra/utilities-view/copy-to-clipboard.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/extra/utilities-view/view.jsx b/front_minimal/src/sections/_examples/extra/utilities-view/view.jsx index 232d0b4..43b6aa5 100644 --- a/front_minimal/src/sections/_examples/extra/utilities-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/utilities-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/extra/walktour-view/view.jsx b/front_minimal/src/sections/_examples/extra/walktour-view/view.jsx index dcbd63c..3203084 100644 --- a/front_minimal/src/sections/_examples/extra/walktour-view/view.jsx +++ b/front_minimal/src/sections/_examples/extra/walktour-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/_examples/foundation/colors-view/view.jsx b/front_minimal/src/sections/_examples/foundation/colors-view/view.jsx index 8920881..5c22332 100644 --- a/front_minimal/src/sections/_examples/foundation/colors-view/view.jsx +++ b/front_minimal/src/sections/_examples/foundation/colors-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/_examples/foundation/grid-view/view.jsx b/front_minimal/src/sections/_examples/foundation/grid-view/view.jsx index 599e3b5..dddc2b4 100644 --- a/front_minimal/src/sections/_examples/foundation/grid-view/view.jsx +++ b/front_minimal/src/sections/_examples/foundation/grid-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/foundation/icons-view/view.jsx b/front_minimal/src/sections/_examples/foundation/icons-view/view.jsx index 1d0fdbd..f321b64 100644 --- a/front_minimal/src/sections/_examples/foundation/icons-view/view.jsx +++ b/front_minimal/src/sections/_examples/foundation/icons-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Link from '@mui/material/Link'; import Tooltip from '@mui/material/Tooltip'; diff --git a/front_minimal/src/sections/_examples/foundation/shadows-view/view.jsx b/front_minimal/src/sections/_examples/foundation/shadows-view/view.jsx index 2055b2e..2a69ca1 100644 --- a/front_minimal/src/sections/_examples/foundation/shadows-view/view.jsx +++ b/front_minimal/src/sections/_examples/foundation/shadows-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; diff --git a/front_minimal/src/sections/_examples/foundation/typography-view/view.jsx b/front_minimal/src/sections/_examples/foundation/typography-view/view.jsx index 5d77869..ea6b7a8 100644 --- a/front_minimal/src/sections/_examples/foundation/typography-view/view.jsx +++ b/front_minimal/src/sections/_examples/foundation/typography-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; diff --git a/front_minimal/src/sections/_examples/mui/accordion-view/view.jsx b/front_minimal/src/sections/_examples/mui/accordion-view/view.jsx index ab886af..36c8370 100644 --- a/front_minimal/src/sections/_examples/mui/accordion-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/accordion-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/alert-view/view.jsx b/front_minimal/src/sections/_examples/mui/alert-view/view.jsx index 80ac355..78f8a81 100644 --- a/front_minimal/src/sections/_examples/mui/alert-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/alert-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/sections/_examples/mui/autocomplete-view/view.jsx b/front_minimal/src/sections/_examples/mui/autocomplete-view/view.jsx index d11b0f6..14feb5b 100644 --- a/front_minimal/src/sections/_examples/mui/autocomplete-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/autocomplete-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/avatar-view/view.jsx b/front_minimal/src/sections/_examples/mui/avatar-view/view.jsx index e863d05..d785d0b 100644 --- a/front_minimal/src/sections/_examples/mui/avatar-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/avatar-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Badge from '@mui/material/Badge'; import Avatar from '@mui/material/Avatar'; diff --git a/front_minimal/src/sections/_examples/mui/badge-view/view.jsx b/front_minimal/src/sections/_examples/mui/badge-view/view.jsx index c3ea701..8d8fbc6 100644 --- a/front_minimal/src/sections/_examples/mui/badge-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/badge-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Badge from '@mui/material/Badge'; diff --git a/front_minimal/src/sections/_examples/mui/breadcrumbs-view/view.jsx b/front_minimal/src/sections/_examples/mui/breadcrumbs-view/view.jsx index cdafae0..c40b92a 100644 --- a/front_minimal/src/sections/_examples/mui/breadcrumbs-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/breadcrumbs-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Link from '@mui/material/Link'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/_examples/mui/button-view/view.jsx b/front_minimal/src/sections/_examples/mui/button-view/view.jsx index 1e3f364..64c8b50 100644 --- a/front_minimal/src/sections/_examples/mui/button-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/button-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/checkbox-view/view.jsx b/front_minimal/src/sections/_examples/mui/checkbox-view/view.jsx index 909ac47..5ec8371 100644 --- a/front_minimal/src/sections/_examples/mui/checkbox-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/checkbox-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/chip-view/view.jsx b/front_minimal/src/sections/_examples/mui/chip-view/view.jsx index e90d551..e564ed1 100644 --- a/front_minimal/src/sections/_examples/mui/chip-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/chip-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/data-grid-view/view.jsx b/front_minimal/src/sections/_examples/mui/data-grid-view/view.jsx index 847e59c..60893b2 100644 --- a/front_minimal/src/sections/_examples/mui/data-grid-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/data-grid-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Link from '@mui/material/Link'; diff --git a/front_minimal/src/sections/_examples/mui/dialog-view/view.jsx b/front_minimal/src/sections/_examples/mui/dialog-view/view.jsx index b9e042b..5eb785a 100644 --- a/front_minimal/src/sections/_examples/mui/dialog-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/dialog-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/list-view/view.jsx b/front_minimal/src/sections/_examples/mui/list-view/view.jsx index 380ec37..7775d9d 100644 --- a/front_minimal/src/sections/_examples/mui/list-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/list-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/menu-view/view.jsx b/front_minimal/src/sections/_examples/mui/menu-view/view.jsx index 39721df..9b0a090 100644 --- a/front_minimal/src/sections/_examples/mui/menu-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/menu-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/pagination-view/view.jsx b/front_minimal/src/sections/_examples/mui/pagination-view/view.jsx index 66cd70a..1e76251 100644 --- a/front_minimal/src/sections/_examples/mui/pagination-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/pagination-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/picker-view/view.jsx b/front_minimal/src/sections/_examples/mui/picker-view/view.jsx index d04f168..48efc22 100644 --- a/front_minimal/src/sections/_examples/mui/picker-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/picker-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/popover-view/view.jsx b/front_minimal/src/sections/_examples/mui/popover-view/view.jsx index 1ce0628..c91a59a 100644 --- a/front_minimal/src/sections/_examples/mui/popover-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/popover-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useRef, useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/progress-view/view.jsx b/front_minimal/src/sections/_examples/mui/progress-view/view.jsx index a70c97e..518e1ef 100644 --- a/front_minimal/src/sections/_examples/mui/progress-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/progress-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useRef, useState, useEffect } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/radio-button-view/view.jsx b/front_minimal/src/sections/_examples/mui/radio-button-view/view.jsx index bfea7a9..ca512c1 100644 --- a/front_minimal/src/sections/_examples/mui/radio-button-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/radio-button-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/rating-view/view.jsx b/front_minimal/src/sections/_examples/mui/rating-view/view.jsx index cd40fd4..f59bed6 100644 --- a/front_minimal/src/sections/_examples/mui/rating-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/rating-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/slider-view/view.jsx b/front_minimal/src/sections/_examples/mui/slider-view/view.jsx index a0b68dd..91da144 100644 --- a/front_minimal/src/sections/_examples/mui/slider-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/slider-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/stepper-view/view.jsx b/front_minimal/src/sections/_examples/mui/stepper-view/view.jsx index 9b2d764..bb74351 100644 --- a/front_minimal/src/sections/_examples/mui/stepper-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/stepper-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/switch-view/view.jsx b/front_minimal/src/sections/_examples/mui/switch-view/view.jsx index a36df5f..b7a5ae1 100644 --- a/front_minimal/src/sections/_examples/mui/switch-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/switch-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Switch from '@mui/material/Switch'; import FormGroup from '@mui/material/FormGroup'; diff --git a/front_minimal/src/sections/_examples/mui/table-view/view.jsx b/front_minimal/src/sections/_examples/mui/table-view/view.jsx index 0869a34..252ae36 100644 --- a/front_minimal/src/sections/_examples/mui/table-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/table-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/tabs-view/view.jsx b/front_minimal/src/sections/_examples/mui/tabs-view/view.jsx index ed15b6e..7c94b20 100644 --- a/front_minimal/src/sections/_examples/mui/tabs-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/tabs-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { Fragment } from 'react'; diff --git a/front_minimal/src/sections/_examples/mui/textfield-view/view.jsx b/front_minimal/src/sections/_examples/mui/textfield-view/view.jsx index 4eb4f0e..de088d0 100644 --- a/front_minimal/src/sections/_examples/mui/textfield-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/textfield-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/timeline-view/view.jsx b/front_minimal/src/sections/_examples/mui/timeline-view/view.jsx index a1cb3cf..5e97a81 100644 --- a/front_minimal/src/sections/_examples/mui/timeline-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/timeline-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Paper from '@mui/material/Paper'; import Timeline from '@mui/lab/Timeline'; diff --git a/front_minimal/src/sections/_examples/mui/tooltip-view/view.jsx b/front_minimal/src/sections/_examples/mui/tooltip-view/view.jsx index 386c613..cb621e6 100644 --- a/front_minimal/src/sections/_examples/mui/tooltip-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/tooltip-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/sections/_examples/mui/transfer-list-view/view.jsx b/front_minimal/src/sections/_examples/mui/transfer-list-view/view.jsx index 153cfdf..8c0135b 100644 --- a/front_minimal/src/sections/_examples/mui/transfer-list-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/transfer-list-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/mui/tree-view/view.jsx b/front_minimal/src/sections/_examples/mui/tree-view/view.jsx index 19fc4d7..a4ed231 100644 --- a/front_minimal/src/sections/_examples/mui/tree-view/view.jsx +++ b/front_minimal/src/sections/_examples/mui/tree-view/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/_examples/view.jsx b/front_minimal/src/sections/_examples/view.jsx index c17c571..ee03c6c 100644 --- a/front_minimal/src/sections/_examples/view.jsx +++ b/front_minimal/src/sections/_examples/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/sections/about/view/about-view.jsx b/front_minimal/src/sections/about/view/about-view.jsx index 35f7f1e..02b4a96 100644 --- a/front_minimal/src/sections/about/view/about-view.jsx +++ b/front_minimal/src/sections/about/view/about-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { AboutHero } from '../about-hero'; import { AboutWhat } from '../about-what'; diff --git a/front_minimal/src/sections/account/view/user-account-view.jsx b/front_minimal/src/sections/account/view/user-account-view.jsx index 26c3b8e..48e4a03 100644 --- a/front_minimal/src/sections/account/view/user-account-view.jsx +++ b/front_minimal/src/sections/account/view/user-account-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; diff --git a/front_minimal/src/sections/auth-demo/centered/centered-reset-password-view.jsx b/front_minimal/src/sections/auth-demo/centered/centered-reset-password-view.jsx index cf45b08..da23543 100644 --- a/front_minimal/src/sections/auth-demo/centered/centered-reset-password-view.jsx +++ b/front_minimal/src/sections/auth-demo/centered/centered-reset-password-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/centered/centered-sign-in-view.jsx b/front_minimal/src/sections/auth-demo/centered/centered-sign-in-view.jsx index 622e26e..53e5107 100644 --- a/front_minimal/src/sections/auth-demo/centered/centered-sign-in-view.jsx +++ b/front_minimal/src/sections/auth-demo/centered/centered-sign-in-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/centered/centered-sign-up-view.jsx b/front_minimal/src/sections/auth-demo/centered/centered-sign-up-view.jsx index 8b980e9..76069d2 100644 --- a/front_minimal/src/sections/auth-demo/centered/centered-sign-up-view.jsx +++ b/front_minimal/src/sections/auth-demo/centered/centered-sign-up-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/centered/centered-update-password-view.jsx b/front_minimal/src/sections/auth-demo/centered/centered-update-password-view.jsx index 7cc3a5c..7fa674c 100644 --- a/front_minimal/src/sections/auth-demo/centered/centered-update-password-view.jsx +++ b/front_minimal/src/sections/auth-demo/centered/centered-update-password-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/centered/centered-verify-view.jsx b/front_minimal/src/sections/auth-demo/centered/centered-verify-view.jsx index e568a1e..21dda86 100644 --- a/front_minimal/src/sections/auth-demo/centered/centered-verify-view.jsx +++ b/front_minimal/src/sections/auth-demo/centered/centered-verify-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/split/split-reset-password-view.jsx b/front_minimal/src/sections/auth-demo/split/split-reset-password-view.jsx index 01bc19a..2e31786 100644 --- a/front_minimal/src/sections/auth-demo/split/split-reset-password-view.jsx +++ b/front_minimal/src/sections/auth-demo/split/split-reset-password-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/split/split-sign-in-view.jsx b/front_minimal/src/sections/auth-demo/split/split-sign-in-view.jsx index 5b71de1..bd7a2b9 100644 --- a/front_minimal/src/sections/auth-demo/split/split-sign-in-view.jsx +++ b/front_minimal/src/sections/auth-demo/split/split-sign-in-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/split/split-sign-up-view.jsx b/front_minimal/src/sections/auth-demo/split/split-sign-up-view.jsx index d52c012..74c7a63 100644 --- a/front_minimal/src/sections/auth-demo/split/split-sign-up-view.jsx +++ b/front_minimal/src/sections/auth-demo/split/split-sign-up-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/split/split-update-password-view.jsx b/front_minimal/src/sections/auth-demo/split/split-update-password-view.jsx index 47e5f1c..a247f12 100644 --- a/front_minimal/src/sections/auth-demo/split/split-update-password-view.jsx +++ b/front_minimal/src/sections/auth-demo/split/split-update-password-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth-demo/split/split-verify-view.jsx b/front_minimal/src/sections/auth-demo/split/split-verify-view.jsx index 26fe9e0..360ac53 100644 --- a/front_minimal/src/sections/auth-demo/split/split-verify-view.jsx +++ b/front_minimal/src/sections/auth-demo/split/split-verify-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { z as zod } from 'zod'; import { useForm } from 'react-hook-form'; diff --git a/front_minimal/src/sections/auth/jwt/jwt-reset-password-view.jsx b/front_minimal/src/sections/auth/jwt/jwt-reset-password-view.jsx index 7dcab21..d7134ff 100644 --- a/front_minimal/src/sections/auth/jwt/jwt-reset-password-view.jsx +++ b/front_minimal/src/sections/auth/jwt/jwt-reset-password-view.jsx @@ -4,8 +4,6 @@ import { z as zod } from 'zod'; import { useState, Suspense } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useSearchParams } from 'react-router-dom'; - import Link from '@mui/material/Link'; import Alert from '@mui/material/Alert'; import Stack from '@mui/material/Stack'; @@ -15,7 +13,7 @@ import LoadingButton from '@mui/lab/LoadingButton'; import InputAdornment from '@mui/material/InputAdornment'; import { paths } from 'src/routes/paths'; -import { useRouter } from 'src/routes/hooks'; +import { useRouter, useSearchParams } from 'src/routes/hooks'; import { RouterLink } from 'src/routes/components'; import { useBoolean } from 'src/hooks/use-boolean'; diff --git a/front_minimal/src/sections/auth/jwt/jwt-sign-in-view.jsx b/front_minimal/src/sections/auth/jwt/jwt-sign-in-view.jsx index 7db6236..a5fb65a 100644 --- a/front_minimal/src/sections/auth/jwt/jwt-sign-in-view.jsx +++ b/front_minimal/src/sections/auth/jwt/jwt-sign-in-view.jsx @@ -13,7 +13,7 @@ import LoadingButton from '@mui/lab/LoadingButton'; import InputAdornment from '@mui/material/InputAdornment'; import { paths } from 'src/routes/paths'; -import { useRouter } from 'src/routes/hooks'; +import { useRouter, useSearchParams } from 'src/routes/hooks'; import { RouterLink } from 'src/routes/components'; import { useBoolean } from 'src/hooks/use-boolean'; @@ -41,6 +41,7 @@ export const SignInSchema = zod.object({ export function JwtSignInView() { const router = useRouter(); + const searchParams = useSearchParams(); const { checkUserSession } = useAuthContext(); @@ -48,9 +49,11 @@ export function JwtSignInView() { const password = useBoolean(); + const returnTo = searchParams.get('returnTo') || paths.dashboard.root; + const defaultValues = { - email: 'mentor@demo.uchill.online', - password: 'demo123456', + email: '', + password: '', }; const methods = useForm({ @@ -67,8 +70,7 @@ export function JwtSignInView() { try { await signInWithPassword({ email: data.email, password: data.password }); await checkUserSession?.(); - - router.refresh(); + router.replace(returnTo); } catch (error) { console.error(error); setErrorMsg(error instanceof Error ? error.message : error); @@ -77,15 +79,15 @@ export function JwtSignInView() { const renderHead = ( - Sign in to your account + Войти в аккаунт - {`Don't have an account?`} + Нет аккаунта? - Get started + Зарегистрироваться @@ -93,7 +95,7 @@ export function JwtSignInView() { const renderForm = ( - + - Forgot password? + Забыли пароль? - Sign in + Войти ); @@ -142,12 +144,6 @@ export function JwtSignInView() { <> {renderHead} - - Use {defaultValues.email} - {' with password '} - {defaultValues.password} - - {!!errorMsg && ( {errorMsg} diff --git a/front_minimal/src/sections/auth/jwt/jwt-sign-up-view.jsx b/front_minimal/src/sections/auth/jwt/jwt-sign-up-view.jsx index e215824..e9a23f9 100644 --- a/front_minimal/src/sections/auth/jwt/jwt-sign-up-view.jsx +++ b/front_minimal/src/sections/auth/jwt/jwt-sign-up-view.jsx @@ -1,7 +1,7 @@ import { z as zod } from 'zod'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; +import { useState, useCallback, useEffect, useRef } from 'react'; +import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import Link from '@mui/material/Link'; @@ -10,8 +10,11 @@ import Stack from '@mui/material/Stack'; import MenuItem from '@mui/material/MenuItem'; import Checkbox from '@mui/material/Checkbox'; import IconButton from '@mui/material/IconButton'; +import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; +import Autocomplete from '@mui/material/Autocomplete'; import FormControlLabel from '@mui/material/FormControlLabel'; +import CircularProgress from '@mui/material/CircularProgress'; import LoadingButton from '@mui/lab/LoadingButton'; import InputAdornment from '@mui/material/InputAdornment'; @@ -26,32 +29,95 @@ import { Form, Field } from 'src/components/hook-form'; import { signUp } from 'src/auth/context/jwt'; import { useAuthContext } from 'src/auth/hooks'; +import { searchCities } from 'src/utils/profile-api'; // ---------------------------------------------------------------------- export const SignUpSchema = zod .object({ - firstName: zod.string().min(1, { message: 'First name is required!' }), - lastName: zod.string().min(1, { message: 'Last name is required!' }), + firstName: zod.string().min(1, { message: 'Введите имя!' }), + lastName: zod.string().min(1, { message: 'Введите фамилию!' }), email: zod .string() - .min(1, { message: 'Email is required!' }) - .email({ message: 'Email must be a valid email address!' }), - role: zod.enum(['mentor', 'client', 'parent'], { message: 'Role is required!' }), - city: zod.string().min(1, { message: 'City is required!' }), + .min(1, { message: 'Введите email!' }) + .email({ message: 'Введите корректный email!' }), + role: zod.enum(['mentor', 'client', 'parent'], { message: 'Выберите роль!' }), + city: zod.string().min(1, { message: 'Введите город!' }), password: zod .string() - .min(1, { message: 'Password is required!' }) - .min(6, { message: 'Password must be at least 6 characters!' }), - passwordConfirm: zod.string().min(1, { message: 'Please confirm your password!' }), + .min(1, { message: 'Введите пароль!' }) + .min(8, { message: 'Пароль должен содержать минимум 8 символов!' }), + passwordConfirm: zod.string().min(1, { message: 'Подтвердите пароль!' }), }) .refine((data) => data.password === data.passwordConfirm, { - message: 'Passwords do not match!', + message: 'Пароли не совпадают!', path: ['passwordConfirm'], }); // ---------------------------------------------------------------------- +function CityAutocomplete({ value, onChange, error, helperText }) { + const [inputValue, setInputValue] = useState(value || ''); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + const timerRef = useRef(null); + + const fetch = useCallback(async (query) => { + if (!query || query.length < 2) { setOptions([]); return; } + setLoading(true); + try { + const res = await searchCities(query, 30); + setOptions(res.map((c) => (typeof c === 'string' ? c : c.name || c.city || String(c)))); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => fetch(inputValue), 350); + return () => clearTimeout(timerRef.current); + }, [inputValue, fetch]); + + return ( + { + setInputValue(val); + onChange(val); + }} + onChange={(_, val) => { + if (val) { setInputValue(val); onChange(val); } + }} + noOptionsText="Города не найдены" + loadingText="Поиск..." + renderInput={(params) => ( + + {loading ? : null} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + ); +} + +// ---------------------------------------------------------------------- + export function JwtSignUpView() { const { checkUserSession } = useAuthContext(); const router = useRouter(); @@ -79,13 +145,14 @@ export function JwtSignUpView() { }); const { + control, handleSubmit, formState: { isSubmitting }, } = methods; const onSubmit = handleSubmit(async (data) => { if (!consent) { - setErrorMsg('Please agree to the Terms of Service and Privacy Policy.'); + setErrorMsg('Необходимо согласиться с условиями использования и политикой конфиденциальности.'); return; } @@ -102,31 +169,31 @@ export function JwtSignUpView() { if (result?.requiresVerification) { setSuccessMsg( - `A confirmation email has been sent to ${data.email}. Please follow the link to verify your account.` + `Письмо с подтверждением отправлено на ${data.email}. Пройдите по ссылке в письме для активации аккаунта.` ); return; } await checkUserSession?.(); - router.refresh(); + router.replace(paths.dashboard.root); } catch (error) { console.error(error); - const msg = error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Registration error. Please check your data.'); + const msg = error?.response?.data?.message || error?.response?.data?.detail || (error instanceof Error ? error.message : 'Ошибка регистрации. Проверьте введённые данные.'); setErrorMsg(msg); } }); const renderHead = ( - Get started absolutely free + Создать аккаунт - Already have an account? + Уже есть аккаунт? - Sign in + Войти @@ -139,7 +206,7 @@ export function JwtSignUpView() { {successMsg} - Back to sign in + Вернуться ко входу @@ -149,24 +216,35 @@ export function JwtSignUpView() { const renderForm = ( - - + + - + - - Student - Mentor - Parent + + Ученик + Ментор + Родитель - + ( + + )} + /> setConsent(e.target.checked)} />} label={ - I agree to the{' '} + Я соглашаюсь с{' '} - Terms of service + Условиями использования {' '} - and{' '} + и{' '} - Privacy policy + Политикой конфиденциальности } @@ -220,9 +298,9 @@ export function JwtSignUpView() { type="submit" variant="contained" loading={isSubmitting} - loadingIndicator="Creating account..." + loadingIndicator="Создание аккаунта..." > - Create account + Зарегистрироваться ); diff --git a/front_minimal/src/sections/auth/jwt/jwt-verify-email-view.jsx b/front_minimal/src/sections/auth/jwt/jwt-verify-email-view.jsx index 627b9d6..8b686dd 100644 --- a/front_minimal/src/sections/auth/jwt/jwt-verify-email-view.jsx +++ b/front_minimal/src/sections/auth/jwt/jwt-verify-email-view.jsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect, Suspense } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'src/routes/hooks'; import Link from '@mui/material/Link'; import Alert from '@mui/material/Alert'; diff --git a/front_minimal/src/sections/blank/view.jsx b/front_minimal/src/sections/blank/view.jsx index e9b39ae..8ca99f1 100644 --- a/front_minimal/src/sections/blank/view.jsx +++ b/front_minimal/src/sections/blank/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; diff --git a/front_minimal/src/sections/blog/view/post-create-view.jsx b/front_minimal/src/sections/blog/view/post-create-view.jsx index 8a7cbe3..3d0eaf0 100644 --- a/front_minimal/src/sections/blog/view/post-create-view.jsx +++ b/front_minimal/src/sections/blog/view/post-create-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/blog/view/post-details-home-view.jsx b/front_minimal/src/sections/blog/view/post-details-home-view.jsx index 31f43e0..8cc9e33 100644 --- a/front_minimal/src/sections/blog/view/post-details-home-view.jsx +++ b/front_minimal/src/sections/blog/view/post-details-home-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Chip from '@mui/material/Chip'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/blog/view/post-details-view.jsx b/front_minimal/src/sections/blog/view/post-details-view.jsx index 58f5858..4b57893 100644 --- a/front_minimal/src/sections/blog/view/post-details-view.jsx +++ b/front_minimal/src/sections/blog/view/post-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/sections/blog/view/post-edit-view.jsx b/front_minimal/src/sections/blog/view/post-edit-view.jsx index e65b881..631e53c 100644 --- a/front_minimal/src/sections/blog/view/post-edit-view.jsx +++ b/front_minimal/src/sections/blog/view/post-edit-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/blog/view/post-list-home-view.jsx b/front_minimal/src/sections/blog/view/post-list-home-view.jsx index 6c05068..654012a 100644 --- a/front_minimal/src/sections/blog/view/post-list-home-view.jsx +++ b/front_minimal/src/sections/blog/view/post-list-home-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/blog/view/post-list-view.jsx b/front_minimal/src/sections/blog/view/post-list-view.jsx index 684caf2..9e613d6 100644 --- a/front_minimal/src/sections/blog/view/post-list-view.jsx +++ b/front_minimal/src/sections/blog/view/post-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/board/view/board-view.jsx b/front_minimal/src/sections/board/view/board-view.jsx index eba18fc..7054c45 100644 --- a/front_minimal/src/sections/board/view/board-view.jsx +++ b/front_minimal/src/sections/board/view/board-view.jsx @@ -1,5 +1,5 @@ import { useRef, useEffect, useState, useCallback } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'src/routes/hooks'; import { useRouter } from 'src/routes/hooks'; diff --git a/front_minimal/src/sections/calendar/view/calendar-view.jsx b/front_minimal/src/sections/calendar/view/calendar-view.jsx index e881785..8ce1d78 100644 --- a/front_minimal/src/sections/calendar/view/calendar-view.jsx +++ b/front_minimal/src/sections/calendar/view/calendar-view.jsx @@ -1,4 +1,3 @@ -'use client'; import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; diff --git a/front_minimal/src/sections/chat/view/chat-platform-view.jsx b/front_minimal/src/sections/chat/view/chat-platform-view.jsx index 47a4666..c425c60 100644 --- a/front_minimal/src/sections/chat/view/chat-platform-view.jsx +++ b/front_minimal/src/sections/chat/view/chat-platform-view.jsx @@ -34,8 +34,11 @@ import { getConversations, markMessagesAsRead, getChatMessagesByUuid, + normalizeChat, } from 'src/utils/chat-api'; +import { getStudents, getMyMentors } from 'src/utils/students-api'; + import { DashboardContent } from 'src/layouts/dashboard'; import { Iconify } from 'src/components/iconify'; @@ -121,12 +124,21 @@ function NewChatDialog({ open, onClose, onCreated }) { return () => clearTimeout(timer); }, [query, handleSearch]); - const handleCreate = async (userId) => { + const handleCreate = async (user) => { try { setCreating(true); setError(null); - const chat = await createChat(userId); - onCreated(chat); + const rawChat = await createChat(user.id); + const enriched = { + ...rawChat, + other_participant: rawChat?.other_participant ?? { + id: user.id, + first_name: user.first_name, + last_name: user.last_name, + avatar_url: user.avatar_url || user.avatar || null, + }, + }; + onCreated(enriched); onClose(); } catch (e) { setError(e?.response?.data?.detail || e?.message || 'Ошибка создания чата'); @@ -172,7 +184,7 @@ function NewChatDialog({ open, onClose, onCreated }) { return ( handleCreate(u.id)} + onClick={() => handleCreate(u)} disabled={creating} sx={{ borderRadius: 1 }} > @@ -197,10 +209,13 @@ function NewChatDialog({ open, onClose, onCreated }) { // ---------------------------------------------------------------------- -function ChatList({ chats, selectedUuid, onSelect, onNew, loading }) { +function ChatList({ chats, selectedUuid, onSelect, onNew, loading, contacts, onStartChat, startingId }) { const [q, setQ] = useState(''); - const filtered = chats.filter((c) => { + // ID пользователей у которых уже есть чат + const existingParticipantIds = new Set(chats.map((c) => c.participant_id).filter(Boolean)); + + const filteredChats = chats.filter((c) => { if (!q.trim()) return true; const qq = q.toLowerCase(); return ( @@ -209,6 +224,14 @@ function ChatList({ chats, selectedUuid, onSelect, onNew, loading }) { ); }); + const filteredContacts = contacts.filter((c) => { + if (existingParticipantIds.has(c.id)) return false; + if (!q.trim()) return true; + const qq = q.toLowerCase(); + const name = `${c.first_name || ''} ${c.last_name || ''}`.toLowerCase(); + return name.includes(qq) || (c.email || '').toLowerCase().includes(qq); + }); + return ( - ) : filtered.length === 0 ? ( - - {q ? 'Не найдено' : 'Нет чатов'} - ) : ( - - {filtered.map((chat) => { - const selected = !!selectedUuid && chat.uuid === selectedUuid; - return ( - onSelect(chat)} - sx={{ py: 1.25, px: 1.5 }} + <> + {/* Существующие чаты */} + {filteredChats.length > 0 && ( + + {filteredChats.map((chat) => { + const selected = !!selectedUuid && chat.uuid === selectedUuid; + return ( + onSelect(chat)} + sx={{ py: 1.25, px: 1.5 }} + > + + {getInitials(chat.participant_name)} + + + + {chat.participant_name || 'Чат'} + + {!!chat.unread_count && ( + + )} + + } + secondary={ + + {stripHtml(chat.last_message || '')} + + } + primaryTypographyProps={{ component: 'div' }} + secondaryTypographyProps={{ component: 'div' }} + /> + + ); + })} + + )} + + {/* Контакты без чата */} + {filteredContacts.length > 0 && ( + <> + 0 ? 1.5 : 1, pb: 0.5, display: 'block', color: 'text.disabled' }} > - - {getInitials(chat.participant_name)} - - - - {chat.participant_name || 'Чат'} - - {!!chat.unread_count && ( - + Контакты + + + {filteredContacts.map((contact) => { + const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim() || contact.email || '—'; + const isStarting = startingId === contact.id; + return ( + onStartChat(contact)} + disabled={isStarting} + sx={{ py: 1.25, px: 1.5, opacity: isStarting ? 0.6 : 1 }} + > + + {getInitials(name)} + + + {name} + + } + secondary={ + + {isStarting ? 'Создание чата...' : 'Начать диалог'} + + } + primaryTypographyProps={{ component: 'div' }} + secondaryTypographyProps={{ component: 'div' }} + /> + {isStarting ? ( + + ) : ( + )} - - } - secondary={ - - {stripHtml(chat.last_message || '')} - - } - primaryTypographyProps={{ component: 'div' }} - secondaryTypographyProps={{ component: 'div' }} - /> - - ); - })} - + + ); + })} + + + )} + + {filteredChats.length === 0 && filteredContacts.length === 0 && ( + + + + {q ? 'Ничего не найдено' : 'Нет чатов и контактов'} + + + )} + )} @@ -307,6 +389,7 @@ function ChatWindow({ chat, currentUserId, onBack }) { const [hasMore, setHasMore] = useState(false); const [text, setText] = useState(''); const [sending, setSending] = useState(false); + const [sendError, setSendError] = useState(null); const listRef = useRef(null); const markedRef = useRef(new Set()); const lastSentRef = useRef(null); @@ -447,6 +530,7 @@ function ChatWindow({ chat, currentUserId, onBack }) { if (!chat || !text.trim() || sending) return; const content = text.trim(); setText(''); + setSendError(null); setSending(true); try { const msg = await sendMessage(chat.id, content); @@ -459,8 +543,10 @@ function ChatWindow({ chat, currentUserId, onBack }) { if (el) el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }); }); }); - } catch { + } catch (e) { setText(content); + const errMsg = e?.response?.data?.error?.message || e?.response?.data?.detail || e?.message || 'Ошибка отправки'; + setSendError(errMsg); } finally { setSending(false); } @@ -620,6 +706,11 @@ function ChatWindow({ chat, currentUserId, onBack }) { {/* Input */} + {sendError && ( + setSendError(null)} sx={{ mx: 1.5, mt: 1, borderRadius: 1 }}> + {sendError} + + )} { try { - const res = await getConversations({ page_size: 50 }); - setChats(res.results); + setLoading(true); + const [chatsRes, contactsRes] = await Promise.allSettled([ + getConversations({ page_size: 100 }), + user?.role === 'mentor' + ? getStudents({ page_size: 100 }).then((r) => r.results.map((s) => ({ ...s.user, id: s.user?.id })).filter(Boolean)) + : getMyMentors().then((list) => (Array.isArray(list) ? list : [])), + ]); + + if (chatsRes.status === 'fulfilled') setChats(chatsRes.value.results ?? []); + if (contactsRes.status === 'fulfilled') setContacts(contactsRes.value ?? []); } catch (e) { - setError(e?.response?.data?.detail || e?.message || 'Ошибка загрузки чатов'); + setError(e?.response?.data?.detail || e?.message || 'Ошибка загрузки'); } finally { setLoading(false); } - }, []); + }, [user?.role]); useEffect(() => { load(); }, [load]); - const handleChatCreated = (chat) => { + const handleChatCreated = useCallback((rawChat) => { + const chat = normalizeChat(rawChat); setChats((prev) => { const exists = prev.some((c) => c.uuid === chat.uuid || c.id === chat.id); if (exists) return prev; return [chat, ...prev]; }); setSelectedChat(chat); - }; + }, []); + + const handleStartChat = useCallback(async (contact) => { + const existing = chats.find((c) => c.participant_id === contact.id); + if (existing) { setSelectedChat(existing); return; } + try { + setStartingId(contact.id); + const rawChat = await createChat(contact.id); + // Если бэкенд не вернул other_participant — заполняем из данных контакта, + // чтобы normalizeChat правильно выставил participant_id и имя + const enriched = { + ...rawChat, + other_participant: rawChat?.other_participant ?? { + id: contact.id, + first_name: contact.first_name, + last_name: contact.last_name, + avatar_url: contact.avatar_url || contact.avatar || null, + }, + }; + handleChatCreated(enriched); + } catch (e) { + setError(e?.response?.data?.detail || e?.message || 'Ошибка создания чата'); + } finally { + setStartingId(null); + } + }, [chats, handleChatCreated]); return ( @@ -704,9 +831,12 @@ export function ChatPlatformView() { setNewChatOpen(true)} + onStartChat={handleStartChat} + startingId={startingId} loading={loading} /> diff --git a/front_minimal/src/sections/chat/view/chat-view.jsx b/front_minimal/src/sections/chat/view/chat-view.jsx index 0e057b3..959fabf 100644 --- a/front_minimal/src/sections/chat/view/chat-view.jsx +++ b/front_minimal/src/sections/chat/view/chat-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/sections/checkout/context/checkout-provider.jsx b/front_minimal/src/sections/checkout/context/checkout-provider.jsx index 768f4ff..79804ba 100644 --- a/front_minimal/src/sections/checkout/context/checkout-provider.jsx +++ b/front_minimal/src/sections/checkout/context/checkout-provider.jsx @@ -1,4 +1,3 @@ -'use client'; import { useMemo, Suspense, useEffect, useCallback, createContext } from 'react'; diff --git a/front_minimal/src/sections/checkout/context/use-checkout-context.jsx b/front_minimal/src/sections/checkout/context/use-checkout-context.jsx index 004436b..1fbe110 100644 --- a/front_minimal/src/sections/checkout/context/use-checkout-context.jsx +++ b/front_minimal/src/sections/checkout/context/use-checkout-context.jsx @@ -1,4 +1,3 @@ -'use client'; import { useContext } from 'react'; diff --git a/front_minimal/src/sections/checkout/view/checkout-view.jsx b/front_minimal/src/sections/checkout/view/checkout-view.jsx index 3ad0874..5e00bb4 100644 --- a/front_minimal/src/sections/checkout/view/checkout-view.jsx +++ b/front_minimal/src/sections/checkout/view/checkout-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useEffect } from 'react'; diff --git a/front_minimal/src/sections/children/view/children-progress-view.jsx b/front_minimal/src/sections/children/view/children-progress-view.jsx index 83fcfac..942d245 100644 --- a/front_minimal/src/sections/children/view/children-progress-view.jsx +++ b/front_minimal/src/sections/children/view/children-progress-view.jsx @@ -1,8 +1,7 @@ 'use client'; -import { useState, useEffect, useCallback } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { useRouter } from 'src/routes/hooks'; +import { useState, useEffect, useCallback } from 'react'; +import { useRouter, useSearchParams } from 'src/routes/hooks'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; @@ -10,14 +9,14 @@ import Stack from '@mui/material/Stack'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import CardContent from '@mui/material/CardContent'; +import CardContent from '@mui/material/CardContent'; import CircularProgress from '@mui/material/CircularProgress'; import { paths } from 'src/routes/paths'; import axios from 'src/utils/axios'; -import { DashboardContent } from 'src/layouts/dashboard'; +import { DashboardContent } from 'src/layouts/dashboard'; import { Iconify } from 'src/components/iconify'; import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs'; diff --git a/front_minimal/src/sections/coming-soon/view.jsx b/front_minimal/src/sections/coming-soon/view.jsx index d68fac1..19dd42e 100644 --- a/front_minimal/src/sections/coming-soon/view.jsx +++ b/front_minimal/src/sections/coming-soon/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/contact/view/contact-view.jsx b/front_minimal/src/sections/contact/view/contact-view.jsx index 0aa06c3..0acb592 100644 --- a/front_minimal/src/sections/contact/view/contact-view.jsx +++ b/front_minimal/src/sections/contact/view/contact-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Container from '@mui/material/Container'; diff --git a/front_minimal/src/sections/error/403-view.jsx b/front_minimal/src/sections/error/403-view.jsx index 3ef7917..eaeb3b6 100644 --- a/front_minimal/src/sections/error/403-view.jsx +++ b/front_minimal/src/sections/error/403-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/sections/error/500-view.jsx b/front_minimal/src/sections/error/500-view.jsx index e16dbd6..06b1e35 100644 --- a/front_minimal/src/sections/error/500-view.jsx +++ b/front_minimal/src/sections/error/500-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/sections/error/not-found-view.jsx b/front_minimal/src/sections/error/not-found-view.jsx index 80e31e4..80e8e2d 100644 --- a/front_minimal/src/sections/error/not-found-view.jsx +++ b/front_minimal/src/sections/error/not-found-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { m } from 'framer-motion'; diff --git a/front_minimal/src/sections/faqs/view/faqs-view.jsx b/front_minimal/src/sections/faqs/view/faqs-view.jsx index 91c8cf0..0741726 100644 --- a/front_minimal/src/sections/faqs/view/faqs-view.jsx +++ b/front_minimal/src/sections/faqs/view/faqs-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Container from '@mui/material/Container'; diff --git a/front_minimal/src/sections/file-manager/view/file-manager-view.jsx b/front_minimal/src/sections/file-manager/view/file-manager-view.jsx index a68ebff..20d694d 100644 --- a/front_minimal/src/sections/file-manager/view/file-manager-view.jsx +++ b/front_minimal/src/sections/file-manager/view/file-manager-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/home/view/home-view.jsx b/front_minimal/src/sections/home/view/home-view.jsx index dfea790..8c4fbe8 100644 --- a/front_minimal/src/sections/home/view/home-view.jsx +++ b/front_minimal/src/sections/home/view/home-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/invoice/view/invoice-create-view.jsx b/front_minimal/src/sections/invoice/view/invoice-create-view.jsx index 79f79d1..1722530 100644 --- a/front_minimal/src/sections/invoice/view/invoice-create-view.jsx +++ b/front_minimal/src/sections/invoice/view/invoice-create-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/invoice/view/invoice-details-view.jsx b/front_minimal/src/sections/invoice/view/invoice-details-view.jsx index 78f447d..349038b 100644 --- a/front_minimal/src/sections/invoice/view/invoice-details-view.jsx +++ b/front_minimal/src/sections/invoice/view/invoice-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/invoice/view/invoice-edit-view.jsx b/front_minimal/src/sections/invoice/view/invoice-edit-view.jsx index 333f99f..87d932c 100644 --- a/front_minimal/src/sections/invoice/view/invoice-edit-view.jsx +++ b/front_minimal/src/sections/invoice/view/invoice-edit-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/invoice/view/invoice-list-view.jsx b/front_minimal/src/sections/invoice/view/invoice-list-view.jsx index 9d70298..0b129e8 100644 --- a/front_minimal/src/sections/invoice/view/invoice-list-view.jsx +++ b/front_minimal/src/sections/invoice/view/invoice-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/job/view/job-create-view.jsx b/front_minimal/src/sections/job/view/job-create-view.jsx index 5e40c28..08c321c 100644 --- a/front_minimal/src/sections/job/view/job-create-view.jsx +++ b/front_minimal/src/sections/job/view/job-create-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/job/view/job-details-view.jsx b/front_minimal/src/sections/job/view/job-details-view.jsx index 54734f1..b828f08 100644 --- a/front_minimal/src/sections/job/view/job-details-view.jsx +++ b/front_minimal/src/sections/job/view/job-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/job/view/job-edit-view.jsx b/front_minimal/src/sections/job/view/job-edit-view.jsx index 905a269..03fcbb8 100644 --- a/front_minimal/src/sections/job/view/job-edit-view.jsx +++ b/front_minimal/src/sections/job/view/job-edit-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/job/view/job-list-view.jsx b/front_minimal/src/sections/job/view/job-list-view.jsx index d6664e9..85b88b1 100644 --- a/front_minimal/src/sections/job/view/job-list-view.jsx +++ b/front_minimal/src/sections/job/view/job-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/kanban/view/kanban-view.jsx b/front_minimal/src/sections/kanban/view/kanban-view.jsx index b307052..2c9cafa 100644 --- a/front_minimal/src/sections/kanban/view/kanban-view.jsx +++ b/front_minimal/src/sections/kanban/view/kanban-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useRef, useState, useEffect, useCallback } from 'react'; import { diff --git a/front_minimal/src/sections/mail/view/mail-view.jsx b/front_minimal/src/sections/mail/view/mail-view.jsx index 006e5e9..f31eaf2 100644 --- a/front_minimal/src/sections/mail/view/mail-view.jsx +++ b/front_minimal/src/sections/mail/view/mail-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/sections/maintenance/view.jsx b/front_minimal/src/sections/maintenance/view.jsx index c5e50ea..4986bc8 100644 --- a/front_minimal/src/sections/maintenance/view.jsx +++ b/front_minimal/src/sections/maintenance/view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/sections/order/view/order-details-view.jsx b/front_minimal/src/sections/order/view/order-details-view.jsx index edc19fb..4ecf751 100644 --- a/front_minimal/src/sections/order/view/order-details-view.jsx +++ b/front_minimal/src/sections/order/view/order-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/order/view/order-list-view.jsx b/front_minimal/src/sections/order/view/order-list-view.jsx index 7a592b5..0bcdd9c 100644 --- a/front_minimal/src/sections/order/view/order-list-view.jsx +++ b/front_minimal/src/sections/order/view/order-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/overview/analytics/view/overview-analytics-view.jsx b/front_minimal/src/sections/overview/analytics/view/overview-analytics-view.jsx index 8c5fe85..8e0b981 100644 --- a/front_minimal/src/sections/overview/analytics/view/overview-analytics-view.jsx +++ b/front_minimal/src/sections/overview/analytics/view/overview-analytics-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Grid from '@mui/material/Unstable_Grid2'; import Typography from '@mui/material/Typography'; diff --git a/front_minimal/src/sections/overview/app/view/overview-app-view.jsx b/front_minimal/src/sections/overview/app/view/overview-app-view.jsx index 212363d..03342da 100644 --- a/front_minimal/src/sections/overview/app/view/overview-app-view.jsx +++ b/front_minimal/src/sections/overview/app/view/overview-app-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/sections/overview/banking/view/overview-banking-view.jsx b/front_minimal/src/sections/overview/banking/view/overview-banking-view.jsx index e295ede..734268d 100644 --- a/front_minimal/src/sections/overview/banking/view/overview-banking-view.jsx +++ b/front_minimal/src/sections/overview/banking/view/overview-banking-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Unstable_Grid2'; diff --git a/front_minimal/src/sections/overview/booking/view/overview-booking-view.jsx b/front_minimal/src/sections/overview/booking/view/overview-booking-view.jsx index 99e2958..d225ac4 100644 --- a/front_minimal/src/sections/overview/booking/view/overview-booking-view.jsx +++ b/front_minimal/src/sections/overview/booking/view/overview-booking-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Unstable_Grid2'; diff --git a/front_minimal/src/sections/overview/client/view/overview-client-view.jsx b/front_minimal/src/sections/overview/client/view/overview-client-view.jsx index 0e3712e..8bd5d5e 100644 --- a/front_minimal/src/sections/overview/client/view/overview-client-view.jsx +++ b/front_minimal/src/sections/overview/client/view/overview-client-view.jsx @@ -7,13 +7,16 @@ import Card from '@mui/material/Card'; import Stack from '@mui/material/Stack'; import Divider from '@mui/material/Divider'; import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import CircularProgress from '@mui/material/CircularProgress'; import LinearProgress from '@mui/material/LinearProgress'; import { useTheme } from '@mui/material/styles'; +import { useRouter } from 'src/routes/hooks'; import { fDateTime } from 'src/utils/format-time'; import { getClientDashboard, getChildDashboard } from 'src/utils/dashboard-api'; +import { createLiveKitRoom } from 'src/utils/livekit-api'; import { DashboardContent } from 'src/layouts/dashboard'; import { useAuthContext } from 'src/auth/hooks'; import { CONFIG } from 'src/config-global'; @@ -46,10 +49,30 @@ const formatDateTime = (str) => { // ---------------------------------------------------------------------- function LessonItem({ lesson }) { + const router = useRouter(); + const [joining, setJoining] = useState(false); + const mentorName = lesson.mentor ? `${lesson.mentor.first_name || ''} ${lesson.mentor.last_name || ''}`.trim() || 'Ментор' : 'Ментор'; + const now = new Date(); + const startTime = new Date(lesson.start_time); + const diffMin = (startTime - now) / 60000; + const canJoin = diffMin < 11 && diffMin > -90; + + const handleJoin = async () => { + try { + setJoining(true); + const res = await createLiveKitRoom(lesson.id); + const token = res?.access_token || res?.token; + router.push(`/video-call?token=${token}&lesson_id=${lesson.id}`); + } catch (err) { + console.error('Join error:', err); + setJoining(false); + } + }; + return ( - - {formatDateTime(lesson.start_time)} - + {canJoin ? ( + + ) : ( + + {formatDateTime(lesson.start_time)} + + )} ); } diff --git a/front_minimal/src/sections/overview/course/view/overview-course-view.jsx b/front_minimal/src/sections/overview/course/view/overview-course-view.jsx index 16ac280..736e14d 100644 --- a/front_minimal/src/sections/overview/course/view/overview-course-view.jsx +++ b/front_minimal/src/sections/overview/course/view/overview-course-view.jsx @@ -1,16 +1,18 @@ -'use client'; import { useState, useEffect, useMemo, useCallback } from 'react'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; +import Chip from '@mui/material/Chip'; import Button from '@mui/material/Button'; import Avatar from '@mui/material/Avatar'; import Stack from '@mui/material/Stack'; +import Divider from '@mui/material/Divider'; import { useTheme } from '@mui/material/styles'; import { cardClasses } from '@mui/material/Card'; import Typography from '@mui/material/Typography'; import CircularProgress from '@mui/material/CircularProgress'; +import { useRouter } from 'src/routes/hooks'; import { CONFIG } from 'src/config-global'; import { varAlpha } from 'src/theme/styles'; import { DashboardContent } from 'src/layouts/dashboard'; @@ -19,9 +21,11 @@ import { fDateTime } from 'src/utils/format-time'; import { fNumber } from 'src/utils/format-number'; import { AvatarShape } from 'src/assets/illustrations'; import { Image } from 'src/components/image'; +import { Iconify } from 'src/components/iconify'; // Статический импорт API import { getMentorDashboard, getMentorIncome } from 'src/utils/dashboard-api'; +import { createLiveKitRoom } from 'src/utils/livekit-api'; import { CourseProgress } from '../course-progress'; import { CourseMyAccount } from '../course-my-account'; @@ -30,6 +34,128 @@ import { CourseWidgetSummary } from '../course-widget-summary'; // ---------------------------------------------------------------------- +function lessonTimeLabel(startTime) { + if (!startTime) return '—'; + const d = new Date(startTime); + const today = new Date(); + const tomorrow = new Date(today); + tomorrow.setDate(today.getDate() + 1); + const time = d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); + if (d.toDateString() === today.toDateString()) return `Сегодня, ${time}`; + if (d.toDateString() === tomorrow.toDateString()) return `Завтра, ${time}`; + return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }); +} + +function UpcomingLessonCard({ lesson }) { + const router = useRouter(); + const [joining, setJoining] = useState(false); + + const now = new Date(); + const startTime = new Date(lesson.start_time); + const diffMin = (startTime - now) / 60000; + const canJoin = diffMin < 11 && diffMin > -90; + + const handleJoin = async () => { + try { + setJoining(true); + const res = await createLiveKitRoom(lesson.id); + const token = res?.access_token || res?.token; + router.push(`/video-call?token=${token}&lesson_id=${lesson.id}`); + } catch (err) { + console.error('Join error:', err); + setJoining(false); + } + }; + + const clientName = lesson.client?.name || + `${lesson.client?.first_name || ''} ${lesson.client?.last_name || ''}`.trim() || + 'Ученик'; + const clientAvatar = lesson.client?.avatar || lesson.client?.avatar_url || ''; + const clientInitial = (clientName[0] || 'У').toUpperCase(); + const subject = lesson.title || lesson.subject || 'Занятие'; + const timeLabel = lessonTimeLabel(lesson.start_time); + + return ( + `2px solid ${t.palette.success.main}`, + outlineOffset: '-2px', + }), + '&:hover': { + transform: 'translateY(-2px)', + boxShadow: (t) => t.customShadows?.z16 || '0 8px 24px rgba(0,0,0,0.12)', + }, + }} + > + + {clientInitial} + + + + {clientName} + + + + {subject} + + + + + + {timeLabel} + + + + {canJoin ? ( + + ) : ( + + )} + + ); +} + +// ---------------------------------------------------------------------- + export function OverviewCourseView() { const theme = useTheme(); const { user } = useAuthContext(); @@ -39,14 +165,14 @@ export function OverviewCourseView() { const [loading, setLoading] = useState(true); const [period, setPeriod] = useState('Месяц'); - // Фильтр активных уроков + // Уроки, к которым можно подключиться (< 11 мин до начала или не более 90 мин с начала) const activeLessons = useMemo(() => { const upcoming = stats?.upcoming_lessons || []; const NOW = new Date(); return upcoming.filter((lesson) => { const startTime = new Date(lesson.start_time); const diffInMinutes = (startTime.getTime() - NOW.getTime()) / 60000; - return diffInMinutes <= 10 && diffInMinutes > -60; + return diffInMinutes < 11 && diffInMinutes > -90; }); }, [stats]); @@ -188,11 +314,25 @@ export function OverviewCourseView() { - {/* Активные уроки */} - - Активные уроки - {activeLessons.length > 0 ? activeLessons.slice(0, 2).map((l) => renderCard(l, 'lesson')) : ( - Нет текущих уроков + {/* Ближайшие уроки */} + + + Ближайшие уроки + {activeLessons.length > 0 && ( + + )} + + {(stats?.upcoming_lessons || []).length > 0 ? ( + + {stats.upcoming_lessons.slice(0, 3).map((lesson) => ( + + ))} + + ) : ( + + + Нет запланированных занятий + )} diff --git a/front_minimal/src/sections/overview/e-commerce/view/overview-ecommerce-view.jsx b/front_minimal/src/sections/overview/e-commerce/view/overview-ecommerce-view.jsx index e7402f9..d838511 100644 --- a/front_minimal/src/sections/overview/e-commerce/view/overview-ecommerce-view.jsx +++ b/front_minimal/src/sections/overview/e-commerce/view/overview-ecommerce-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; import { useTheme } from '@mui/material/styles'; diff --git a/front_minimal/src/sections/overview/file/view/overview-file-view.jsx b/front_minimal/src/sections/overview/file/view/overview-file-view.jsx index a502c0a..7d515cb 100644 --- a/front_minimal/src/sections/overview/file/view/overview-file-view.jsx +++ b/front_minimal/src/sections/overview/file/view/overview-file-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/payment/payment-methods.jsx b/front_minimal/src/sections/payment/payment-methods.jsx index 7a559a4..893d4da 100644 --- a/front_minimal/src/sections/payment/payment-methods.jsx +++ b/front_minimal/src/sections/payment/payment-methods.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/payment/view/payment-view.jsx b/front_minimal/src/sections/payment/view/payment-view.jsx index 75b6c88..6aa0879 100644 --- a/front_minimal/src/sections/payment/view/payment-view.jsx +++ b/front_minimal/src/sections/payment/view/payment-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Container from '@mui/material/Container'; diff --git a/front_minimal/src/sections/permission/view.jsx b/front_minimal/src/sections/permission/view.jsx index bccceb5..268ed67 100644 --- a/front_minimal/src/sections/permission/view.jsx +++ b/front_minimal/src/sections/permission/view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/pricing/view/pricing-view.jsx b/front_minimal/src/sections/pricing/view/pricing-view.jsx index 5adffdb..caa1a8e 100644 --- a/front_minimal/src/sections/pricing/view/pricing-view.jsx +++ b/front_minimal/src/sections/pricing/view/pricing-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; diff --git a/front_minimal/src/sections/product/product-skeleton.jsx b/front_minimal/src/sections/product/product-skeleton.jsx index 9a0f991..736f879 100644 --- a/front_minimal/src/sections/product/product-skeleton.jsx +++ b/front_minimal/src/sections/product/product-skeleton.jsx @@ -1,4 +1,3 @@ -'use client'; import Stack from '@mui/material/Stack'; import Paper from '@mui/material/Paper'; diff --git a/front_minimal/src/sections/product/view/product-create-view.jsx b/front_minimal/src/sections/product/view/product-create-view.jsx index 8be141f..9b046e8 100644 --- a/front_minimal/src/sections/product/view/product-create-view.jsx +++ b/front_minimal/src/sections/product/view/product-create-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/product/view/product-details-view.jsx b/front_minimal/src/sections/product/view/product-details-view.jsx index 5064783..82702d5 100644 --- a/front_minimal/src/sections/product/view/product-details-view.jsx +++ b/front_minimal/src/sections/product/view/product-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/sections/product/view/product-edit-view.jsx b/front_minimal/src/sections/product/view/product-edit-view.jsx index 4781f59..89fdc3d 100644 --- a/front_minimal/src/sections/product/view/product-edit-view.jsx +++ b/front_minimal/src/sections/product/view/product-edit-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/product/view/product-list-view.jsx b/front_minimal/src/sections/product/view/product-list-view.jsx index e7a4674..ae3af83 100644 --- a/front_minimal/src/sections/product/view/product-list-view.jsx +++ b/front_minimal/src/sections/product/view/product-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useEffect, useCallback } from 'react'; diff --git a/front_minimal/src/sections/product/view/product-shop-details-view.jsx b/front_minimal/src/sections/product/view/product-shop-details-view.jsx index 7e9c5e1..504bcdc 100644 --- a/front_minimal/src/sections/product/view/product-shop-details-view.jsx +++ b/front_minimal/src/sections/product/view/product-shop-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; diff --git a/front_minimal/src/sections/product/view/product-shop-view.jsx b/front_minimal/src/sections/product/view/product-shop-view.jsx index ac124f9..cab9e79 100644 --- a/front_minimal/src/sections/product/view/product-shop-view.jsx +++ b/front_minimal/src/sections/product/view/product-shop-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/students/view/students-view.jsx b/front_minimal/src/sections/students/view/students-view.jsx index 9a11205..b2c1d1f 100644 --- a/front_minimal/src/sections/students/view/students-view.jsx +++ b/front_minimal/src/sections/students/view/students-view.jsx @@ -5,6 +5,8 @@ import { useState, useEffect, useCallback } from 'react'; import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; +import Chip from '@mui/material/Chip'; +import Grid from '@mui/material/Grid'; import Tabs from '@mui/material/Tabs'; import List from '@mui/material/List'; import Stack from '@mui/material/Stack'; @@ -26,7 +28,7 @@ import InputAdornment from '@mui/material/InputAdornment'; import ListItemAvatar from '@mui/material/ListItemAvatar'; import CircularProgress from '@mui/material/CircularProgress'; -import { paths } from 'src/routes/paths'; +import { paths } from 'src/routes/paths'; import { getStudents, @@ -39,13 +41,13 @@ import { rejectInvitationAsStudent, confirmInvitationAsStudent, getMentorshipRequestsPending, -} from 'src/utils/students-api'; +} from 'src/utils/students-api'; import { CONFIG } from 'src/config-global'; -import { DashboardContent } from 'src/layouts/dashboard'; +import { DashboardContent } from 'src/layouts/dashboard'; import { Iconify } from 'src/components/iconify'; -import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs'; +import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs'; import { useAuthContext } from 'src/auth/hooks'; @@ -99,7 +101,7 @@ function MentorStudentList({ onRefresh }) { if (loading) return ; return ( - + {error}} {filtered.length === 0 ? ( - - {search ? 'Ничего не найдено' : 'Нет учеников'} - + + + + {search ? 'Ничего не найдено' : 'Нет учеников'} + + ) : ( - + {filtered.map((s) => { const u = s.user || {}; + const name = `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || '—'; return ( - - - + + t.customShadows?.z16 || '0 8px 24px rgba(0,0,0,0.12)', + }, + }} + > + {initials(u.first_name, u.last_name)} - - - {s.total_lessons != null && ( - - {s.total_lessons} уроков + + + {name} - )} - + + {u.email || ''} + + + + {s.total_lessons != null && ( + } + /> + )} + {s.subject && ( + + )} + + + ); })} - + )} ); @@ -309,31 +346,59 @@ function ClientMentorList() { .finally(() => setLoading(false)); }, []); - if (loading) return ; + if (loading) return ; + + if (mentors.length === 0) { + return ( + + + Нет подключённых менторов + + ); + } return ( - - Мои менторы - {mentors.length === 0 ? ( - Нет подключённых менторов - ) : ( - - {mentors.map((m) => ( - - - - {initials(m.first_name, m.last_name)} - - - - - ))} - - )} - + + {mentors.map((m) => { + const name = `${m.first_name || ''} ${m.last_name || ''}`.trim() || m.email || '—'; + return ( + + t.customShadows?.z16 || '0 8px 24px rgba(0,0,0,0.12)', + }, + }} + > + + {initials(m.first_name, m.last_name)} + + + {name} + + + {m.email || ''} + + + + ); + })} + ); } diff --git a/front_minimal/src/sections/tour/view/tour-create-view.jsx b/front_minimal/src/sections/tour/view/tour-create-view.jsx index 714e294..a3f48b3 100644 --- a/front_minimal/src/sections/tour/view/tour-create-view.jsx +++ b/front_minimal/src/sections/tour/view/tour-create-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/tour/view/tour-details-view.jsx b/front_minimal/src/sections/tour/view/tour-details-view.jsx index 9fb6e04..812c79f 100644 --- a/front_minimal/src/sections/tour/view/tour-details-view.jsx +++ b/front_minimal/src/sections/tour/view/tour-details-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/tour/view/tour-edit-view.jsx b/front_minimal/src/sections/tour/view/tour-edit-view.jsx index d164f5c..3359886 100644 --- a/front_minimal/src/sections/tour/view/tour-edit-view.jsx +++ b/front_minimal/src/sections/tour/view/tour-edit-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/tour/view/tour-list-view.jsx b/front_minimal/src/sections/tour/view/tour-list-view.jsx index 572bbd5..a8c4a60 100644 --- a/front_minimal/src/sections/tour/view/tour-list-view.jsx +++ b/front_minimal/src/sections/tour/view/tour-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/user/profile-followers.jsx b/front_minimal/src/sections/user/profile-followers.jsx index 0dc1a59..4864b05 100644 --- a/front_minimal/src/sections/user/profile-followers.jsx +++ b/front_minimal/src/sections/user/profile-followers.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/user/profile-home.jsx b/front_minimal/src/sections/user/profile-home.jsx index 9c65f4b..56fd5da 100644 --- a/front_minimal/src/sections/user/profile-home.jsx +++ b/front_minimal/src/sections/user/profile-home.jsx @@ -1,4 +1,3 @@ -'use client'; import { useRef } from 'react'; diff --git a/front_minimal/src/sections/user/view/user-cards-view.jsx b/front_minimal/src/sections/user/view/user-cards-view.jsx index b53b64a..a70d210 100644 --- a/front_minimal/src/sections/user/view/user-cards-view.jsx +++ b/front_minimal/src/sections/user/view/user-cards-view.jsx @@ -1,4 +1,3 @@ -'use client'; import Button from '@mui/material/Button'; diff --git a/front_minimal/src/sections/user/view/user-create-view.jsx b/front_minimal/src/sections/user/view/user-create-view.jsx index 5ad5df5..7c5e947 100644 --- a/front_minimal/src/sections/user/view/user-create-view.jsx +++ b/front_minimal/src/sections/user/view/user-create-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/user/view/user-edit-view.jsx b/front_minimal/src/sections/user/view/user-edit-view.jsx index adb7e54..94ef42c 100644 --- a/front_minimal/src/sections/user/view/user-edit-view.jsx +++ b/front_minimal/src/sections/user/view/user-edit-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { paths } from 'src/routes/paths'; diff --git a/front_minimal/src/sections/user/view/user-list-view.jsx b/front_minimal/src/sections/user/view/user-list-view.jsx index d4baa8d..8911a95 100644 --- a/front_minimal/src/sections/user/view/user-list-view.jsx +++ b/front_minimal/src/sections/user/view/user-list-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/user/view/user-profile-view.jsx b/front_minimal/src/sections/user/view/user-profile-view.jsx index 1c3213b..973aa15 100644 --- a/front_minimal/src/sections/user/view/user-profile-view.jsx +++ b/front_minimal/src/sections/user/view/user-profile-view.jsx @@ -1,4 +1,3 @@ -'use client'; import { useState, useCallback } from 'react'; diff --git a/front_minimal/src/sections/video-call/view/video-call-view.jsx b/front_minimal/src/sections/video-call/view/video-call-view.jsx index b0e9052..6675b2d 100644 --- a/front_minimal/src/sections/video-call/view/video-call-view.jsx +++ b/front_minimal/src/sections/video-call/view/video-call-view.jsx @@ -5,8 +5,7 @@ import 'src/styles/livekit-components.css'; import { createPortal } from 'react-dom'; import { isTrackReference } from '@livekit/components-core'; -import { useSearchParams } from 'react-router-dom'; -import { useRouter } from 'src/routes/hooks'; +import { useRouter, useSearchParams } from 'src/routes/hooks'; import { useRef, useState, useEffect, Component } from 'react'; import { Track, RoomEvent, VideoPresets } from 'livekit-client'; import { diff --git a/front_minimal/src/theme/color-scheme-script.js b/front_minimal/src/theme/color-scheme-script.js index e780bd1..59da447 100644 --- a/front_minimal/src/theme/color-scheme-script.js +++ b/front_minimal/src/theme/color-scheme-script.js @@ -1,4 +1,3 @@ -'use client'; import { getInitColorSchemeScript as _getInitColorSchemeScript } from '@mui/material/styles'; diff --git a/front_minimal/src/theme/theme-provider.jsx b/front_minimal/src/theme/theme-provider.jsx index 91e3531..d95e234 100644 --- a/front_minimal/src/theme/theme-provider.jsx +++ b/front_minimal/src/theme/theme-provider.jsx @@ -1,4 +1,5 @@ import CssBaseline from '@mui/material/CssBaseline'; +import GlobalStyles from '@mui/material/GlobalStyles'; import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles'; import { useTranslate } from 'src/locales'; @@ -25,6 +26,41 @@ export function ThemeProvider({ children }) { modeStorageKey={schemeConfig.modeStorageKey} > + ({ + /* Firefox */ + '*': { + scrollbarWidth: 'thin', + scrollbarColor: `${theme.vars.palette.grey[400]} transparent`, + }, + /* Webkit (Chrome, Safari, Edge) */ + '*::-webkit-scrollbar': { + width: 6, + height: 6, + }, + '*::-webkit-scrollbar-track': { + background: 'transparent', + }, + '*::-webkit-scrollbar-thumb': { + background: theme.vars.palette.grey[400], + borderRadius: 99, + }, + '*::-webkit-scrollbar-thumb:hover': { + background: theme.vars.palette.grey[500], + }, + [theme.getColorSchemeSelector('dark')]: { + '*': { + scrollbarColor: `${theme.vars.palette.grey[700]} transparent`, + }, + '*::-webkit-scrollbar-thumb': { + background: theme.vars.palette.grey[700], + }, + '*::-webkit-scrollbar-thumb:hover': { + background: theme.vars.palette.grey[600], + }, + }, + })} + /> {children} ); diff --git a/front_minimal/src/utils/chat-api.js b/front_minimal/src/utils/chat-api.js index d33e727..8342e82 100644 --- a/front_minimal/src/utils/chat-api.js +++ b/front_minimal/src/utils/chat-api.js @@ -2,15 +2,34 @@ import axios from 'src/utils/axios'; // ---------------------------------------------------------------------- +export function normalizeChat(c) { + const other = c?.other_participant ?? {}; + const name = + other.full_name || + [other.first_name, other.last_name].filter(Boolean).join(' ') || + c?.participant_name || + 'Чат'; + const lastText = c?.last_message?.content || c?.last_message?.text || c?.last_message || ''; + return { + ...c, + participant_name: name, + participant_id: other.id ?? c?.other_user_id ?? c?.participant_id ?? null, + avatar_url: other.avatar_url || other.avatar || c?.avatar_url || null, + last_message: lastText, + unread_count: c?.my_participant?.unread_count ?? c?.unread_count ?? 0, + }; +} + export async function getConversations(params) { const res = await axios.get('/chat/chats/', { params }); const {data} = res; - if (Array.isArray(data)) return { count: data.length, next: null, previous: null, results: data }; + const raw = Array.isArray(data) ? data : (data?.results ?? []); + const results = raw.map(normalizeChat); return { - count: data?.count ?? (data?.results?.length ?? 0), + count: Array.isArray(data) ? data.length : (data?.count ?? results.length), next: data?.next ?? null, previous: data?.previous ?? null, - results: data?.results ?? [], + results, }; } @@ -19,9 +38,11 @@ export async function getChatById(uuid) { return res.data; } -export async function createChat(participantId) { - const res = await axios.post('/chat/chats/', { participants: [participantId] }); - return res.data; +export async function createChat(userId) { + const res = await axios.post('/chat/chats/create_direct/', { user_id: userId }); + const { data } = res; + if (data && typeof data === 'object' && 'data' in data && typeof data.data === 'object') return data.data; + return data; } export async function getChatMessagesByUuid(chatUuid, params) { @@ -35,13 +56,20 @@ export async function getMessages(chatId, params) { } export async function sendMessage(chatId, content, file) { - const formData = new FormData(); - formData.append('chat', String(chatId)); - formData.append('content', content); - if (file) formData.append('file', file); - const res = await axios.post('/chat/messages/', formData); - const {data} = res; - if (data && typeof data === 'object' && 'data' in data) return data.data; + let res; + if (file) { + const formData = new FormData(); + formData.append('chat', String(chatId)); + formData.append('content', content); + formData.append('file', file); + res = await axios.post('/chat/messages/', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } else { + res = await axios.post('/chat/messages/', { chat: chatId, content }); + } + const { data } = res; + if (data && typeof data === 'object' && 'data' in data && typeof data.data === 'object') return data.data; return data; }