feat(discord): make /newsession identical to Telegram wizard
PR Checks / test-and-build (pull_request) Successful in 30m45s

- Remove legacy DiscordNewSessionCommand/Handler and their tests.
- Rename /newsession-wizard to /newsession.
- Add shared pool capacity step before Format/Location.
- Render Format and Location in Discord wizard; Location uses a modal.
- Propagate Format, JoinLink and LocationAddress in BuildCommand.
- Publish created sessions through existing IPlatformMessenger pipeline.
- Update README, version bump to 3.11.1, sync compose/deploy/NavMenu.
This commit is contained in:
2026-06-15 17:49:53 +03:00
parent a391c51761
commit 9709d09b15
23 changed files with 362 additions and 560 deletions
@@ -224,7 +224,16 @@ public sealed class GameCreationWizard
? (WizardStepNames.Capacity, SetScheduledAt(payload, dt), payload)
: (null, dt == default ? "Не удалось разобрать дату" : "Дата в прошлом", payload);
case WizardStepNames.Capacity when payload.Single?.MaxPlayers is null:
case WizardStepNames.Capacity:
if (payload.Type == WizardCreationType.Pool)
{
if (payload.Pool?.MaxPlayers is not null) return (null, "Лимит уже задан", payload);
return int.TryParse(input, out var poolCap) && poolCap >= WizardStepLimits.MinCapacity && poolCap <= WizardStepLimits.MaxCapacity
? (WizardStepNames.Format, SetPoolMaxPlayers(payload, poolCap), payload)
: (null, "Лимит должен быть 1..50", payload);
}
if (payload.Single?.MaxPlayers is not null) return (null, "Лимит уже задан", payload);
return int.TryParse(input, out var cap) && cap >= WizardStepLimits.MinCapacity && cap <= WizardStepLimits.MaxCapacity
? (WizardStepNames.Format, SetMaxPlayers(payload, cap), payload)
: (null, "Лимит должен быть 1..50", payload);
@@ -314,10 +323,12 @@ public sealed class GameCreationWizard
{
if (choice is "no_limit")
{
return (WizardStepNames.Format, SetMaxPlayers(p, null));
return p.Type == WizardCreationType.Pool
? (WizardStepNames.Format, SetPoolMaxPlayers(p, null))
: (WizardStepNames.Format, SetMaxPlayers(p, null));
}
if (choice is "waitlist:on" or "waitlist:off" && p.Single?.MaxPlayers is null)
if (choice is "waitlist:on" or "waitlist:off" && p.Type != WizardCreationType.Pool && p.Single?.MaxPlayers is null)
{
return (null, "Сначала введите лимит мест или нажмите «♾ Без лимита»");
}
@@ -368,11 +379,12 @@ public sealed class GameCreationWizard
{
"_custom" => (WizardStepNames.PoolSystemDuration, null),
{ } c when c.Contains(':') => SplitSystemDuration(c) is (var sys, var dur)
? (WizardStepNames.Format, SetSystem(p, sys) ?? SetDurationMinutes(p, dur))
? (WizardStepNames.Capacity, SetSystem(p, sys) ?? SetDurationMinutes(p, dur))
: (null, "Неверный выбор"),
_ => (null, "Неизвестный выбор"),
};
private static (string?, string?) ApplyPoolAddSlotsChoice(WizardPayload p, string choice) => choice switch
{
"add" => BeginNewPoolSlot(p),
@@ -410,7 +422,7 @@ public sealed class GameCreationWizard
WizardStepNames.Duration => WizardStepNames.System,
WizardStepNames.DateTime => WizardStepNames.Duration,
WizardStepNames.Capacity => WizardStepNames.DateTime,
WizardStepNames.Format => p.Type == WizardCreationType.Pool ? WizardStepNames.PoolSystemDuration : WizardStepNames.Capacity,
WizardStepNames.Format => WizardStepNames.Capacity,
WizardStepNames.Location => WizardStepNames.Format,
WizardStepNames.Visibility => WizardStepNames.Location,
WizardStepNames.PickClub => WizardStepNames.Visibility,
@@ -458,6 +470,8 @@ public sealed class GameCreationWizard
{ p.Single ??= new WizardSingleInput(); p.Single.ScheduledAt = v; return null; }
private static string? SetMaxPlayers(WizardPayload p, int? v)
{ p.Single ??= new WizardSingleInput(); p.Single.MaxPlayers = v; return null; }
private static string? SetPoolMaxPlayers(WizardPayload p, int? v)
{ p.Pool ??= new WizardPoolInput(); p.Pool.MaxPlayers = v; return null; }
private static string? SetWaitlist(WizardPayload p, bool v) { p.Waitlist = v; return null; }
private static string? SetVisibility(WizardPayload p, WizardVisibility? v) { p.Visibility = v; return null; }
private static string? SetClubId(WizardPayload p, Guid v) { p.ClubId = v; return null; }
@@ -518,7 +532,7 @@ public sealed class GameCreationWizard
private static string? NextAfterSystem(WizardPayload p) => WizardStepNames.Duration;
private static string? NextAfterDuration(WizardPayload p)
{
if (p.Type == WizardCreationType.Pool) return WizardStepNames.Format;
if (p.Type == WizardCreationType.Pool) return WizardStepNames.Capacity;
return p.Single?.MaxPlayers is not null ? WizardStepNames.Format : WizardStepNames.DateTime;
}
private static string? NextAfterVisibility(WizardPayload p)
@@ -530,6 +544,7 @@ public sealed class GameCreationWizard
return p.Type == WizardCreationType.Pool ? WizardStepNames.PoolAddSlots : WizardStepNames.Publish;
}
private static (string? sys, int? dur) SplitSystemDuration(string s)
{
var idx = s.IndexOf(':');
@@ -50,6 +50,8 @@ public sealed class WizardPayload
public sealed class WizardPoolInput
{
public int? MaxPlayers { get; set; }
public List<WizardSlotInput> Slots { get; set; } = new();
}
@@ -224,6 +224,7 @@ public static class WizardStepViewBuilder
if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}");
if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч");
AppendFormatLocation(sb, p);
if (p.Pool?.MaxPlayers is { } poolMax) sb.AppendLine($"👥 Мест в пуле: {poolMax}");
sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}");
sb.AppendLine();
sb.AppendLine($"Слоты ({p.Pool?.Slots.Count ?? 0}):");