Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25c22b2ff5 | |||
| cb40c2438d |
@@ -6,7 +6,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: 1.9.3
|
VERSION: 1.9.5
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.9.3</Version>
|
<Version>1.9.5</Version>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
|
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
|
||||||
|
|
||||||
**Текущая версия:** `v1.9.3`.
|
**Текущая версия:** `v1.9.5`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -179,11 +179,11 @@ Owner или co-GM нажимает кнопку `⏰ Перенести` у н
|
|||||||
Если включён режим `В группе и в личку`, бот дополнительно отправляет игрокам персональные сообщения о RSVP за 24 часа, напоминание за 1 час, ссылку перед стартом, отмену и перенос. Если Telegram не позволяет написать игроку в ЛС, бот логирует ошибку и продолжает отправку остальным участникам.
|
Если включён режим `В группе и в личку`, бот дополнительно отправляет игрокам персональные сообщения о RSVP за 24 часа, напоминание за 1 час, ссылку перед стартом, отмену и перенос. Если Telegram не позволяет написать игроку в ЛС, бот логирует ошибку и продолжает отправку остальным участникам.
|
||||||
|
|
||||||
### Telegram Mini App Dashboard
|
### Telegram Mini App Dashboard
|
||||||
Owner и co-GM могут открыть мобильный dashboard прямо из Telegram через кнопку меню бота или кнопку `Открыть dashboard` после `/start`. Страница `/miniapp` получает `Telegram.WebApp.initData`, отправляет его на серверный endpoint `/auth/telegram-webapp`, проходит HMAC-проверку токеном бота и выдаёт обычную cookie-сессию dashboard.
|
Owner и co-GM могут открыть мобильный dashboard прямо из Telegram: через кнопку меню бота или кнопку `Открыть dashboard` после `/start`. Дополнительный пароль вводить не нужно: GM-Relay сам проверит вход через Telegram.
|
||||||
|
|
||||||
После входа Mini App использует те же страницы, что и Web Dashboard: список групп, карточки сессий, редактирование игры, повышение игрока из листа ожидания, применение шаблонов и bulk-операции batch. Доступ к чужим группам не появляется: все данные по-прежнему фильтруются через `AuthorizedSessionService` по роли owner/co-GM.
|
Внутри открывается та же панель управления, только удобная для телефона. Можно смотреть свои группы, редактировать игры, управлять листом ожидания, запускать шаблоны и выполнять массовые действия с пачками игр. Чужие группы не появятся: dashboard показывает только те группы, где вы owner или co-GM.
|
||||||
|
|
||||||
Если `Telegram.WebApp.initData` недоступен или серверная проверка Mini App не прошла, `/miniapp` показывает диагностичное состояние и fallback-кнопку входа. Fallback больше не зависит от внешнего redirect: Telegram Login Widget вызывает callback на странице, отправляет payload на `/auth/telegram-login`, получает cookie в текущем WebView и сразу возвращает пользователя в dashboard.
|
Если автоматический вход не сработал, Mini App покажет понятное сообщение и кнопку входа через Telegram. Нажмите её, подтвердите вход, и dashboard откроется в том же окне Telegram.
|
||||||
|
|
||||||
### Другие команды
|
### Другие команды
|
||||||
- `/listsessions` — Показать список всех актуальных игр в этой группе.
|
- `/listsessions` — Показать список всех актуальных игр в этой группе.
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.3
|
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.9.5
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
- gmrelay
|
- gmrelay
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.3
|
image: git.codeanddice.ru/toutsu/gmrelay-web:1.9.5
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="nav-version">v1.9.3</div>
|
<div class="nav-version">v1.9.5</div>
|
||||||
</div>
|
</div>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<NotAuthorized>
|
<NotAuthorized>
|
||||||
|
|||||||
@@ -214,7 +214,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* Desktop table *@
|
@* Desktop table *@
|
||||||
<div class="glass-card session-table-desktop animate-slide-up" style="padding: 0; overflow: hidden;">
|
<div class="glass-card session-table-desktop session-table-desktop-card animate-slide-up">
|
||||||
<table class="gm-table">
|
<table class="gm-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -237,19 +237,18 @@
|
|||||||
<span class="status-badge @GetStatusClass(session.Status)">@TranslateStatus(session.Status)</span>
|
<span class="status-badge @GetStatusClass(session.Status)">@TranslateStatus(session.Status)</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="@session.JoinLink" target="_blank" rel="noopener noreferrer"
|
<a href="@session.JoinLink" target="_blank" rel="noopener noreferrer" class="session-join-link">
|
||||||
style="max-width: 150px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
||||||
Подключиться ↗
|
Подключиться ↗
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
<div class="session-table-actions">
|
||||||
<a href="/session/edit/@session.Id" class="btn-gm btn-gm-outline" style="font-size: 0.8125rem; padding: 0.375rem 0.75rem;">
|
<a href="/session/edit/@session.Id" class="btn-gm btn-gm-outline">
|
||||||
✏️ Изменить
|
✏️ Изменить
|
||||||
</a>
|
</a>
|
||||||
@if (CanPromote(session))
|
@if (CanPromote(session))
|
||||||
{
|
{
|
||||||
<button type="button" class="btn-gm btn-gm-success" style="font-size: 0.8125rem; padding: 0.375rem 0.75rem;" disabled="@(promotingSessionId == session.Id)" @onclick="() => PromoteWaitlisted(session.Id)">
|
<button type="button" class="btn-gm btn-gm-success" disabled="@(promotingSessionId == session.Id)" @onclick="() => PromoteWaitlisted(session.Id)">
|
||||||
@(promotingSessionId == session.Id ? "⏳ Поднимаем..." : "⬆️ Из ожидания")
|
@(promotingSessionId == session.Id ? "⏳ Поднимаем..." : "⬆️ Из ожидания")
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-51
@@ -1,5 +1,5 @@
|
|||||||
/* ============================================
|
/* ============================================
|
||||||
GM-Relay Design System v1.9.3
|
GM-Relay Design System v1.9.5
|
||||||
Dark RPG Dashboard Theme
|
Dark RPG Dashboard Theme
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
||||||
@@ -422,6 +422,46 @@ select option {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.session-table-desktop {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-table-desktop-card {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-table-desktop .gm-table {
|
||||||
|
min-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-table-desktop .gm-table th:last-child,
|
||||||
|
.session-table-desktop .gm-table td:last-child {
|
||||||
|
width: 8.5rem;
|
||||||
|
min-width: 8.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-join-link {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-table-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-table-actions .btn-gm {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* === Alerts === */
|
/* === Alerts === */
|
||||||
.gm-alert {
|
.gm-alert {
|
||||||
padding: 0.875rem 1.125rem;
|
padding: 0.875rem 1.125rem;
|
||||||
@@ -918,7 +958,64 @@ body.telegram-mini-app .content {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
body.telegram-mini-app .session-table-desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.telegram-mini-app .session-card-mobile {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(var(--glass-blur));
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
transition: all var(--transition-smooth);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card:hover {
|
||||||
|
border-color: var(--border-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-card-actions {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
.session-table-desktop {
|
.session-table-desktop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -926,56 +1023,9 @@ body.telegram-mini-app .content {
|
|||||||
.session-card-mobile {
|
.session-card-mobile {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.session-card {
|
@media (max-width: 768px) {
|
||||||
background: var(--glass-bg);
|
|
||||||
backdrop-filter: blur(var(--glass-blur));
|
|
||||||
border: 1px solid var(--glass-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
transition: all var(--transition-smooth);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card:hover {
|
|
||||||
border-color: var(--border-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card-title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.session-card-actions {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
padding-top: 0.75rem;
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,56 @@ public sealed class WebStylesTests
|
|||||||
Assert.Contains(".telegram-mini-app .nav-toggle", appCss, StringComparison.Ordinal);
|
Assert.Contains(".telegram-mini-app .nav-toggle", appCss, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AppCss_ShouldKeepDesktopSessionActionsReadableWhenTableOverflows()
|
||||||
|
{
|
||||||
|
var appCss = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/wwwroot/app.css"));
|
||||||
|
|
||||||
|
Assert.Matches(
|
||||||
|
@"\.session-table-desktop\s*\{[\s\S]*overflow-x:\s*auto;",
|
||||||
|
appCss);
|
||||||
|
Assert.Matches(
|
||||||
|
@"\.session-table-desktop\s+\.gm-table\s*\{[\s\S]*min-width:\s*760px;",
|
||||||
|
appCss);
|
||||||
|
Assert.Matches(
|
||||||
|
@"\.session-table-actions\s+\.btn-gm\s*\{[\s\S]*white-space:\s*nowrap;",
|
||||||
|
appCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AppCss_ShouldUseCardSessionLayoutInsideTelegramMiniApp()
|
||||||
|
{
|
||||||
|
var appCss = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/wwwroot/app.css"));
|
||||||
|
|
||||||
|
Assert.Matches(
|
||||||
|
@"body\.telegram-mini-app\s+\.session-table-desktop\s*\{[\s\S]*display:\s*none;",
|
||||||
|
appCss);
|
||||||
|
Assert.Matches(
|
||||||
|
@"body\.telegram-mini-app\s+\.session-card-mobile\s*\{[\s\S]*display:\s*block;",
|
||||||
|
appCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AppCss_ShouldUseCardSessionLayoutWhenDesktopSidebarLeavesNarrowContent()
|
||||||
|
{
|
||||||
|
var appCss = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/wwwroot/app.css"));
|
||||||
|
|
||||||
|
Assert.Matches(
|
||||||
|
@"@media\s*\(max-width:\s*1024px\)\s*\{[\s\S]*\.session-table-desktop\s*\{[\s\S]*display:\s*none;[\s\S]*\.session-card-mobile\s*\{[\s\S]*display:\s*block;",
|
||||||
|
appCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GroupDetailsPage_ShouldUseSessionTableLayoutClasses()
|
||||||
|
{
|
||||||
|
var groupDetailsPage = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Pages/GroupDetails.razor"));
|
||||||
|
|
||||||
|
Assert.Contains("session-table-desktop-card", groupDetailsPage, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("session-table-actions", groupDetailsPage, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("session-join-link", groupDetailsPage, StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain("overflow: hidden", groupDetailsPage, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private static string FindRepositoryFile(string relativePath)
|
private static string FindRepositoryFile(string relativePath)
|
||||||
{
|
{
|
||||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
|||||||
Reference in New Issue
Block a user