From 27a2811806691c905e0b81f26527deee67a949bc Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Thu, 21 May 2026 19:53:32 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA=20=D0=B2=20=D1=82=D0=BE?= =?UTF-8?q?=D0=BF=D0=B1=D0=B0=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/layout/AppTopbar.vue | 102 +++++++++++++++++-- 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/layout/AppTopbar.vue b/frontend/src/components/layout/AppTopbar.vue index 8e77ca4..045fee5 100644 --- a/frontend/src/components/layout/AppTopbar.vue +++ b/frontend/src/components/layout/AppTopbar.vue @@ -14,13 +14,17 @@ const userStore = useUserStore() const router = useRouter() const isProfileMenuOpen = ref(false) const isCoinDialogOpen = ref(false) +const isLevelDialogOpen = ref(false) +const isSlotsDialogOpen = ref(false) const profileMenuRef = ref(null) const unreadCount = computed(() => userStore.unreadCount()) +const showStudentChips = computed(() => auth.user?.activeRole === 'student') const enrollmentSlotText = computed(() => { - if (auth.user?.activeRole !== 'student') return '' - if (typeof auth.user.enrollmentSlotLimit !== 'number') return '' - return `${auth.user.activeEnrollments ?? 0}/${auth.user.enrollmentSlotLimit}` + const user = auth.user + if (!user || user.activeRole !== 'student') return '' + if (typeof user.enrollmentSlotLimit !== 'number') return '' + return `${user.activeEnrollments ?? 0}/${user.enrollmentSlotLimit}` }) const roleLabels: Record = { student: 'Студент', @@ -53,8 +57,18 @@ function closeCoinDialog() { isCoinDialogOpen.value = false } +function closeLevelDialog() { + isLevelDialogOpen.value = false +} + +function closeSlotsDialog() { + isSlotsDialogOpen.value = false +} + function toggleProfileMenu() { closeCoinDialog() + closeLevelDialog() + closeSlotsDialog() isProfileMenuOpen.value = !isProfileMenuOpen.value } @@ -63,6 +77,25 @@ function openCoinDialog() { isCoinDialogOpen.value = true } +function openLevelDialog() { + closeProfileMenu() + closeCoinDialog() + closeSlotsDialog() + isLevelDialogOpen.value = true +} + +function openSlotsDialog() { + closeProfileMenu() + closeCoinDialog() + closeLevelDialog() + isSlotsDialogOpen.value = true +} + +function openMyLecturesFromSlotsDialog() { + closeSlotsDialog() + router.push('/my-lectures') +} + function openProfile() { closeProfileMenu() router.push('/profile') @@ -109,22 +142,35 @@ onBeforeUnmount(() => {
-
+
+ -
+
+ + + +

+ Уровень показывает ваш прогресс в UniVerse. Он растет вместе с активностью: посещением открытых лекций, + полезными отзывами и достижениями. +

+ +
+ + +

+ Слоты показывают, сколько открытых лекций уже занято в вашем текущем лимите. Когда лимит заполнен, + освободите слот отменой записи или повышайте уровень, чтобы получить больше возможностей. +

+ +
@@ -238,8 +305,10 @@ onBeforeUnmount(() => { border-radius: 20px; background: var(--color-primary-a10); color: var(--color-primary-dark); - cursor: default; + cursor: pointer; + font: inherit; white-space: nowrap; + transition: background-color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; } .level-icon { color: var(--color-primary); @@ -264,8 +333,21 @@ onBeforeUnmount(() => { border-radius: 20px; background: var(--color-primary-a10); color: var(--color-success-text); - cursor: default; + cursor: pointer; + font: inherit; white-space: nowrap; + transition: background-color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; +} +.level-chip:hover, +.slot-chip:hover { + background: var(--color-primary-a18); + border-color: var(--color-primary-a40); +} +.level-chip:focus-visible, +.slot-chip:focus-visible { + outline: 2px solid var(--color-primary-a45); + outline-offset: 2px; + box-shadow: 0 0 0 3px var(--color-primary-a12); } .slot-icon { color: var(--color-primary);