Skip to content
Open
8 changes: 4 additions & 4 deletions WARP.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ dotnet test --filter "ClassName"

### Docker Commands
```bash
# Start all services
# Start all services (API, Admin UI, SQL Server, Azurite)
docker compose --profile all up -d

# Start only dependencies (for local API/Admin development)
# Start only dependencies (SQL Server + Azurite for local API/Admin development)
docker compose --profile tools up -d

# Start only WebAPI
# Start only WebAPI (includes SQL Server + Azurite)
docker compose --profile webapi up -d

# Start only Admin UI
# Start only Admin UI (includes SQL Server, Azurite, and WebAPI)
docker compose --profile admin up -d

# View logs
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ services:
- SigningAuthority=https://app-ssw-ident-staging-api.azurewebsites.net/
volumes:
- ${CERTS_PATH}:/https:ro
profiles: ["all", "webapi"]
profiles: ["all", "webapi", "admin"]

# Admin UI
rewards-adminui:
Expand Down
173 changes: 13 additions & 160 deletions src/AdminUI/Pages/KioskLeaderboard.razor
Original file line number Diff line number Diff line change
@@ -1,87 +1,12 @@
@layout KioskLayout
@page "/kiosk-leaderboard"

@using SSW.Rewards.Shared.DTOs.Leaderboard
@using SSW.Rewards.ApiClient.Services
@using SSW.Rewards.Admin.UI.Components
@using SSW.Rewards.Enums

@inject ILeaderboardService leaderboardService

<MudPaper Class="kiosk-leaderboard" Style="padding: 2rem; margin-top: 0.5rem; min-height: 100vh; background: #121212; color: #fafafa;">
<style>
.kiosk-tabs .mud-tabs-toolbar {
justify-content: center;
background: #222;
border-radius: 12px;
padding: 0.3rem;
gap: 1.2rem;
}
.kiosk-tabs .mud-tabs-toolbar .mud-tabs-toolbar-inner {
min-height: 2.5rem;
}
.kiosk-tabs .mud-tab {
min-height: 2.5rem;
min-width: 80px !important;
padding: 0.4rem 1.2rem !important;
font-size: 1rem;
color: #fff !important;
background: #222 !important;
border-radius: 10px !important;
margin: 0 0.15rem;
transition: background 0.2s, color 0.2s;
}
.kiosk-tabs .mud-tab.mud-tab-active {
background: #CC4141 !important;
color: #fff !important;
border-radius: 10px !important;
}
.kiosk-tabs .mud-tabs-indicator,
.kiosk-tabs .mud-tab-active .mud-tab-indicator,
.kiosk-tabs .mud-tab-slider {
display: none !important;
height: 0 !important;
}
.kiosk-tabs .mud-tabs-scroll-button {
display: none !important;
}
.kiosk-tabs .mud-tooltip-root {
width: 100%;
}
.kiosk-tabs .mud-tabs-toolbar-content .mud-tabs-toolbar-wrapper {
width: 100%;
transform: translateX(-2px) !important;
}
.kiosk-leaderboard-table .mud-table {
font-size: 2.2rem;
}
.kiosk-leaderboard-table .mud-table-head .mud-table-cell {
background-color: #525252 !important;
}
.kiosk-leaderboard-table .mud-table-row:nth-child(2n) {
background-color: #333;
}
.kiosk-leaderboard-table .mud-table-row:nth-child(2n+1) {
background-color: #222;
}
.kiosk-leaderboard-table .mud-table-cell {
font-size: 1rem;
padding: 0.7rem;
}
</style>
<div style="display: flex; flex-direction: column; align-items: center; margin-bottom: 1.25rem;">
<MudImage Src="/images/ssw-rewards-logo.svg" Class="mb-2" Style="height: 60px; margin: 0 auto; display: block;" />
<MudText Typo="Typo.subtitle1" Style="color: #ccc; text-align: center; margin-top: 0.5rem;">
Use this app to scan SSW QR codes, earn SSW Points ⭐, claim rewards and win prizes!
</MudText>
</div>
<div style="display: flex; justify-content: center; align-items: center; margin-bottom: 1rem;">
<div style="width: 600px; max-width: 100%;">
Expand All @@ -95,11 +20,11 @@
</div>
<div style="display: flex; justify-content: center;">
<div style="width: 600px; max-width: 100%;">
<MudTable @ref="table" TItem="MobileLeaderboardUserDto" ServerData="@ServerReload" FixedHeader="true" Dense="false" Hover="true" Bordered="false" Striped="false" Class="kiosk-leaderboard-table" Style="border-radius: 16px; box-shadow: 0 4px 24px rgba(0,0,0,0.3); overflow: hidden; background: #181818;">
<MudTable @ref="table" TItem="MobileLeaderboardUserDto" ServerData="@ServerReload" FixedHeader="true" Dense="false" Hover="true" Bordered="false" Striped="false" Class="kiosk-leaderboard-table" Style="border-radius: 16px; box-shadow: 0 4px 24px rgba(0,0,0,0.3); overflow: hidden; background: #181818;" RowsPerPage="@_defaultPageSize">
<HeaderContent>
<MudTh Style="width: 80px; text-align: center;">Rank</MudTh>
<MudTh Style="width: 320px;">Name</MudTh>
<MudTh Style="width: 160px; text-align: center;">Total Points</MudTh>
<MudTh Style="width: 160px; text-align: center;">Points</MudTh>
</HeaderContent>
<RowTemplate Context="row">
<MudTd Style="width: 80px; text-align: center;" DataLabel="Rank">@row.Rank</MudTd>
Expand All @@ -113,98 +38,26 @@
}
</div>
</MudTd>
<MudTd Style="width: 160px; text-align: center;" DataLabel="Points">@row.Points</MudTd>
<MudTd Style="width: 160px; text-align: center;" DataLabel="Points">@row.Points.ToString("N0")</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager PageSizeOptions="@_pageSizeOptions" />
</PagerContent>
</MudTable>
<!-- TODO: Hide if no scrolling -->
<div class="page-progress-container">
<div class="page-progress-bar" style="width: @_progressBarWidth%;"></div>
</div>
Comment on lines +45 to +47
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment indicates incomplete implementation. The progress bar is shown unconditionally, but the comment suggests it should be hidden when scrolling is not active (i.e., when there's only one page).

Issue: When _totalPages <= 1, the progress bar shows at 100% width (per line 92 in the .cs file), which may be confusing to users.

Suggestion: Implement the conditional rendering:

@if (_totalPages > 1)
{
    <div class="page-progress-container">
        <div class="page-progress-bar" style="width: @_progressBarWidth%;"></div>
    </div>
}
Suggested change
<div class="page-progress-container">
<div class="page-progress-bar" style="width: @_progressBarWidth%;"></div>
</div>
@if (_totalPages > 1)
{
<div class="page-progress-container">
<div class="page-progress-bar" style="width: @_progressBarWidth%;"></div>
</div>
}

Copilot uses AI. Check for mistakes.
</div>
</div>
@if (!_hasErrorsOnUpdate)
{
<div style="text-align: center; margin-top: 2rem; font-size: 1.1rem; color: #ccc;">
Last updated: @(_lastUpdated != null ? _lastUpdated?.ToString("dd MMMM yyyy HH:mm") : "never")
Last refreshed: @GetLastUpdatedText() (refreshing in @_secondsUntilRefresh seconds)
</div>
}
else
{
<div style="text-align: center; margin-top: 2rem; font-size: 1.1rem; color: #FF7A7A;">
⚠️ We couldn't refresh the leaderboard. Retrying in 1 min.<br />
Last updated: @(_lastUpdated != null ? _lastUpdated?.ToString("dd MMMM yyyy HH:mm") : "never")
⚠️ We couldn't refresh the leaderboard. Retrying in @_secondsUntilRefresh seconds.<br />
Last refreshed: @GetLastUpdatedText()
</div>
}
</MudPaper>

@code {
private MudTable<MobileLeaderboardUserDto> table;
private LeaderboardFilter _selectedFilter = LeaderboardFilter.ThisWeek;
private int _activeTabIndex = 0;
private readonly int[] _pageSizeOptions = new[] { 10, 25, 50, 100, 250 };
private System.Timers.Timer? _refreshTimer;
private DateTime? _lastUpdated;
private bool _hasErrorsOnUpdate = false;

private TableData<MobileLeaderboardUserDto> _lastTableCache = new() { TotalItems = 0, Items = [] };

protected override async Task OnInitializedAsync()
{
await LoadLeaderboard();

_refreshTimer = new System.Timers.Timer(60000); // 1 minute
_refreshTimer.Elapsed += async (_, __) => await InvokeAsync(LoadLeaderboard);
_refreshTimer.AutoReset = true;
_refreshTimer.Start();
}

private async Task<TableData<MobileLeaderboardUserDto>> ServerReload(TableState state)
{
try
{
var result = await leaderboardService.GetMobilePaginatedLeaderboard(state.Page, state.PageSize, _selectedFilter, CancellationToken.None);
_lastUpdated = DateTime.Now;
_hasErrorsOnUpdate = false;
_lastTableCache = new() { TotalItems = result.Count, Items = result.Items };
}
catch (Exception ex)
{
_hasErrorsOnUpdate = true;
Console.WriteLine($"Error in ServerReload: {ex.Message}");
}
StateHasChanged();
return _lastTableCache;
}

private async Task LoadLeaderboard()
{
if (table != null)
{
await table.ReloadServerData();
}
}

private async Task OnTabChanged(int tabIndex)
{
_activeTabIndex = tabIndex;
_selectedFilter = tabIndex switch
{
0 => LeaderboardFilter.ThisWeek,
1 => LeaderboardFilter.ThisMonth,
2 => LeaderboardFilter.ThisYear,
3 => LeaderboardFilter.Forever,
_ => LeaderboardFilter.ThisWeek
};

await LoadLeaderboard();
}

public void Dispose()
{
if (_refreshTimer != null)
{
_refreshTimer.Stop();
_refreshTimer.Dispose();
_refreshTimer = null;
}
}
}
Loading
Loading