diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index e0ef7a7..e81154e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,7 +6,7 @@ on: - main env: - VERSION: 1.0.1 + VERSION: 1.1.0 jobs: # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) diff --git a/Directory.Build.props b/Directory.Build.props index 8b7bae1..bda9b78 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.0.1 + 1.1.0 net10.0 preview enable diff --git a/compose.yaml b/compose.yaml index 74dfebc..ff04b49 100644 --- a/compose.yaml +++ b/compose.yaml @@ -18,7 +18,7 @@ services: retries: 10 bot: - image: git.codeanddice.ru/toutsu/gmrelay-bot:1.0.1 + image: git.codeanddice.ru/toutsu/gmrelay-bot:1.1.0 container_name: gmrelay_bot restart: always network_mode: host @@ -30,7 +30,7 @@ services: - "Telegram__BotToken=${TELEGRAM_BOT_TOKEN}" web: - image: git.codeanddice.ru/toutsu/gmrelay-web:1.0.1 + image: git.codeanddice.ru/toutsu/gmrelay-web:1.1.0 container_name: gmrelay_web restart: always network_mode: host diff --git a/src/GmRelay.Web/Components/App.razor b/src/GmRelay.Web/Components/App.razor index 3a15a29..9bb1aea 100644 --- a/src/GmRelay.Web/Components/App.razor +++ b/src/GmRelay.Web/Components/App.razor @@ -1,12 +1,16 @@ - - + + + + - + + + diff --git a/src/GmRelay.Web/Components/Layout/MainLayout.razor b/src/GmRelay.Web/Components/Layout/MainLayout.razor index 46bc25d..f9949ea 100644 --- a/src/GmRelay.Web/Components/Layout/MainLayout.razor +++ b/src/GmRelay.Web/Components/Layout/MainLayout.razor @@ -1,19 +1,15 @@ -@inherits LayoutComponentBase +@inherits LayoutComponentBase
- + -
- - -
+
+
@Body
-
+
diff --git a/src/GmRelay.Web/Components/Layout/MainLayout.razor.css b/src/GmRelay.Web/Components/Layout/MainLayout.razor.css index 38d1f25..4d1d93a 100644 --- a/src/GmRelay.Web/Components/Layout/MainLayout.razor.css +++ b/src/GmRelay.Web/Components/Layout/MainLayout.razor.css @@ -1,86 +1,39 @@ .page { - position: relative; display: flex; - flex-direction: column; -} - -main { - flex: 1; + min-height: 100vh; } .sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; + width: var(--sidebar-width); + background: linear-gradient(180deg, #0f1629 0%, #1a0a2e 100%); + border-right: 1px solid var(--border-color); + position: fixed; + top: 0; + left: 0; + height: 100vh; + z-index: 100; display: flex; - align-items: center; + flex-direction: column; + transition: transform var(--transition-smooth); } - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } +.main-area { + flex: 1; + margin-left: var(--sidebar-width); + min-height: 100vh; } -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row.auth ::deep a:first-child { - flex: 1; - text-align: right; - width: 0; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } +.content { + padding: 1.5rem 2rem; + max-width: 100%; } +/* === Error UI === */ #blazor-error-ui { - color-scheme: light only; - background: lightyellow; + background: var(--bg-secondary); + border-top: 1px solid var(--border-color); bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3); box-sizing: border-box; display: none; left: 0; @@ -88,11 +41,44 @@ main { position: fixed; width: 100%; z-index: 1000; + color: var(--text-secondary); + font-size: 0.875rem; } + #blazor-error-ui .reload { + color: var(--accent-secondary); + margin-left: 0.5rem; + } + #blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; } + +/* === Mobile Responsive === */ +@media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + width: 280px; + } + + .sidebar.open { + transform: translateX(0); + } + + .main-area { + margin-left: 0; + } + + .content { + padding: 1rem; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .content { + padding: 1.25rem 1.5rem; + } +} diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor index a28b9c1..6431a0e 100644 --- a/src/GmRelay.Web/Components/Layout/NavMenu.razor +++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor @@ -1,41 +1,73 @@ - + + + + + + + +@code { + private bool isOpen; + + private void ToggleMenu() => isOpen = !isOpen; + private void CloseMenu() => isOpen = false; +} diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor.css b/src/GmRelay.Web/Components/Layout/NavMenu.razor.css index a2aeace..1054027 100644 --- a/src/GmRelay.Web/Components/Layout/NavMenu.razor.css +++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor.css @@ -1,105 +1,194 @@ -.navbar-toggler { - appearance: none; +/* === Nav Header === */ +.nav-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.25rem 1rem; + border-bottom: 1px solid var(--border-color); +} + +.nav-brand { + display: flex; + align-items: center; + gap: 0.625rem; + text-decoration: none; + color: var(--text-primary); +} + +.nav-brand-icon { + font-size: 1.5rem; +} + +.nav-brand-text { + font-size: 1.125rem; + font-weight: 700; + background: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.nav-toggle { + display: none; + background: none; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-secondary); + padding: 0.375rem; cursor: pointer; - width: 3.5rem; - height: 2.5rem; - color: white; - position: absolute; - top: 0.5rem; - right: 1rem; - border: 1px solid rgba(255, 255, 255, 0.1); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); + transition: all var(--transition-fast); } -.navbar-toggler:checked { - background-color: rgba(255, 255, 255, 0.5); +.nav-toggle:hover { + background: var(--bg-surface); + color: var(--text-primary); } -.top-row { - min-height: 3.5rem; - background-color: rgba(0,0,0,0.4); +/* === Nav Body === */ +.nav-body { + flex: 1; + display: flex; + flex-direction: column; + padding: 0.75rem 0; + overflow-y: auto; } -.navbar-brand { - font-size: 1.1rem; -} - -.bi { - display: inline-block; - position: relative; - width: 1.25rem; - height: 1.25rem; - margin-right: 0.75rem; - top: -1px; - background-size: cover; -} - -.bi-house-door-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); -} - -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); -} - -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +.nav-section { + padding: 0 0.75rem; + flex: 1; } +/* === Nav Items === */ .nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.625rem 0.875rem; + border-radius: var(--radius-sm); + color: var(--text-secondary); + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + transition: all var(--transition-normal); + margin-bottom: 0.125rem; } - .nav-item:first-of-type { - padding-top: 1rem; - } +.nav-item:hover { + background: rgba(255, 255, 255, 0.06); + color: var(--text-primary); +} - .nav-item:last-of-type { - padding-bottom: 1rem; - } +.nav-item.active, +.nav-item ::deep a.active { + background: rgba(124, 58, 237, 0.15); + color: var(--accent-primary); + border: 1px solid rgba(124, 58, 237, 0.2); +} - .nav-item ::deep .nav-link { - color: #d7d7d7; - background: none; - border: none; - border-radius: 4px; - height: 3rem; +.nav-icon { + width: 1.125rem; + height: 1.125rem; + flex-shrink: 0; +} + +/* === Nav Footer === */ +.nav-footer { + padding: 0.75rem; + border-top: 1px solid var(--border-color); + margin-top: auto; +} + +.nav-user { + display: flex; + align-items: center; + gap: 0.625rem; + padding: 0.5rem 0.5rem 0.75rem; +} + +.nav-user-avatar { + width: 2rem; + height: 2rem; + border-radius: 50%; + background: var(--accent-gradient); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8125rem; + font-weight: 700; + color: white; + flex-shrink: 0; +} + +.nav-user-name { + font-size: 0.8125rem; + font-weight: 500; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.nav-logout-btn { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; + padding: 0.5rem 0.875rem; + background: none; + border: 1px solid transparent; + border-radius: var(--radius-sm); + color: var(--text-muted); + font-family: 'Inter', sans-serif; + font-size: 0.8125rem; + cursor: pointer; + transition: all var(--transition-normal); +} + +.nav-logout-btn:hover { + background: var(--status-danger-bg); + color: var(--status-danger); + border-color: rgba(239, 68, 68, 0.15); +} + +.nav-version { + text-align: center; + font-size: 0.6875rem; + color: var(--text-muted); + padding-top: 0.5rem; + opacity: 0.6; +} + +/* === Mobile === */ +@media (max-width: 768px) { + .nav-toggle { display: flex; align-items: center; - line-height: 3rem; - width: 100%; + justify-content: center; + position: fixed; + top: 0.75rem; + left: 0.75rem; + z-index: 200; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + width: 2.5rem; + height: 2.5rem; } -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.37); - color: white; -} - -.nav-item ::deep .nav-link:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -.nav-scrollable { - display: none; -} - -.navbar-toggler:checked ~ .nav-scrollable { - display: block; -} - -@media (min-width: 641px) { - .navbar-toggler { + .nav-body { display: none; } - .nav-scrollable { - /* Never collapse the sidebar for wide screens */ - display: block; + .nav-body.open { + display: flex; + } - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; + .nav-header { + padding-right: 0.75rem; + } +} + +@media (min-width: 769px) { + .nav-body { + height: calc(100vh - 4.5rem); } } diff --git a/src/GmRelay.Web/Components/Pages/EditSession.razor b/src/GmRelay.Web/Components/Pages/EditSession.razor index f416c53..eab499b 100644 --- a/src/GmRelay.Web/Components/Pages/EditSession.razor +++ b/src/GmRelay.Web/Components/Pages/EditSession.razor @@ -6,57 +6,65 @@ @inject SessionService SessionService @inject NavigationManager Navigation -Редактирование сессии - GM-Relay +Редактирование сессии — GM-Relay -
- +
+ -

Редактирование сессии

+ @if (session == null) { -

Загрузка деталей сессии...

+
+
+
+
+
+
+
} else { -
-
- -
- - -
Изменение этого поля обновит все сессии в одной группе.
-
+
+ +
+ + +
Изменение этого поля обновит все сессии в одной группе.
+
-
- - -
Текущее: @session.ScheduledAt.FormatMoscow()
-
+
+ + +
Текущее: @session.ScheduledAt.FormatMoscow()
+
-
- - -
+
+ + +
-
- - -
-
-
+
+ + +
+
- + @if (!string.IsNullOrEmpty(errorMessage)) { -
@errorMessage
+
+ ⚠️ @errorMessage +
} }
diff --git a/src/GmRelay.Web/Components/Pages/Error.razor b/src/GmRelay.Web/Components/Pages/Error.razor index b045a3d..f4d5461 100644 --- a/src/GmRelay.Web/Components/Pages/Error.razor +++ b/src/GmRelay.Web/Components/Pages/Error.razor @@ -1,28 +1,26 @@ -@page "/Error" +@page "/Error" @using System.Diagnostics -Ошибка +Ошибка — GM-Relay -

Ошибка.

-

Произошла ошибка при обработке вашего запроса.

+
+
+
⚠️
+

Произошла ошибка

+

При обработке вашего запроса что-то пошло не так. Пожалуйста, попробуйте снова.

-@if (ShowRequestId) -{ -

- ID запроса: @RequestId -

-} + @if (ShowRequestId) + { +

+ ID запроса: @RequestId +

+ } -

Режим разработки

-

- Переключение на среду Development отобразит более подробную информацию о произошедшей ошибке. -

-

- Среда Development не должна быть включена для развернутых приложений. - Это может привести к отображению конфиденциальной информации из исключений конечным пользователям. - Для локальной отладки включите среду Development, установив переменную среды ASPNETCORE_ENVIRONMENT в значение Development - и перезапустите приложение. -

+ + ← На главную + +
+
@code{ [CascadingParameter] diff --git a/src/GmRelay.Web/Components/Pages/GroupDetails.razor b/src/GmRelay.Web/Components/Pages/GroupDetails.razor index 6eff9c4..ec96c55 100644 --- a/src/GmRelay.Web/Components/Pages/GroupDetails.razor +++ b/src/GmRelay.Web/Components/Pages/GroupDetails.razor @@ -5,60 +5,105 @@ @attribute [Authorize] @inject SessionService SessionService -Сессии группы - GM-Relay +Сессии группы — GM-Relay -
- +
+ -

Предстоящие игры

- -
- @if (sessions == null) - { -

Загрузка сессий...

- } - else if (sessions.Count == 0) - { -
Для этой группы не найдено предстоящих сессий.
- } - else - { -
- - - - - - - - - - - - @foreach (var session in sessions) - { - - - - - - - - } - -
НазваниеВремя (МСК)СтатусСсылкаДействие
@session.Title@session.ScheduledAt.FormatMoscow() - @TranslateStatus(session.Status) - Ссылка - Изменить -
-
- } + + + @if (sessions == null) + { +
+
+
+
+
+
+ } + else if (sessions.Count == 0) + { +
+
+
🎯
+
Нет предстоящих сессий
+

Для этой группы пока не запланировано игровых сессий.

+
+
+ } + else + { + @* Desktop table *@ +
+ + + + + + + + + + + + @foreach (var session in sessions) + { + + + + + + + + } + +
НазваниеВремя (МСК)СтатусСсылкаДействие
@session.Title@session.ScheduledAt.FormatMoscow() + @TranslateStatus(session.Status) + + + Подключиться ↗ + + + + ✏️ Изменить + +
+
+ + @* Mobile cards *@ +
+ @foreach (var session in sessions) + { +
+
+ @session.Title + @TranslateStatus(session.Status) +
+
+
+ 🕐 Время + @session.ScheduledAt.FormatMoscow() +
+
+ 🔗 Ссылка + Подключиться ↗ +
+
+ +
+ } +
+ }
@code { @@ -72,10 +117,12 @@ private string GetStatusClass(string status) => status switch { - SessionStatus.Confirmed => "bg-success", - SessionStatus.Cancelled => "bg-danger", - SessionStatus.ConfirmationSent => "bg-warning text-dark", - _ => "bg-secondary" + SessionStatus.Confirmed => "status-success", + SessionStatus.Cancelled => "status-danger", + SessionStatus.ConfirmationSent => "status-warning", + "Recruiting" => "status-info", + "RecruitmentClosed" => "status-info", + _ => "status-neutral" }; private string TranslateStatus(string status) => status switch @@ -83,7 +130,7 @@ "Recruiting" => "Набор", "RecruitmentClosed" => "Набор закрыт", SessionStatus.Planned => "Запланировано", - SessionStatus.ConfirmationSent => "Ждем подтверждения", + SessionStatus.ConfirmationSent => "Ждём подтверждения", SessionStatus.Confirmed => "Подтверждено", SessionStatus.Cancelled => "Отменено", _ => status diff --git a/src/GmRelay.Web/Components/Pages/Home.razor b/src/GmRelay.Web/Components/Pages/Home.razor index 8a9f002..d6d06a7 100644 --- a/src/GmRelay.Web/Components/Pages/Home.razor +++ b/src/GmRelay.Web/Components/Pages/Home.razor @@ -6,45 +6,78 @@ @inject SessionService SessionService @inject AuthenticationStateProvider AuthStateProvider -Панель управления - GM-Relay +Панель управления — GM-Relay -
-

Добро пожаловать, @userName!

-

Выберите группу для управления играми.

+
+ -
- @if (groups == null) - { -

Загрузка групп...

- } - else if (groups.Count == 0) - { -
-
-
-

У вас еще нет зарегистрированных групп. Сначала добавьте бота в группу Telegram!

-
-
+ @if (groups == null) + { +
+ @for (int i = 0; i < 3; i++) + { +
+ } +
+ } + else if (groups.Count == 0) + { +
+
+
🤖
+
Нет зарегистрированных групп
+

Добавьте бота GM-Relay в свою группу Telegram, чтобы начать управлять игровыми сессиями.

- } - else - { +
+ } + else + { +
@foreach (var group in groups) { -
-
-
-
@group.Name
-

ID: @group.TelegramChatId

- Посмотреть игры -
-
+
+
🎮
+

@group.Name

+

ID: @group.TelegramChatId

+ + Посмотреть игры → +
} - } -
+
+ }
+ + @code { private List? groups; private string userName = ""; diff --git a/src/GmRelay.Web/Components/Pages/Login.razor b/src/GmRelay.Web/Components/Pages/Login.razor index 06eccc9..88d5f09 100644 --- a/src/GmRelay.Web/Components/Pages/Login.razor +++ b/src/GmRelay.Web/Components/Pages/Login.razor @@ -3,29 +3,27 @@ @inject NavigationManager Navigation @inject IConfiguration Configuration -Вход - GM-Relay +Вход — GM-Relay -
-
-
-

Панель управления GM-Relay

-

Пожалуйста, войдите как Мастер Игры для управления сессиями.

+